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

# Copyright 2008-2016 Mir Calculate. http://www.calculate-linux.org
#
#  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.

import types
from calculate.lib import cl_overriding
import re
import sys
from itertools import *

from calculate.lib.cl_lang import setLocalTranslate

_ = lambda x: x
setLocalTranslate('cl_lib3', sys.modules[__name__])

_u = lambda t: t.decode('UTF-8', 'replace') if isinstance(t, str) else t
_uu = lambda *tt: tuple(_u(t) for t in tt)
_u8 = lambda t: t.encode('UTF-8', 'replace') if isinstance(t, unicode) else t
_uu8 = lambda *tt: tuple(_u8(t) for t in tt)


def columnMatrix(*cols):
    """
    Split columns text to matrix
    """

    def splitter(s, wid):
        """Split string by width and \\n"""
        for part in _u(s).split('\n'):
            if wid > 0:
                while part:
                    p, part = part[:wid], part[wid:]
                    yield p
            else:
                yield part

    return izip_longest(
        *starmap(splitter,
                 izip_longest(islice(cols, 0, None, 2),
                              islice(cols, 1, None, 2))),
        fillvalue="")


def columnStr(*cols):
    """
    Get string for write, data by columns

    Parameters:
    cols     list(tuple(text,width))

    Return:
    string for display

    Example: columnStr( "Some text", 10, "Next column", 20 )
    """

    def generateStrings(*cols):
        colWidth = list(islice(cols, 1, None, 2))
        for line in map(lambda x: izip_longest(colWidth, x),
                        columnMatrix(*cols)):
            yield " ".join(("{0:%d}" % (width or 1)).format(s) for \
                           width, s in line)

    return "\n".join(generateStrings(*cols))


def columnWrite(*cols):
    """
    Display data by columns

    Parameters:
    cols     list(tuple(text,width))

    Example: columnWrite( "Some text", 10, "Next column", 20 )
    """
    colWidth = list(islice(cols, 1, None, 2))
    for line in map(lambda x: izip_longest(colWidth, x), columnMatrix(*cols)):
        for width, s in line:
            cl_overriding.printSUCCESS(("{0:%d} " % (width or 1)).format(s),
                                       printBR=False)
        cl_overriding.printSUCCESS("", printBR=True)


def justify(s, width):
    """
    Выровнить текст по ширине

    Параметры:
    s      выводимая строка
    width  ширина на которую надо выровнить строку

    Возвращаямые параметры:
    Выровненная строка
    """
    # если подана строка без пробелов - прекратить обработку
    if s.find(' ') == -1:
        return s
    pos = 0
    # переводим в юникод для правильного вычисления длины
    try:
        s = s.decode('utf-8')
    # пропуск если это не utf-8
    except UnicodeEncodeError:
        pass
    # пока длина строки меньше указанной
    while len(s) < width:
        # находим очередной пробел
        pos = s.find(' ', pos)
        # если не найден искать сначала
        if pos == -1:
            pos = s.find(' ')
        # вставить в позицию еще один пробел
        s = s[:pos] + ' ' + s[pos:]
        # оставить удвоенный пробел
        pos += 3
    # вернуть строку в utf8 если она пришла в utf8
    return s.encode('utf-8')


class tableReport(object):
    """Display data in table"""

    def __init__(self, title, headerList, dataList, colSpan=1):

        # title
        self.title = title
        # elements list for first table row
        self.headerList = headerList
        # row list (each row is list)
        self.dataList = map(lambda x: map(lambda y: str(y).expandtabs(),
                                          map(lambda y: y or "", x)),
                            dataList)
        # calculate columns list
        self.columnsWidth = self.getColumsnWidth()
        self.vertChar = colSpan * " " + "|" + colSpan * " "
        self.crossChar = colSpan * "-" + "+" + colSpan * "-"
        self.colSpan = colSpan
        self.align = "<" + "^" * (len(headerList) - 2) + "<"

    def setAutosize(self, colNum=-1, maxSize=None, forceMax=False):
        """
        Set width for specified column
        """
        if maxSize is None:
            maxSize = get_term_size()[1]
            if not maxSize:
                maxSize = 80
        spanSize = self.colSpan * 2 + 1
        if colNum < 0:
            colNum += len(self.columnsWidth)
        currentSize = sum(map(lambda x: x + spanSize, self.columnsWidth)) + 1
        if currentSize < maxSize and not forceMax:
            return
        excludeSize = sum(map(lambda x: x[1] + spanSize,
                              filter(lambda x: x[0] != colNum,
                                     enumerate(
                                         self.columnsWidth)))) + spanSize + 1
        if maxSize - excludeSize > 5:
            self.columnsWidth[colNum] = maxSize - excludeSize

    def getColumsnWidth(self):
        """Находит максимальную ширину каждой колонки

        результат список ширин колонок
        """
        columnsWidth = []
        for s in self.headerList:
            columnsWidth.append(len(_u(s)))
        lenCol = len(self.headerList)
        maxLenCol = lenCol
        # Вычисляем максимальное количество столбцов
        for s in self.dataList:
            lenS = len(s)
            if maxLenCol < lenS:
                maxLenCol = lenS
        if maxLenCol > lenCol:
            appCol = maxLenCol - lenCol
            # Добавляем элементы в список ширин
            for i in range(appCol):
                columnsWidth.append(0)
        # Вычисляем ширину столбцов
        for e in self.dataList:
            i = 0
            for s in e:
                lenS = len(_u(s))
                if columnsWidth[i] < lenS:
                    columnsWidth[i] = lenS
                i += 1
        return columnsWidth

    def createFormatStr(self, listStr):
        """Создает список (текст, ширина ...)"""
        return chain(*izip_longest(listStr, self.columnsWidth, fillvalue=""))

    def printFunc(self, s):
        """
        Overriding print
        """
        cl_overriding.printSUCCESS(s, printBR=False)

    def line_printer(self, s):
        """
        Вывод используется для отображения "линий" таблицы
        """
        self.printFunc(s)

    def head_printer(self, s):
        """
        Вывод используется для отображения текста в шапке
        """
        self.printFunc(s)

    def body_printer(self, s):
        """
        Вывод используется для отображения текста в таблице
        """
        self.printFunc(s)

    def default_printer(self, s):
        self.printFunc(s)

    def printReport(self, printRows=True):
        """Напечатать данные в табличном виде"""
        if self.title:
            self.printFunc(self.title)
        listStrSep = []
        for lenCol in self.columnsWidth:
            listStrSep.append("-" * lenCol)
        printData = [[self.crossChar, self.line_printer,
                      columnMatrix(*self.createFormatStr(listStrSep))],
                     [self.vertChar, self.head_printer,
                      columnMatrix(*self.createFormatStr(self.headerList))],
                     [self.crossChar, self.line_printer,
                      columnMatrix(*self.createFormatStr(listStrSep))]]
        for s in self.dataList:
            printData.append([self.vertChar, self.body_printer,
                              columnMatrix(*self.createFormatStr(s))])
        printData.append([self.crossChar, self.line_printer,
                          columnMatrix(*self.createFormatStr(listStrSep))])
        last_col = len(self.headerList) - 1

        for char, _print, row in printData:
            for line in row:
                self.line_printer(char[self.colSpan:])
                for i, align, width, txt in izip(count(), self.align,
                                                 self.columnsWidth,
                                                 line):
                    _print((u"{0:%s%d}" % (align, width)).format(txt))
                    if i < last_col:
                        self.line_printer(char)
                self.line_printer(char[:-self.colSpan + 1])
                self.default_printer('\n')
        if printRows:
            self.printFunc("(%s %s)" % (len(self.dataList), _("rows")))


class MultiReplace:
    """MultiReplace function object
    
    Usage:
    replacer = MultiReplace({'str':'efg','in':'asd'})
    s = replacer("string")
    """

    def __init__(self, repl_dict):
        # string to string mapping; use a regular expression
        keys = repl_dict.keys()
        keys.sort(reverse=True)  # lexical order
        pattern = u"|".join([re.escape(key) for key in keys])
        self.pattern = re.compile(pattern)
        self.dict = repl_dict

    def replace(self, s):
        # apply replacement dictionary to string
        def repl(match, get=self.dict.get):
            item = match.group(0)
            return get(item, item)

        return self.pattern.sub(repl, s)

    __call__ = replace


def str2dict(s):
    """Convert string to dictionary:

    String format:
    {'key1':'value1','key2':'val\'ue2'}
    """
    value = r'(?:\\\\|\\\'|[^\'])'
    pair = r"""
        \s*'           # begin key 
        (%(v)s*)
        '              # end key 
        \s*:\s*        # delimeter key/value
        '              # begin value
        (%(v)s*)
        '\s*           # end value
        """ % {"v": value}
    reDict = re.compile(pair, re.X)
    reMatchDict = re.compile("""
            ^{          # begin dict
            ((%(v)s,)*  # many pair with comma at end
            %(v)s)?     # pair without comma
            }$          # end dict
            """ % {'v': pair}, re.X)
    if reMatchDict.match(s.strip()):
        d = dict(reDict.findall(s))
        replaceSlash = MultiReplace({'\\\\': '\\', '\\\'': '\''})
        for i in d.keys():
            d[i] = replaceSlash(d[i])
        return d
    else:
        cl_overriding.printERROR(_("wrong dict value: %s" % s))
        cl_overriding.exit(1)


def str2list(s):
    """Convert string to list:

    String format:
    ['value1','val\'ue2']
    """
    value = r'(?:\\\\|\\\'|[^\'])'
    element = r"""
        \s*'           # begin value 
        (%(v)s*)
        '\s*           # end value
        """ % {"v": value}
    reList = re.compile(element, re.X)
    reMatchList = re.compile("""
            ^\[          # begin dict
            ((%(v)s,)*  # many elements with comma at end
            %(v)s)?     # element without comma
            \]$          # end dict
            """ % {'v': element}, re.X)
    if reMatchList.match(s.strip()):
        replaceSlash = MultiReplace({'\\\\': '\\', '\\\'': '\''})
        return [replaceSlash(i) for i in reList.findall(s)]
    else:
        cl_overriding.printERROR(_("wrong list value: %s" % s))
        cl_overriding.exit(1)


def list2str(list):
    """Convert list to string
    
    Return string with escaped \ and '.
    """
    replaceSlash = MultiReplace({'\\': '\\\\', '\'': '\\\''})
    return "[%s]" % ','.join(["'%s'" % replaceSlash(str(i))
                              for i in list])


def dict2str(dict):
    """Convert dictionry to string
    
    Return string with escaped \ and '.
    """
    replaceSlash = MultiReplace({'\\': '\\\\', '\'': '\\\''})
    return '{%s}' % ','.join(["'%s':'%s'" % (str(k), replaceSlash(str(v))) \
                              for (k, v) in dict.items()])


def convertStrListDict(val):
    """Convert data between string, list, dict"""
    # if val is list
    if isinstance(val, types.ListType):
        return list2str(val)
    # if val is dictionary
    elif isinstance(val, types.DictType):
        return dict2str(val)
    # else it is string
    else:
        # detect dictionary
        if re.match("^{.*}$", val):
            return str2dict(val)
        # detect list
        elif re.match("^\[.*\]$", val):
            return str2list(val)
        # else is simple string
        else:
            return val


def formatListOr(lst):
    """Convert list to string like this: [1,2,3] -> 1,2 or 3"""
    lststr = map(lambda x: str(x), lst)
    return (" %s " % _("or")).join(filter(lambda x: x,
                                          [", ".join(map(lambda x: x or "''",
                                                         lststr[:-1])),
                                           "".join(lststr[-1:])]))


def get_term_size(fd=sys.stdout.fileno()):
    """
    Get terminal size
    """
    try:
        import struct
        import fcntl
        import termios
    except ImportError:
        struct, fcntl, termios = None, None, None
        return None, None

    try:
        s = struct.pack("HHHH", 0, 0, 0, 0)
        data = fcntl.ioctl(fd, termios.TIOCGWINSZ, s)
        height, width = struct.unpack('4H', data)[:2]
        return height, width
    except Exception:
        return None, None


def simplify_profiles(profiles, drop_words=("amd64", "x86")):
    """
    Упростить названия профилей (удалить одинаковое начало и конец в строках)
    :param profiles: список названий профилей
    :param drop_words: список слов из профилей, которые можно отбросить
        если профиль один
    :return: список упрощенных названий профилей
    """
    if len(profiles) < 2:
        if len(profiles) == 1:
            return ["/".join(filter(lambda x: x not in drop_words,
                                    profiles[0].split('/')))]
        return profiles
    matrix = [x.split('/') for x in profiles]
    # удаляем одинаковые начальные и конечные поля
    matrix = zip(*reversed(list(dropwhile(lambda x: x.count(x[0]) == len(x),
                                          (reversed(list(dropwhile(
                                              lambda x: x.count(x[0]) == len(x),
                                              izip_longest(*matrix,
                                                           fillvalue="")))))))))
    return ["/".join(filter(None, x)) for x in matrix]
