#-*- coding: utf-8 -*-

#Copyright 2008 Calculate Pack, http://www.calculate-linux.ru
#
#  Licensed under the Apache License, Version 2.0 (the "License");
#  you may not use this file except in compliance with the License.
#  You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
#  Unless required by applicable law or agreed to in writing, software
#  distributed under the License is distributed on an "AS IS" BASIS,
#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#  See the License for the specific language governing permissions and
#  limitations under the License.
Version = "calculate-server 2.0.12"

import sys
sys.path.append('/var/tmp/calculate2/calculate-lib/trunk/pym')
import cStringIO
import cl_base
import struct
import termios
import fcntl
import cl_utils

import cl_profile
import ldap
class ldapFun(cl_profile._error):
    '''Объект для работы с LDAP сервером

        подключение к серверу и поиск данных
    '''
    def __init__(self, dnUser, password, host="localhost"):
        self.conLdap = False
        # Получаем соединение с LDAP
        try:
            self.conLdap = self.__ldapConnect(dnUser, password, host)
        except ldap.LDAPError, e:
            self.setError(e[0]['desc'])

    def __ldapConnect(self, dnUser, password, host):
        """Соединение с LDAP сервером"""
        conLdap = ldap.initialize('ldap://%s'%host)
        conLdap.simple_bind_s(dnUser, password)
        return conLdap

    def ldapSearch(self,baseDN, searchScope, searchFilter, retrieveAttributes):
        try:
            ldap_result_id = self.conLdap.search(baseDN, searchScope,
                                                searchFilter,
                                                retrieveAttributes)
            result_set = []
            while 1:
                result_type, result_data = self.conLdap.result(ldap_result_id,
                                                               0)
                if (result_data == []):
                    break
                else:
                    if result_type == ldap.RES_SEARCH_ENTRY:
                        result_set.append(result_data)
        except ldap.NO_SUCH_OBJECT:
            return []
        except:
            return False
        return result_set

pcs = cl_utils.prettyColumnStr

class cl_help:
    """Объект для работы со справкой, и обработкой параметров.

    Конструктор __init__ должен определить следующие переменные:

    self.chapter     список разделов справки, каждый элементы состоит из
                     имени раздела, флаг видимый/скрытый, кол-во переводов строк
                     после названия раздела, количество строк после раздела,
                     тип раздела
                     Пример: [("Copyright",False,0,2),"options"]
    self.relService  словарь связей сервисов и действующих опций
                     ключ - название сервиса, значение - список отображаемых
                     разделов отмеченных как "options"
                     Пример: {"samba":[_("Common options"),
                                      _("Service Samba options")]}
    self.relOptions  словарь связей длинных опций помощи и выводимых разделов
                     помощи с опциями
                     ключ - параметр справки, значение список отображаемых
                     разделов справки
                     Пример: {"help-ldap":[_("Common options"),
                                           _("Service LDAP options)]}
    self.progName    словарь имена используемых программ и их номера для
                     доступа к переменным
                     Пример: {'cl-groupadd':0, 'cl-groupdel':1}
    self.data        список данных для справки, каждый элемент словарь:
                         progAccess: список номеров программ отображающих
                     Пример: {'progAccess':(0,),
                              'shortOption':"g",
                              'longOption':"gid",
                              'optVal':"GID",
                              'helpChapter':_("Options"),
                              'help':_("use GID for the new group")
                             },
    после заполнения параметров необходимо выполнить
         self._cl_help__setParamHelp() для заполнения справки

    """
    def __init__(self, cmdName):
        # ширина консоли взята за 80
        # -1 чтобы компенсировать расстрояние между колонками
        self.consolewidth = 79
        self.column_width = 32
        self.cmdName = cmdName
        #короткие опции командной строки
        self.shortOpt = []
        #длинные опции командной строки
        self.longOpt = []
        # массив разделов (заполняется в __setParamHelp)
        self.chapterBloc = []
        #optEnd = ""
        #if "user" in self.cmdName and not "mod" in self.cmdName:
            #optEnd = _("user")
        #elif "group" in self.cmdName and not "mod" in self.cmdName:
            #optEnd = _("group")
        #self.__setParamHelp()

    def getChapterNumber(self,NameChapter):
        """Получить номер раздела по имени"""
        num = 0
        for i in  self.chapter:
            if i[0] == NameChapter:
                return num
            num += 1
        return False

    def __setParamHelp(self):
        """Внутренняя функция формирования справки по данным

        Перебирает все элементы списка data, проверяет их на доступность
        данной программы, разбирает опции на среди data и формирует
        для по ним справку.
        """
        # сформировать нужное количество блоков раздела
        self.chapterBloc = [""]*len(self.chapter)
        #
        sp = {}
        i = 0
        # перебираем все элементы справки собираем элементы опции
        # так же формируем разделы не опции
        for par in self.data:
            # перебираем только те опции, которые принадлежат команде
            if self.access(par):
                # есть короткая (возможно есть и длинная)
                if par.has_key("shortOption"):
                    sp[par["shortOption"]+":"+par["helpChapter"]] = i
                # есть только длинная опция
                elif par.has_key("longOption"):
                    sp[par["longOption"]+":"+par["helpChapter"]] = i
                # формирование разделов не опций
                else:
                    helpTxt = par['help']
                    numChapter = self.getChapterNumber(par['helpChapter'])
                    self.addChapterHelp(numChapter,helpTxt)
            i += 1
        # перебираем все "собранные" опции
        # опции перебираются по порядку в списке date
        # для сортировки по ключам следует применить код:
        # for index in sorted(sp.keys()):
        #     par = self.data[sp[index]]
        for index in sorted(sp.values()):
            par = self.data[index]
            numChapter = self.getChapterNumber(par['helpChapter'])
            # если есть и короткая и длинная
            if "shortOption" in par and "longOption" in par:
                paraminfo = "-%s, --%s "%(par["shortOption"],par["longOption"])
            # если есть только короткая
            elif "shortOption" in par:
                paraminfo = "-%s "%par["shortOption"]
            # если только длинная
            else:
                paraminfo = "--%s "%par["longOption"]
            # если указан параметр для опции
            if "optVal" in par:
                optVal = par["optVal"]
            else:
                optVal = ""

            # вывод вида: "  [-o, ][--option] [PARAM]"  "helpstring"
            helpTxt = pcs("  "+paraminfo+optVal, self.column_width, \
                          par['help'], self.consolewidth-self.column_width)
            # добавить строку в нужный раздел
            self.addChapterHelp(numChapter,helpTxt)

    def getHelp(self, optionsChapters=False):
        """Выдать справку.

        Выдает справку в случае если указан optionsChapters, то фильтрует по
        типу разделов.

        Параметры:
        optionsChapters     Flase или список опциональных разделов для
                            отображения

        Возвращаемые параметры:
        Строка со справкой.
        """
        # Выдать справку
        help = ""
        # перебираем все элементы справочных блоков
        iterChapterBloc = iter(self.chapterBloc)
        # перебираем все разделы по параметрам
        for (nameChapter, visibleChapter, beforeStrChapter, \
                afterStrChapter, typeChapter) in self.chapter:
            # получаем следующий блок (т.о. textChapterBloc соответ, chapter)
            textChapterBloc = iterChapterBloc.next()
            # если тип раздела опциональный
            if optionsChapters and typeChapter=="options":
                # проверяем нужно ли его отображать
                if not (nameChapter in optionsChapters):
                    continue
            bef = "\n"*beforeStrChapter
            aft = "\n"*afterStrChapter
            # если блок не пустой и раздел отображаемый
            if len(textChapterBloc) > 0:
                if visibleChapter:
                    help += nameChapter + ": " + bef
                help += textChapterBloc + aft
        help = help.rstrip()+"\n"
        return help

    def addChapterHelp(self, numChapter, helpTxt):
        """Добавить в раздел помощи numChapteк тектстовую строку helpTxt

        Параметры:
        numChapter      номер раздела в который нужно добавить данные справки
        helpTxt         строка, содержащая данные
        """
        self.chapterBloc[numChapter] += helpTxt
        return True

    def addData(self,dataHash):
        # На будущее (добавляет опции)
        self.data.append(dataHash)
        return True

    def handleCheckAccess(self,dataHash):
        """Замещаемый дополнительный обработчик проверки
        доступности опции.

        Входные параметры:
        dataHash      элементы списка данных справки (self.data)
        """
        return True

    def access(self,dataHash):
        """Доступна ли опция вызывающей программе

        Параметры:
        dataHash        словарь элемент типа self.data

        Возвращаемые параметры:
        True/False      доступна/недоступна
        """
        # доступна ли опция вызывающей программе
        # опция без progAccess доступна
        numProg = self.progName[self.cmdName]
        if 'progAccess' in dataHash:
            if numProg in dataHash['progAccess']:
                # вызов дополнительной проверки доступа к опции
                return self.handleCheckAccess(dataHash)
            else:
                return False
        else:
            # вызов дополнительной проверки доступа к опции
            return self.handleCheckAccess(dataHash)

    def getTypeChapter(self, nameChapter):
        """Получить тип раздела по его имени

        Параметры:
        nameChapter     название раздела

        Возвращаемые параметры:
        строка          тип раздела
        Flase(Boolean)  такой раздел отсутствует
        """
        # фильтруем список по имени раздела, помещаем в список тип раздела
        filtered = [typeChapter for name, na, na, na, typeChapter \
                    in self.chapter if name == nameChapter]
        # если среди фильтрованных есть хоть один элемент
        if len(filtered) > 0:
            # возвращаем - он запрашиваемый
            return filtered[0]
        else:
            # такой раздел отсутствует
            return False

    def clearAllOpt(self):
        """Очистить все опции, полученные посредством getAllOpt"""
        if len(self.shortOpt) > 0:
            self.shortOpt = []
        if len(self.longOpt) > 0:
            self.longOpt = []
        return True

    def getAllOpt(self,typeOpt="all", optionsChapters=False):
        """Получить все доступные опции

        Параметры:
        typeOpt          'short'/'long'/'all', вернуть короткие или длинные
                         опции или все (возвращаются кортежем)
        optionsChapters  фильтр для опций по типам разделов (список,кортеж)

        Возвращаемые параметры:
        строка коротки или список строк длинных опций ('hb:c:wg:G:k:ms:u:')
        """
        # Выдать все действующие опции
        if typeOpt=="short" or typeOpt=="all":
            if len(self.shortOpt) == 0:
                for par in self.data:
                    if optionsChapters and\
                        self.getTypeChapter(par['helpChapter'])=="options":
                        if not (par['helpChapter'] in optionsChapters):
                            continue
                    if par.has_key("shortOption") and self.access(par):
                        if par.has_key("optVal"):
                            self.shortOpt.append(par["shortOption"]+':')
                        else:
                            self.shortOpt.append(par["shortOption"])
        if typeOpt=="long" or typeOpt=="all":
            if len(self.longOpt) == 0:
                for par in self.data:
                    if optionsChapters and\
                        self.getTypeChapter(par['helpChapter'])=="options":
                        #print par["longOption"]
                        if not (par['helpChapter'] in optionsChapters):
                            continue
                    if par.has_key("longOption") and self.access(par):
                        if par.has_key("optVal"):
                            self.longOpt.append(par["longOption"]+'=')
                        else:
                            self.longOpt.append(par["longOption"])
        if typeOpt=="short":
            return "".join(self.shortOpt)
        elif typeOpt=="long":
            return self.longOpt
        elif typeOpt=="all":
            return ("".join(self.shortOpt),self.longOpt)

    def getShortOpt(self,option):
        """Из любой опции получить короткую опцию.

        Фильтрация также происходит и по названию команды.

        Параметры:
        option     запрашиваемая опция

        Возвращаемые параметры:
        короткая опция, если же для длинной опции нет короткой, возвращается
        пустая строка.
        """
        # Из любой опции получаем короткую опцию
        for par in self.data:
            if par.has_key("shortOption") and self.access(par):
                if (par.has_key("longOption") and\
                par["longOption"] == option) or \
                par["shortOption"] == option:
                    return par["shortOption"]
                    break
        return ""

class cl_smartcon(object):

    def getconsolewidth(self):
        """Получить ширину текущей консоли"""
        s = struct.pack("HHHH", 0, 0, 0, 0)
        fd_stdout = sys.stdout.fileno()
        try:
            x = fcntl.ioctl(fd_stdout, termios.TIOCGWINSZ, s)
        except IOError:
            # если ошибка то ширина 80 символов
            return 80
        #(rows, cols, x pixels, y pixels)
        return struct.unpack("HHHH", x)[1]

    def printRight(self, offsetLeft, offsetRight):
        """Добавляет необходимое количество пробелов:

        количество пробелов = (ширина консоли - offsetLeft - offsetRight)
        """
        cols = self.getconsolewidth()
        for i in range(cols - offsetLeft - offsetRight):
            sys.stdout.write(" ")

    def colorPrint(self,attr,fg,bg,string):
        """Раскрашивает выводимое сообщение

        Параметры:
            attr - это атрибут
            fg - цвет символа
            bg - цвет фона

        в случае если параметр равен "" то он не изменяется

        attr может принимать следующие значения:
        0       сбросить все атрибуты (вернуться в нормальный режим)
        1       яркий (обычно включает толстый шрифт)
        2       тусклый
        3       подчёркнутый
        5       мигающий
        7       реверсный
        8       невидимый

        fg может принимать следующие значения:
        30      чёрный
        31      красный
        32      зелёный
        33      жёлтый
        34      синий
        35      фиолетовый
        36      голубой
        37      белый

        bg может принимать следующие значения:
        40      чёрный
        41      красный
        42      зелёный
        43      жёлтый
        44      синий
        45      фиолетовый
        46      голубой
        47      белый
        """
        lst = []
        if attr:
            lst.append(attr)
        if fg:
            lst.append(fg)
        if bg:
            lst.append(bg)
        sys.stdout.write("\033[%sm%s\033[0m" %(";".join(lst),string))

    def redBrightPrint(self, string):
        """Печатает яркое красное сообщение"""
        self.colorPrint("1","31","",string)

    def greenBrightPrint(self, string):
        """Печатает яркое зеленое сообщение"""
        self.colorPrint("1","32","",string)

    def yellowBrightPrint(self, string):
        """Печатает яркое желтое сообщение"""
        self.colorPrint("1","33","",string)

    def blueBrightPrint(self, string):
        """Печатает яркое cинее сообщение"""
        self.colorPrint("1","34","",string)

    def lenString(self, string):
        """Получаем длинну строки"""
        stringUnicode = cl_utils._toUNICODE(string)
        lenString = len(stringUnicode)
        return lenString


    def defaultPrint(self, string):
        sys.stdout.write(string)
        sys.stdout.flush()

    def printLine(self, argL, argR):
        """Печатает справа и слева консоли цветные сообщения"""
        #Допустимые цвета
        colorDict = {\
                 # цвет по умолчанию
                 '':self.defaultPrint,
                 # ярко зеленый
                 'greenBr':self.greenBrightPrint,
                 # ярко голубой
                 'blueBr':self.blueBrightPrint,
                 # ярко красный
                 'redBr':self.redBrightPrint,
                 # ярко желтый
                 'yellowBr':self.yellowBrightPrint,
        }
        # cмещение от левого края консоли
        offsetL = 0
        for color,leftString in argL:
            offsetL += self.lenString(leftString)
            if colorDict.has_key(color):
                # печатаем и считаем смещение
                colorDict[color](leftString)
            else:
                colorDict[''](leftString)
        # cмещение от правого края консоли
        offsetR = 0
        for color,rightString in argR:
            offsetR += self.lenString(rightString)
        # Добавляем пробелы
        self.printRight(offsetL, offsetR)
        for color,rightString in argR:
            if colorDict.has_key(color):
                # печатаем и считаем смещение
                colorDict[color](rightString)
            else:
                colorDict[''](rightString)
        print ""

    def printNotOK(self, string):
        """Вывод на печать в случае сбоя"""
        self.printLine((('greenBr',' * '),
                        ('',string),
                       ),
                       (('blueBr','['),
                        ('redBr',' !! '),
                        ('blueBr',']'),
                       )
                      )

    def printOK(self, string):
        """Вывод на печать в случае успеха"""
        self.printLine((('greenBr',' * '),
                        ('',string),
                       ),
                       (('blueBr','['),
                        ('greenBr',' ok '),
                        ('blueBr',']'),
                       )
                      )
    def printWARNING(self, string):
        """Вывод на печать предупреждения"""
        self.printLine((('yellowBr',' * '),
                        ('',string),
                       ),
                       (('',''),
                       )
                      )
    def printERROR(self, string):
        """Вывод на печать предупреждения"""
        self.printLine((('redBr',' * '),
                        ('',string),
                       ),
                       (('',''),
                       )
                      )
    def printSUCCESS(self, string):
        """Вывод на печать в случае успеха без  [ok] справа"""
        self.printLine((('greenBr',' * '),
                        ('',string),
                       ),
                       (('',''),
                       )
                      )
