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

# Copyright 2012-2013 Calculate Ltd. 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 sys, 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
setLocalTranslate('cl_core3',sys.modules[__name__])
from itertools import *

colorPrint = color_print()

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

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

    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 __call__(self, parser, ns, values, option):
        if values is None:
            values = "on"
        else:
            if self.reTrue.match(values):
                values = "on"
            elif self.reFalse.match(values):
                values = "off"
            else:
                msg = _('the value may be on or off')
                parser.error(msg)
        setattr(ns, self.dest, values)

def _print (*args):
    print " ".join(map(lambda x:unicode(x).encode('utf-8'),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:
            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
    ArrayArray = client.factory.create('%sArrayArray' %_type)
    for i in _list:
        Array = client.factory.create('%sArray' %_type)
        for j in i:
            Array[_type].append(j)
        ArrayArray['%sArray' %_type].append(Array)
    return ArrayArray

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 getTerminalSize
class RawAndDefaultsHelpFormatter(argparse.HelpFormatter):
    def __init__(self, prog, max_help_position=24, **kwargs):
        # Use the whole terminal width
        height, width = getTerminalSize()
        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(' ', text.decode('utf-8')).strip()
        return _textwrap.wrap(text.decode('utf-8'), 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)
    """
    # get list (local View) or first element of tuple (WSDL View)
    getlist = (lambda x:x) if cl_core else (lambda x:x[0])
    error_flag = False
    method = args.method
    if cl_core:
        progr = os.path.basename(sys.argv[0])
    else:
        progr = 'cl-console --method '+method


    boolVars = ["f"]
    def fixBoolVariables(args):
        prevLen = 0
        while prevLen != len(args):
            prevLen = len(args)
            args = reduce(lambda x,y: x + [y[:2],"-%s"%y[2:]] \
                if len(y) > 2 and y[:1] == "-" and y[1:2] in boolVars and \
                not y[2:].lower().startswith("on") and \
                not y[2:].lower().startswith("off") \
                else x+[y],args,[])
        return 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 = fixBoolVariables

    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']:
                    data['action'] = BoolAction
                    if opt.shortopt:
                        boolVars.append(opt.shortopt[1])
                elif field.element == 'radio' and field.type == 'bool':
                    data['action'] = BoolAction
                    if opt.shortopt:
                        boolVars.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 field.element in ['check']:
                    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 as e:
                    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]:
        ChoiceValues = field.tablevalue.values
#        column = len(field.tablevalue.head)
    else:
        ChoiceValues = field.tablevalue.values.ChoiceValue
#        column = len(field.tablevalue.head.string)
#    print ChoiceValue, column
    found_flag = False
    if not getattr(param_object, field.name):
        for column in range(len(ChoiceValues)):
            if found_flag:
                break
            if "password" in ChoiceValues[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(ChoiceValues)):
            if found_flag:
                break
            if "password" in ChoiceValues[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)

        result = []
        for _array in param_object[field.name].stringArray:
            temp = []
            for item in _array.string:
                temp.append(item) if item else temp.append('')
            result.append(temp)
        for item in result:
            if str(item[0]) == str(changed_string[0]):
                result.remove(item)

        result.append(changed_string)
        param_object[field.name] = listToArrayArray(client, result)
        return param_object
    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))

        result = []
        for _array in getattr(param_object,field.name):
            temp = []
            for item in _array:
                temp.append(item) if item else temp.append('')
            result.append(temp)
        for item in result:
            if str(item[0]) == str(changed_string[0]):
                result.remove(item)

        result.append(changed_string)
        setattr (param_object, field.name, result)
        return param_object

def check_result_msg(method_result, view, input_error_dict = {},args=None):
    password_errors = {}
    if hasattr (method_result, 'ReturnedMessage'):
        method_result = method_result.ReturnedMessage

    if hasattr (view.groups, 'GroupField'):
        groups = view.groups.GroupField
    else:
        groups = view.groups
    for error in method_result:
        if error.type == 'pwderror':
            password_errors[error.field] = error.message
            continue

        params_text = ''
        if error.type != "commonerror":
            for Group in groups:
                if hasattr (Group.fields, 'Field'):
                    fields = Group.fields.Field
                else:
                    fields = Group.fields
                for field in fields:
                    if field.name == error.field:
                        if not args is None:
                            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'

        sys.stdout.write('\r')
        sys.stdout.flush()
        if error.type != "commonerror":
            colorPrint.printERROR(params_text % error.message)
        else:
            colorPrint.printWARNING(params_text % error.message)

    if len(password_errors) < len(method_result):
        return None
    else:
        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

def get_param_pwd(check_res, view, param_object, client = None,
                  stdin_passwd=False):
    if hasattr (view.groups, 'GroupField'):
        groups = view.groups.GroupField
    else:
        groups = view.groups
    for pwd_field in check_res:
        _print (check_res[pwd_field])
        for Group in groups:
            if hasattr (Group.fields, 'Field'):
                fields = Group.fields.Field
            else:
                fields = Group.fields
            for field in fields:
                if field.name == pwd_field:
                    if field.element == 'table':
                        value = get_password(getfromstdin=stdin_passwd)
                        if value == None:
                            raise KeyboardInterrupt
                        set_table_pwd(client, param_object, field, value)
                    else:
                        value = get_password(getfromstdin=stdin_passwd)
                        if value == None:
                            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
    if type(view.groups) == list:
        # for local call method
        groups = view.groups
    else:
        groups = view.groups[0]
    for Group in groups:
        if not Group.fields:
            continue
        if type(Group.fields) == list:
            # for local call method
            fields = Group.fields
        else:
            fields = Group.fields[0]
        for field in fields:
            if field.uncompatible:
                continue
            if field.element in ['check', 'check_tristate'] and \
                                                        field.type == 'bool':
                set_flag = False
                #if hasattr (field.opt, 'metavalue'):
                #    if field.opt.metavalue and _getattr(args, field.name):
                #        if type(_getattr(args, field.name)) == bool:
                #            if _getattr(args, field.name) == True:
                #                param_object = set_obj_item(client,
                #                                    param_object, field.name,
                #                                    field.opt.metavalue)
                #        elif _getattr(args, field.name).lower() in \
                #                                                ['on','yes']:
                #            param_object = set_obj_item(client, param_object,
                #                             field.name, field.opt.metavalue)
                #            set_flag = True
                if not set_flag:
                    value = _getattr(args, field.name)
                    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)

            if field.element == 'radio' and field.type == 'bool':
#                param_object[field.name] = _getattr(args, field.name)
                param_object = set_obj_item(client, param_object, field.name,
                                            _getattr(args, field.name))

            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 'passwd' in field.element and _getattr(args, field.name) \
                or field.type and  "need" in field.type:
                if wait_thread:
                    wait_thread.pause()
                password=get_password("%s: "%field.label,_('Repeat password: '),
                                      getfromstdin=stdin_passwd,
                                      needrepeat=not "one" 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:
        outrow = []
        yield [row[i] or '' for i in sorted(row.keys())]

def collect_table(field, val_list, client, wait_thread = None,
                  stdin_passwd = False):
    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:
        val_table = map(lambda x: x.split(':'), val_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)

    bGetPassword = any('password' in x.typefield for x in choiceValue)
    obj_body = []
    for line in val_table:
        receivedPassword = None
        if bGetPassword:
            if len(line) > 1 and line[1].lower() != '':
                if wait_thread:
                    wait_thread.stop()
                    sys.stdout.write('\r')
                    sys.stdout.flush()
                receivedPassword=get_password(_('Password for %s: ')%line[0],\
                                        _('Repeat password for %s: ') %line[0],
                               getfromstdin=stdin_passwd)
                if password == None:
                    raise KeyboardInterrupt
        temp_row = []
        offset = 0
        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 not receivedPassword is None:
                    temp_row.append(receivedPassword)
                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):
    column = len(field.tablevalue.head) if type(field.tablevalue.head) == list\
                                        else len(field.tablevalue.head.string)
    result_table = []
    if type(field.tablevalue.values) in [list, tuple]:
        ChoiceValue = field.tablevalue.values
    else:
        ChoiceValue = field.tablevalue.values.ChoiceValue
    for i in range(len(body)):
        temp_row = []
        for j in range(column):
            # not adding if readonly
            if j > (len(ChoiceValue) + 1):
                continue
            typefield = ChoiceValue[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))):
        paramName = ', '.join(filter(None,
                [field.opt.shortopt, field.opt.longopt]))
    else:
        paramName = field.opt.metavalue
    if getattr(args,field.name) is None and \
        not "need" in field.type:
        params_text += "%s. " + _("Use the parameter") + " "
        params_text += paramName+'. '
    else:
        if "need" in field.type:
            params_text += \
                _('Error in field \'%s\'. ')%field.label + " %s"
        else:
            params_text += _('Error in parameter ')
            params_text += paramName +'. %s'
    return params_text
