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

# Copyright 2012-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.

from __future__ import print_function
from __future__ import absolute_import
import sys
import os
import argparse
import re
from calculate.lib.utils.common import getpass
from calculate.lib.cl_print import color_print
from calculate.lib.cl_lang import setLocalTranslate
from calculate.core.server.api_types import TableAdapter
from functools import reduce

_ = lambda x: x
setLocalTranslate('cl_core3', sys.modules[__name__])
from itertools import *
from .api_types import ViewInfoAdapter, ArrayReturnedMessage, FieldAdapter
from calculate.lib.utils.colortext import get_terminal_print, TextState

colorPrint = color_print()


class GotErrorField(Exception):
    """
    Исключение о получение поля error среди
    элементов view
    """
    pass


class BoolAction(argparse.Action):
    reTrue = re.compile("^(?:on|yes)$", re.I)
    reFalse = re.compile("^(?:off|no)$", re.I)
    available_values = ("on", "off","yes", "no")

    def __init__(self, option_strings, dest, nargs="?",
                 const=None, default=None, type=None, choices=None,
                 required=False, help=None, metavar=None):
        super(BoolAction, self).__init__(
            option_strings=option_strings, dest=dest,
            nargs=nargs, const=const, default=default,
            type=type, choices=choices, required=required,
            help=help, metavar=metavar)

    def get_values(self, value):
        value = value.lower()
        for v in self.available_values:
            if value == v:
                return v
        else:
            return ""

    def __call__(self, parser, ns, values, option_string=None):
        if values is None:
            values = "on"
        else:
            if self.reTrue.match(values):
                values = "on"
            if self.reFalse.match(values):
                values = "off"
            values = self.get_values(values)
            if not values:
                msg = _('the value may be {0}').format(
                    formatListOr(self.available_values))
                parser.error(msg)
        setattr(ns, self.dest, values)

class BoolAutoAction(BoolAction):
    available_values = ("on", "off", "yes", "no", "auto")

def _print(*args):
    print(" ".join(map(lambda x: _u8(x), args)))


def get_password(text1=None, text2=None, getfromstdin=False,
                 needrepeat=True):
    if getfromstdin:
        try:
            passwd = ''
            while not passwd:
                passwd = sys.stdin.readline()
                if not passwd:
                    return None
                passwd = passwd.rstrip('\n')
            return passwd
        except BaseException:
            return None
    if not text1:
        text1 = _('Password: ')
    if not text2:
        text2 = _('Repeat: ')
    try:
        pass1 = 'password'
        pass2 = 'repeat'
        while pass1 != pass2:
            pass1 = getpass.getpass(text1)
            if not needrepeat:
                return pass1
            pass2 = getpass.getpass(text2)
            if pass1 != pass2:
                print(_('Passwords do not match'))
    except KeyboardInterrupt:
        return None
    passwd = pass1 if (pass1 and pass1 == pass2) else ''
    return passwd


def listToArray(client, _list, _type='string'):
    if not client:
        return _list
    array = client.factory.create('%sArray' % _type)
    for i in _list:
        array['%s' % _type].append(i)
    return array


def listToArrayArray(client, _list, _type='string'):
    if not client:
        return _list
    array_array = client.factory.create('%sArrayArray' % _type)
    for i in _list:
        array = client.factory.create('%sArray' % _type)
        for j in i:
            array[_type].append(j)
        array_array['%sArray' % _type].append(array)
    return array_array


def _getattr(obj, attr):
    return getattr(obj, attr) if hasattr(obj, attr) else None


import argparse
import textwrap as _textwrap
from calculate.lib.utils.text import get_term_size, _u, _u8, _uu8, formatListOr


class RawAndDefaultsHelpFormatter(argparse.HelpFormatter):
    def __init__(self, prog, max_help_position=24, **kwargs):
        # Use the whole terminal width
        height, width = get_term_size()
        argparse.HelpFormatter.__init__(self, prog, width=width,
                                        max_help_position=max_help_position,
                                        **kwargs)

    def _split_lines(self, text, width):
        text = self._whitespace_matcher.sub(' ', _u(text)).strip()
        return _uu8(*_textwrap.wrap(text, width))


def get_method_argparser(view, args, cl_core=False):
    """
    Get argparser by ViewInfo get from WSDL server (or stub)

    cl_core  - argparser for cl_core (local call)
    """
    error_flag = False
    method = args.method
    if cl_core:
        progr = os.path.basename(sys.argv[0])
    else:
        progr = 'cl-console --method ' + method

    bool_vars = ["f"]

    def fix_bool_variables(args):
        prevlen = 0
        local_args = args
        while prevlen != len(local_args):
            prevlen = len(local_args)
            local_args = reduce(lambda x, y: (
                x + [y[:2], "-%s" % y[2:]]
                if (len(y) > 2 and y[:1] == "-" and y[1:2] in bool_vars and
                    not y[2:].lower().startswith("on") and
                    not y[2:].lower().startswith("off"))
                else x + [y]), local_args, [])
        return local_args

    def get_list(data):
        if data is not None:
            for entry in data if cl_core else data[0]:
                yield entry

    parser = argparse.ArgumentParser(
        prog=progr, add_help=False, formatter_class=RawAndDefaultsHelpFormatter)
    parser.fixBoolVariables = fix_bool_variables

    for Group in ifilter(lambda x: x.fields, get_list(view.groups)):
        group = parser.add_argument_group(Group.name)
        for field in get_list(Group.fields):
            if field.element == 'error':
                error_flag = True
                colorPrint.printERROR(field.label)
            elif field.opt:
                opt = field.opt
                data = {'dest': field.name, 'help': opt.help}
                if "choice" in field.type and hasattr(field.opt, "syntax") and \
                        field.opt.syntax and "{" in field.opt.syntax:
                    lgroup = group.add_mutually_exclusive_group()
                    help = dict(map(lambda x: (x[0].strip("'"), x[2]),
                                    map(lambda x: x.partition(' - '),
                                        field.help.split(',\n'))))
                    choice = field.choice
                    if not type(choice) in (list, tuple):
                        choice = choice.string
                    for val in filter(None, choice):
                        data['action'] = 'store_const'
                        # data['nargs'] = '?'
                        data['const'] = val
                        data['metavar'] = ""
                        if hasattr(field, "value") and field.value == val:
                            data['help'] = (help.get(val, field.help) +
                                            " " + _("(by default)"))
                        else:
                            data['help'] = help.get(val, field.help)
                        lgroup.add_argument(field.opt.syntax.format(choice=val),
                                            **data)
                    continue
                if "password" in field.type:
                    data['action'] = "store_true"
                    if "need" in field.type:
                        data['help'] = argparse.SUPPRESS
                else:
                    data['type'] = str
                if field.element in ['check', 'check_tristate']:
                    if field.element == "check_tristate":
                        data['action'] = BoolAutoAction
                    else:
                        data['action'] = BoolAction
                    if field.value == 'on':
                        data['help'] = data['help'] + " " + _(
                            "(enabled by default)")
                    if opt.shortopt:
                        bool_vars.append(opt.shortopt[1])
                elif field.element == 'radio' and field.type == 'bool':
                    data['action'] = BoolAction
                    if field.value == 'on':
                        data['help'] = data['help'] + " " + _(
                            "(enabled by default)")
                    if opt.shortopt:
                        bool_vars.append(opt.shortopt[1])
                if field.element == 'table' and field.type != 'steps':
                    data['action'] = 'append'

                if data.get('action') != "store_true":
                    data['metavar'] = opt.metavalue if opt.metavalue \
                        else field.name.upper()
                    # if ':' in data['metavar']:
                    #    data['metavar'] = field.name.upper()
                    if "choice" in field.type:
                        if "list" in field.type:
                            data['help'] = "%s (%s)" % (
                                data['help'],
                                _("'list' for displaying possible values, "
                                  "'none' is never one"))
                        else:
                            data['help'] = "%s (%s)" % (
                                data['help'],
                                _("'list' for displaying possible values"))
                if "boolauto" in field.type:
                    data['metavar'] = "ON/OFF/AUTO"
                elif "bool3" in field.type:
                    data['metavar'] = "ON/OFF/AUTO"
                elif "bool" in field.type:
                    data['metavar'] = "ON/OFF"
                try:
                    opts = filter(None, [opt.shortopt, opt.longopt])
                    if any("-" not in x for x in opts):
                        data.pop('dest')
                        data['nargs'] = '?'
                    group.add_argument(*opts, **data)
                except argparse.ArgumentError:
                    continue
    group = parser.add_argument_group(_("Common arguments"))
    group.add_argument(
        '-f', '--force', action='store_true', default=False,
        dest='no_questions', help=_('silent during the process'))
    if error_flag:
        raise GotErrorField
    return parser


def set_obj_item(client, param_object, field_name, value):
    """
    Set value for Info object. By client detect (local or WSDL call)
    """
    if client:
        param_object.__setitem__(field_name, value)
    else:
        setattr(param_object, field_name, value)
    return param_object


def set_table_pwd(client, param_object, field, value):
    if type(field.tablevalue.values) in [list, tuple]:
        choice_values = field.tablevalue.values
    # column = len(field.tablevalue.head)
    else:
        choice_values = field.tablevalue.values.ChoiceValue
    # column = len(field.tablevalue.head.string)
    #    print ChoiceValue, column
    found_flag = False
    changed_string = None
    if not getattr(param_object, field.name):
        for column in range(len(choice_values)):
            if found_flag:
                break
            if "password" in choice_values[column].typefield:
                if hasattr(field.tablevalue.body, 'stringArray'):
                    body = field.tablevalue.body.stringArray
                else:
                    body = field.tablevalue.body
                for _row in body:
                    if hasattr(_row, 'string'):
                        row = _row.string
                    else:
                        if not _row[0]:
                            row = _row[1:]
                        else:
                            row = _row
                    if not row[column]:
                        row[column] = value
                        temp = []
                        for item in row:
                            temp.append(item) if item else temp.append('')
                        changed_string = temp
                        found_flag = True
                        break
    else:
        user_table = getattr(param_object, field.name)
        if hasattr(user_table, 'stringArray'):
            user_table = user_table.stringArray
        for column in range(len(choice_values)):
            if found_flag:
                break
            if "password" in choice_values[column].typefield:
                for _row in user_table:
                    if hasattr(_row, 'string'):
                        row = _row.string
                    else:
                        if not _row[0]:
                            row = _row[1:]
                        else:
                            row = _row
                    if not row[column]:
                        row[column] = value
                        temp = []
                        for item in row:
                            temp.append(item) if item else temp.append('')
                        changed_string = temp
                        found_flag = True
                        break

    # код выполняется через сетевую консоль
    if client:
        if not getattr(param_object, field.name):
            setattr(param_object, field.name, field.tablevalue.body)
        object_array = TableAdapter.get_matrix(param_object[field.name])
    # код выполняется локально
    else:
        if not getattr(param_object, field.name):
            setattr(param_object, field.name,
                    map(lambda x: x[1:] if not x[0] else x,
                        field.tablevalue.body))
        object_array = getattr(param_object, field.name)

    result = []
    for _array in object_array:
        temp = []
        for item in _array:
            temp.append(item) if item else temp.append('')
        result.append(temp)
    if changed_string:
        for item in result:
            if str(item[0]) == str(changed_string[0]):
                result.remove(item)

    result.append(changed_string)

    if client:
        param_object[field.name] = listToArrayArray(client, result)
    else:
        setattr(param_object, field.name, result)
    return param_object


def display_error(error, args, groups):
    params_text = ''
    sys.stdout.write('\r')
    sys.stdout.flush()
    list_answer = False
    varname, comments, values, value, list_value = None, None, None, None, []
    if error.type != "commonerror":
        for group in groups:
            for field in group.fields:
                if field.name == error.field:
                    if args is not None:
                        if (getattr(args, field.name) == "list" and
                                    "choice" in field.type):
                            if error.field_obj:
                                field_obj = FieldAdapter.from_detect(error.field_obj)
                                list_answer = True
                                varname = (field_obj.label or
                                           field_obj.name)
                                comments = field_obj.comments
                                values = field_obj.choice
                                value = field_obj.value
                                list_value = error.field_obj.listvalue or []
                        params_text += getErrorOnParam(args, field)
                    else:
                        if field.opt.shortopt or field.opt.longopt:
                            params_text += _('Wrong option ')
                            params_text += ' ' + ', '.join(
                                filter(None, [field.opt.shortopt,
                                              field.opt.longopt])) + '. %s'
    if list_answer:
        __print = get_terminal_print(colorPrint.defaultPrint)
        __print.foreground(TextState.Colors.WHITE)(
            _("%s values:") % varname)
        __print("\n")
        if comments and values:
            maxlen = len(max(values, key=len))
            for v, c in zip(values, comments):
                if not v and not c:
                    continue
                __print("  ")
                __print.bold("[" + v + "]")
                __print(" " * (maxlen - len(v)))
                __print("  ")
                __print(c)
                if value == v or v in list_value:
                    __print(" ")
                    __print.bold.foreground(TextState.Colors.WHITE)("*")
                __print("\n")
        if not comments:
            for v in filter(None, values):
                __print("  ")
                __print.bold("[" + v + "]")
                if value == v:
                    __print(" ")
                    __print.bold.foreground(TextState.Colors.WHITE)("*")
                __print("\n")
    elif error.type != "commonerror":
        colorPrint.printERROR(params_text % error.message)
    else:
        colorPrint.printWARNING(error.message)


def check_result_msg(method_result, view, input_error_dict=None, args=None):
    if not input_error_dict:
        input_error_dict = {}
    password_errors = {}
    method_result = ArrayReturnedMessage.from_detect(method_result)
    view = ViewInfoAdapter.from_detect(view)

    for error in method_result:
        if error.type == 'pwderror':
            password_errors[error.field] = error.message
            continue

        display_error(error, args, view.groups)

    # если все ошибки связаны с паролем
    if len(password_errors) == len(method_result):
        if (not dict([x for x in input_error_dict.items()
                      if x not in password_errors.items()]) and
                not dict([x for x in password_errors.items()
                          if x not in input_error_dict.items()])):
            return None
        return password_errors
    else:
        return None


def get_param_pwd(check_res, view, param_object, client=None,
                  stdin_passwd=False):
    view = ViewInfoAdapter.from_detect(view)
    for pwd_field in check_res:
        if not stdin_passwd:
            _print(check_res[pwd_field])
        for group in view.groups:
            for field in group.fields:
                if field.name == pwd_field:
                    if field.element == 'table':
                        value = get_password(getfromstdin=stdin_passwd)
                        if value is None:
                            _print(check_res[pwd_field])
                            raise KeyboardInterrupt
                        set_table_pwd(client, param_object, field, value)
                    else:
                        value = get_password(getfromstdin=stdin_passwd)
                        if value is None:
                            _print(check_res[pwd_field])
                            raise KeyboardInterrupt
                        setattr(param_object, pwd_field, value)
    return param_object


def collect_object(client, param_object, view, args, wait_thread=None,
                   stdin_passwd=False):
    """
    Collect Info object by args
    """
    steps = None
    view = ViewInfoAdapter.from_detect(view)
    for group in view.groups:
        for field in group.fields:
            #if field.uncompatible:
            #    continue
            if (field.element in ['check', 'check_tristate'] and
                        field.type in ('bool', 'boolauto', 'bool3')) or (
                            field.element == 'radio' and field.type == 'bool'):
                value = _getattr(args, field.name)
                if field.type == 'bool3':
                    pass
                else:
                    if value:
                        if _getattr(args, field.name).lower() in ['on', 'yes']:
                            value = True
                        elif _getattr(args,
                                      field.name).lower() in ['off', 'no']:
                            value = False
                        else:
                            value = None
                    else:
                        value = None
                param_object = set_obj_item(client, param_object,
                                            field.name, value)
            elif (field.element == 'input' and
                          field.name in ['cl_page_offset', 'cl_page_count']):
                val = _getattr(args, field.name)
                if not val:
                    val = 0
                if wait_thread:
                    wait_thread.stop()
                # param_object[field.name] = val
                param_object = set_obj_item(client, param_object, field.name,
                                            val)

            elif field.element in ['input', 'openfile',
                                   'file', 'radio', 'combo', 'comboEdit']:
                param_object = set_obj_item(client, param_object, field.name,
                                            _getattr(args, field.name))
            elif 'passw' in field.element and _getattr(args, field.name) \
                    or field.type and "need" in field.type:
                if wait_thread:
                    wait_thread.pause()
                label = field.label or _("Password")
                password = get_password(label + _(": "),
                                        _('Repeat password: '),
                                        getfromstdin=stdin_passwd,
                                        needrepeat="one" not in field.type)
                if password is None:
                    raise KeyboardInterrupt
                param_object = set_obj_item(client, param_object, field.name,
                                            password)
                if wait_thread:
                    wait_thread.resume()
            elif field.element in ['multichoice', 'multichoice_add',
                                   'selecttable', 'selecttable_add']:
                val = _getattr(args, field.name)
                if val in ['off', 'none']:
                    if client:
                        value = listToArray(client, [None])
                    else:
                        value = []
                else:
                    value = listToArray(client,
                                        val.split(',')) if val else None
                param_object = set_obj_item(client, param_object, field.name,
                                            value)

            elif field.element == 'table' and field.type != 'steps':
                val = _getattr(args, field.name)
                value = collect_table(field, val, client, wait_thread,
                                      stdin_passwd)
                param_object = set_obj_item(client, param_object, field.name,
                                            value)

            elif field.element == 'table' and field.type == 'steps':
                steps = field
    if hasattr(param_object, 'CheckAll'):
        param_object = set_obj_item(client, param_object, 'CheckAll', True)

    return param_object, steps


def convertArgDictToList(argDict):
    """
    Convert list of dict like {'arg_1':None,'arg_2':'value2','arg_5':'value5'}
    to [..,['','value2','','','value5']..] (iterator)
    """
    for row in argDict:
        yield [row[i] or '' for i in sorted(row.keys())]


def collect_table(field, val_list, client, wait_thread=None,
                  stdin_passwd=False):
    def split_param(l, delimeter=","):
        for x in l:
            for y in x.split(delimeter):
                yield y

    if not val_list:
        return None
    # if specified syntax for table row
    if hasattr(field.opt, "syntax") and field.opt.syntax:
        reArgs = re.compile(field.opt.syntax)
        # check for correct sentense
        for wrong in ifilterfalse(reArgs.search, val_list):
            # raise Error on wrong
            raise ValueError(_("Wrong %s value syntax") %
                             (field.opt.shortopt or field.opt.longopt)
                             + " " + wrong)
        # create groupdict from all vals
        argDict = map(lambda x: x.groupdict(), map(reArgs.search, val_list))
        # convert groupdicts to val_table
        val_table = list(convertArgDictToList(argDict))
    # standard syntax
    else:
        reduced_list = list(val_list)
        val_table = map(lambda x: x.split(':'), reduced_list)

    if type(field.tablevalue.values) in [list, tuple]:
        choiceValue = field.tablevalue.values
    else:
        choiceValue = field.tablevalue.values.ChoiceValue
    choiceValue = filter(lambda x: x.typefield != 'readonly', choiceValue)
    lenChoiceValue = len(choiceValue)
    for wrong in ifilter(lambda x: len(x) > lenChoiceValue,
                         val_table):
        if type(wrong) in (list, tuple):
            wrong = ":".join(wrong)
        raise ValueError(_("Wrong %s value syntax") %
                         (field.opt.shortopt or field.opt.longopt)
                         + " " + wrong)

    # obj_body = []

    #    if type(field.tablevalue.body) == list:
    #        temp_body = field.tablevalue.body
    #    else:
    #        temp_body = field.tablevalue.body.stringArray
    #    for obj_row in temp_body:
    #        if type(obj_row) != list:
    #            obj_row = obj_row.string
    #            for item in range(len(obj_row)):
    #                if not obj_row[item]:
    #                    obj_row[item] = ''
    #        else:
    #            if obj_row:
    #                if obj_row[0] == '':
    #                    obj_row.pop(0)
    #        obj_body.append(obj_row)
    #
    #    obj_body = collect_obj_body(obj_body, field)

    is_password_get = any('password' in x.typefield for x in choiceValue)
    obj_body = []
    for line in val_table:
        received_password = None
        if is_password_get:
            if len(line) > 0 and line[0].lower() != '':
                if wait_thread:
                    wait_thread.stop()
                    sys.stdout.write('\r')
                    sys.stdout.flush()
                received_password = get_password(
                    _('Password for %s: ') % line[0],
                    _('Repeat password for %s: ') % line[0],
                    getfromstdin=stdin_passwd)
                if received_password is None:
                    raise KeyboardInterrupt
        temp_row = []
        for val, choice_value in izip_longest(
                line, filter(lambda x: x.typefield != 'readonly', choiceValue),
                fillvalue=''):
            typefield = choice_value.typefield
            # if readonly, except first column
            if typefield in ['check', 'check_tristate']:
                if BoolAction.reTrue.match(val):
                    temp_row.append('on')
                elif BoolAction.reFalse.match(val):
                    temp_row.append('off')
                else:
                    temp_row.append(val)
            elif typefield in ['input', 'combo', 'comboEdit', 'openfile',
                               'file', 'radio', 'text', 'multichoice',
                               'multichoice_add']:
                temp_row.append(val)
            elif typefield == 'password':
                if received_password is not None:
                    temp_row.append(received_password)
                else:
                    temp_row.append(val)
        obj_body.append(temp_row)

    if not obj_body:
        obj_body = [[None]]
    return listToArrayArray(client, obj_body)


def collect_obj_body(body, field):
    field = FieldAdapter.from_detect(field)
    column = len(field.tablevalue.head)
    result_table = []
    choice_value = field.tablevalue.values
    for i in range(len(body)):
        temp_row = []
        for j in range(column):
            # not adding if readonly
            if j > (len(choice_value) + 1):
                continue
            typefield = choice_value[j].typefield
            if typefield == 'readonly' and j > 0:
                continue
            elif typefield in ['check', 'check_tristate']:
                if len(body[i]) < j + 1:
                    temp_row.append('')
                    continue
                if not body[i][j]:
                    temp_row.append('')
                elif body[i][j].lower() in ['on', 'yes']:
                    temp_row.append('on')
                elif body[i][j].lower() in ['off', 'no']:
                    temp_row.append('off')
                else:
                    temp_row.append(body[i][j])

            elif typefield in ['input', 'combo', 'comboEdit', 'openfile',
                               'file', 'password', 'radio', 'text']:
                if len(body[i]) < j + 1:
                    temp_row.append('')
                elif not body[i][j]:
                    temp_row.append('')
                else:
                    temp_row.append(body[i][j])
            elif typefield in ['multichoice', 'multichoice_add']:
                if len(body[i]) < j + 1:
                    temp_row.append('')
                elif not body[i][j]:
                    temp_row.append('')
                else:
                    temp_row.append(body[i][j])
        result_table.append(temp_row)
    return result_table


def getErrorOnParam(args, field):
    """
    Get errors for param
    """
    params_text = ""
    if any("-" in x
           for x in filter(None, (field.opt.longopt, field.opt.shortopt))):
        param_name = ', '.join(filter(None,
                                      [field.opt.shortopt, field.opt.longopt]))
    else:
        param_name = field.opt.metavalue
    if getattr(args, field.name) is None and "need" not in field.type:
        params_text += "%s. " + _("Use the parameter") + " "
        params_text += param_name + '. '
    else:
        if "need" in field.type:
            params_text += \
                _('Error in field \'%s\'. ') % field.label + " %s"
        elif getattr(args, field.name) == "list" and "choice" in field.type:
            params_text += "%s"
        else:
            params_text += _('Error in parameter ')
            params_text += param_name + '. %s'
    return params_text
