#-*- 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.

from soaplib.serializers.primitive import String, Integer, Boolean
from soaplib.serializers.clazz import Array, ClassSerializer
from soaplib.service import rpc
import sys
import pickle, os
from calculate.lib.datavars import SourceReadonlyVariable
from collections import Mapping

from calculate.lib.cl_lang import setLocalTranslate
setLocalTranslate('cl_core3',sys.modules[__name__])

################# API ########################
class CommonInfo(ClassSerializer):
    """
    Common info for all Info classes

    cl_templates_locate - templates location
    cl_dipatch_conf - update config method
    cl_verbose_set - display appling templates
    """
    cl_templates_locate = Array(String)
    cl_dispatch_conf = String
    cl_verbose_set = Boolean

    Default = Array(String)
    CheckAll = Boolean

class DataVarsSerializer(ClassSerializer):
    """Serializer for datavars types"""

    def elementByType(self,typeobj):
        """Get element by variable type, given for table or not"""
        elementMap = {'table':'table',
                  "string":"input",
                  "bool":"check",
                  "boolauto":"check_tristate",
                  "choice":"combo",
                  "choiceedit":"comboEdit",
                  "choiceedit-list":"multichoice_add",
                  "choice-list":"multichoice",
                  "bool-list":"multichoice",
                  "list":"multichoice_add",
                  "password":"password",
                  "onepassword":"password",
                  "need-onepassword":"password",
                  "need-password":"password"}
        return elementMap.get(typeobj,'input')

    def arrayString(self,value):
        if type(value) in (list,tuple):
            return [""] + \
                map(lambda x:",".join(x) if type(x) in (list,tuple) else str(x),
                value)
        else:
            return value

    def arrayArrayString(self,value):
        if type(value) in (list,tuple):
            return map(self.arrayString,value)
        else:
            return value

    def getChoice(self,varObj):
        if "choice" in varObj.type or "file" in varObj.type or \
            varObj.element == "radio":
            choice,comments = varObj.parent.ChoiceAndComments(varObj.name)
            return self.arrayString(choice), self.arrayString(comments)
        else:
            return (None,None)

class ChoiceValue(DataVarsSerializer):
    typefield = String
    values = Array(String)
    comments = Array(String)
    onChanged = Array(String)

    def __init__(self,dv=None,varObj=None,readOnly=False,**kwargs):
        if dv:
            super(ChoiceValue,self).__init__()
            if not readOnly:
                self.values, self.comments = self.getChoice(varObj)
            elif isinstance(varObj,SourceReadonlyVariable):
                self.values, self.comments = \
                    zip(*varObj.getMapHumanReadable().items())
                self.values = self.arrayString(self.values)
                self.comments = self.arrayString(self.comments)
            if varObj.mode == 'w':
                self.typefield = varObj.element or \
                        self.elementByType(varObj.type)
            else:
                if readOnly:
                    self.typefield = "readonly"
                else:
                    self.typefield = "text"
        else:
            super(ChoiceValue,self).__init__(**kwargs)

    def elementByType(self,typeobj):
        """Get element by variable type, given for table or not"""
        if "-list" in typeobj:
            typeobj = typeobj.replace("-list","",1)
        elif "list" in typeobj:
            typeobj = typeobj.replace("list","",1)
        return DataVarsSerializer.elementByType(self,
               typeobj)

class Table(DataVarsSerializer):
    head = Array(String)
    fields = Array(String) # name fields in params object
    body = Array(Array(String))
    values = Array(ChoiceValue)
    onClick = String
    addAction = String

    def __init__(self,dv=None,briefmode=None,
                 varObj=None,head=None,body=None,values=None,
                 fields=None,onClick=None,addAction=None):
        super(Table,self).__init__()
        if dv:
            self.head = []
            self.body = []
            self.values = []
            # get information about all table columns
            self.writable = True
            for i,col in enumerate(varObj.source):
                # get columns variable obj
                varSource = dv.getInfo(col)
                # invalidate columns vars for uncompatible table
                if varSource.uncompatible():
                    dv.Invalidate(col,onlySet=True)
                # header label
                self.head.append(varSource.label or varSource.name)
                # if column writable then add ChoiceValue info
                if varSource.mode == 'w' or i == 0:
                    self.values.append(ChoiceValue(dv,varSource))
                    if varSource.mode == 'r' and i == 0:
                        self.writable = False
                else:
                    self.values.append(ChoiceValue(dv,varSource,readOnly=True))
            # get table body
            # empty value at start add for fix data transfer
            self.body = self.arrayArrayString(dv.Get(varObj.name,
                            humanreadable=True if briefmode else None))
        else:
            self.head = head
            self.fields = fields
            self.body = body
            self.values = values
            self.onClick = onClick
            self.addAction = addAction


class Option(DataVarsSerializer):
    shortopt = String
    longopt = String
    metavalue = String
    syntax = String
    help = String

    def __init__(self,optlist,metaval,helpval,syntax=""):
        super(Option,self).__init__()
        self.help = helpval
        self.metavalue = metaval
        self.syntax = syntax
        for val in optlist:
            if val.startswith('--'):
                self.longopt=val
            else:
                self.shortopt=val

class Field(DataVarsSerializer):
    name = String               # varname from Datavars
    label = String              # label for GUI
    type = String               # data type of variable
    opt = Option                # opt for cmdline
    help = String               # help for cmdline (GUI?)
    element = String            # type element
    choice = Array(String)      # value (combobox,comboedit)
    listvalue = Array(String)   # current listvalue
    default = Boolean           # default value or False
    value = String              # current value
    tablevalue = Table          # current table value
    uncompatible = String       # message for uncompatibility variable
    comments = Array(String)    # comments for choice

    def __init__(self,dv=None,varObj=None,expert=False,briefmode=False,
                 inbrief=False,groupVars=(),onlyhelp=False,**kwargs):
        """
        dv - datavars, varObj - current variable, expert - expert variable flag,
        briefmode - view request for brief, inbrief - variable palced in brief,
        """
        if dv:
            super(Field,self).__init__()
            self.name = varObj.name
            self.type = varObj.type
            if varObj.opt:
                self.opt = Option(varObj.opt,varObj.metavalue,varObj.help,
                                  varObj.syntax)
            self.help = varObj.help
            self.element = varObj.element or \
                           self.elementByType(self.type)
            self.label = varObj.label or varObj.name
            if not onlyhelp or varObj.syntax or self.type == "table":
                if inbrief:
                    self.uncompatible = ""
                else:
                    self.uncompatible = dv.Uncompatible(varObj.name)
                if self.uncompatible:
                    for var in varObj.reqUncompat:
                        if not briefmode and var in groupVars:
                            if not dv.Uncompatible(var.name):
                                self.uncompatible = ""
                                break
                    else:
                        dv.Invalidate(varObj.name,onlySet=True)
                if expert:
                    self.default = not varObj.wasSet
                self.choice, self.comments = self.getChoice(varObj)
                if self.type == "table":
                    self.tablevalue = Table(dv=dv,briefmode=briefmode,
                                            varObj=varObj)
                    if self.tablevalue.writable:
                        self.type = "writable"
                else:
                    value = dv.Get(varObj.name)
                    if type(value) == list:
                        self.listvalue = self.arrayString(value)
                    else:
                        if briefmode:# and not "choice" in self.type:
                            self.value = dv.Get(varObj.name,humanreadable=True)
                        else:
                            self.value = value
            #if self.value:
            #    self.default = self.value
        else:
            super(Field,self).__init__(**kwargs)

class GroupField(DataVarsSerializer):
    name = String
    fields = Array(Field)
    prevlabel = String
    nextlabel = String
    prevlabel = String
    last = Boolean

    def __init__(self,name="",fields=[],prevlabel="",
                 nextlabel="",last=False,dv=None,info=None,
                 expert=False,brief=False,onlyhelp=False):
        super(GroupField,self).__init__()
        self.last = last
        if dv:
            self.name = info['name']
            if not onlyhelp:
                self.nextlabel = info['next_label']
                self.prevlabel = _("Previous")
            else:
                self.nextlabel = None
                self.prevlabel = None
            self.fields = []

            # get all variables with deps which using in this group
            groupDepVars = []
            from itertools import chain
            for varname in chain(info['normal'],info['expert']):
                for var in dv.getRequired(varname):
                    if not var in groupDepVars:
                        groupDepVars.append(var)

            if brief:
                uniqBrief = list(info['brief']) + [x for x in info['brief_force']
                                             if x not in info['brief']]
                for varname in uniqBrief:
                    varObj = dv.getInfo(varname)
                    inbrief = varname in info['brief_force']
                    self.fields.append(Field(dv=dv,inbrief=inbrief,
                                             briefmode = brief,
                                             varObj=varObj,
                                             groupVars=groupDepVars,
                                             onlyhelp=onlyhelp))
            else:
                uniqBrief = []
            for varname in (x for x in info['normal'] if x not in uniqBrief):
                inbrief = brief and varname in info['brief_force']
                if brief and 'hide' in info and  varname in info['hide']:
                    continue
                varObj = dv.getInfo(varname)
                self.fields.append(Field(dv=dv,inbrief=inbrief,
                                         briefmode = brief,
                                         varObj=varObj,
                                         groupVars=groupDepVars,
                                         onlyhelp=onlyhelp))
            if info['expert']:
                for varname in (x for x in info['expert'] if x not in uniqBrief):
                    if expert == True or \
                        expert == None and dv.getInfo(varname).wasSet:
                        self.fields.append(Field(name = "expert",
                                 element = "expert",
                                 label=info['expert_label'],
                                 value="open"))
                        for varname in info['expert']:
                            inbrief = brief and varname in info['brief_force']
                            if brief and 'hide' in info and \
                                varname in info['hide']:
                                continue
                            varObj = dv.getInfo(varname)
                            self.fields.append(Field(dv=dv,expert=True,
                                               inbrief=inbrief,
                                               briefmode = brief,
                                               varObj=varObj,
                                               groupVars=groupDepVars,
                                               onlyhelp=onlyhelp))
                        break
                else:
                    if expert == False:
                        for varname in info['expert']:
                            dv.Invalidate(varname,True)
                    if not onlyhelp:
                        self.fields.append(Field(name = "expert",
                                     element = "expert",
                                     label=info['expert_label'],
                                     value="close",
                                     onlyhelp=onlyhelp))
        else:
            self.name = name
            self.fields = fields
            self.nextlabel = nextlabel

class ViewInfo(DataVarsSerializer):
    groups = Array(GroupField)
    has_brief = Boolean

    def __init__(self,datavars=None,step=None,expert=None,allsteps=False,
                 brief=None,brief_label=None,has_brief=False,groups=[],
                 viewparams=None):
        super(ViewInfo,self).__init__()
        onlyhelp = False
        if viewparams:
            # for compatible specifing step by param
            step = viewparams.step if step is None else step
            expert = viewparams.expert if expert is None else expert
            brief = viewparams.brief if brief is None else brief
            onlyhelp = viewparams.onlyhelp
        self.has_brief = has_brief
        if datavars:
            self.groups = []
            varGroups = datavars.getGroups()
            lastGroup = len(varGroups)-1
            # interate all vars group
            if step in(0,-1,None) or allsteps:
                briefData = datavars.getBrief()
                self.groups.append(self.stepGroup(varGroups,brief_label,
                            help_value=briefData.get("help",None),
                            next_value=briefData.get("next",None),
                            image_value=briefData.get("image","")))
            for i, groupInfo in enumerate(varGroups):
                if step in (None,-1) or step == i:
                    self.groups.append(GroupField(dv=datavars,info=groupInfo,
                                         last=(lastGroup==i),expert=expert,
                                         brief=brief,onlyhelp=onlyhelp))
                    if groupInfo['custom_buttons']:
                        for but in groupInfo['custom_buttons']:
                            self.groups[-1].fields.append(Field(
                                name=but[0],label=but[1],value=but[2],
                                element=but[3]))

        else:
            self.groups = groups

    def stepGroup(self,groupInfo,brief_label,help_value=None,next_value=None,
                  image_value=""):
        """Step group"""
        return GroupField(fields=[
                    Field(name = "Steps",
                          element = "table",
                          label = brief_label,
                          type = 'steps',
                          help = help_value,
                          value = next_value,
                          tablevalue = Table(
                          head = [i['name'] for i in groupInfo],
                          fields = [i.get('image','') for i in groupInfo]+\
                                   [image_value],
                          body=[self.arrayString(
                              list(i['normal']) + [""] + list(i['expert']))
                                for i in groupInfo]))])

#element = ['table', 'radio', 'combo', 'comboEdit', 'multichoice', \
           #'multichoice_add', 'check', 'check_tristate', 'expert', 'input']


class ViewParams(ClassSerializer):
    """
    Struct for _view methods
    """
    step = Integer          # number of group variables
    expert = Boolean        # request expert variables
    brief = Boolean         # request brief variables
    onlyhelp = Boolean      # request params for only help
    clienttype = String     # type of client "gui","console"


################# MESSAGE ####################
class ReturnedMessage(ClassSerializer):
    type = String
    field = String
    message = String
    expert = Boolean
    field_obj = Field


    def __init__(self, type = None, field = None, message = None,
                 expert = False, field_obj = None):
        self.type = type
        self.field = field
        self.message = message
        self.expert = expert
        if field_obj:
            self.field_obj = Field(dv=field_obj.parent, varObj=field_obj)
        else:
            self.field_obj = None

class Message(ClassSerializer):
    type = String
    message = String
    id = Integer
    result = Boolean
    onlyShow = String
    default = String

    def __init__(self, type='normal', message=None, id=None,
                 result=None, onlyShow=None, default=None):
        self.type = type
        self.message = message
        self.id = id
        self.result = result
        self.onlyShow = onlyShow
        self.default = default


class ReturnProgress(ClassSerializer):
    percent = Integer
    short_message = String
    long_message = String
    control = Integer

    def __init__(self, percent=0, short_message = None, long_message = None,
                        control = None):
        self.percent = percent
        self.short_message = short_message
        self.long_message = long_message
        self.control = control


class Frame(ClassSerializer):
    values = Array (Message)


# get and send client messages
class CoreWsdl:
    perm_denied = []
    perm_denied.append(Message (type = 'error', message = '403 Forbidden'))

    def callMethod(self,sid,info,logicClass=None,method=None,method_name=None,
                   callbackRefresh=lambda sid,dv:True):
        """
        Common algorithm of method executing
        """
        try:
            dv = self.get_cache(sid,method_name,"vars")
            if not dv:
                dv = getattr(self,"%s_vars"%method_name)()
            else:
                callbackRefresh(sid,dv)
                dv.processRefresh()
            if info:
                checkonly = info.CheckOnly
                checkall = info.CheckAll if hasattr(info,"CheckAll") else False
            else:
                checkonly = False
                checkall = False
            errors = map(lambda x:ReturnedMessage(**x),
                     dv.checkGroups(info,allvars=checkall or not checkonly))
            if errors:
                return errors
            if checkonly:
                returnmess = ReturnedMessage(type = '', message = None)
                return [returnmess]
            install_meth = type("CommonCore",(self.Common,
                                logicClass, object), {})
            pid = self.startprocess(sid, target=install_meth,
                            method=method,\
                            method_name=method_name,
                            args_proc = (dv,))
            returnmess = ReturnedMessage(type = 'pid', message = pid)
            returnmess.type = "pid"
            returnmess.message = pid
            dv = self.clear_cache(sid,method_name)
            return [returnmess]
        finally:
            if dv:
                self.set_cache(sid,method_name,"vars",dv,smart=False)
        return []

    def callAction(self,sid,info,logicClass={},
                   method_name=None,
                   actionClass=None,
                   callbackRefresh=lambda sid,dv:True):
        """
        Общий алгоритм вызова действия
        """
        try:
            dv = self.get_cache(sid,method_name,"vars")
            if not dv:
                dv = getattr(self,"%s_vars"%method_name)()
            else:
                callbackRefresh(sid,dv)
                dv.processRefresh()
            checkonly = False
            checkall = False
            if info and hasattr(info,"CheckOnly"):
                checkonly = info.CheckOnly
            if info and not hasattr(info,"CheckOnly"):
                checkall = True
            elif info and hasattr(info,"CheckAll"):
                checkall = info.CheckAll
            errors = map(lambda x:ReturnedMessage(**x),
                     dv.checkGroups(info,allvars=checkall or not checkonly))
            #if dv.Get('cl_env_debug_set') == 'on':
            #    dv.printGroup(info)
            if errors:
                return errors
            if checkonly:
                returnmess = ReturnedMessage(type='', message=None)
                return [returnmess]
            if not actionClass:
                return []
            objs = {}
            from calculate.core.server.func import CommonLink
            for k,v in logicClass.items():
                objs[k] = type("Logic",(CommonLink, v, object), {})()
            install_meth = type("CommonCore",(self.Common,
                                actionClass, object), {})
            pid = self.startprocess(sid, target=install_meth,
                            method="run",
                            method_name=method_name,
                            args_proc = (objs,dv,))
            returnmess = ReturnedMessage(type = 'pid', message = pid)
            returnmess.type = "pid"
            returnmess.message = pid
            self.clear_cache(sid,method_name)
            dv = None
            return [returnmess]
        finally:
            if dv:
                self.set_cache(sid,method_name,"vars",dv,smart=False)
        return []

    def fixInstallLocalization(self,sid,dv):
        """
        Метод смены локализации интерфейса на лету (во время выбора
        параметров метода)
        """
        if not "--start" in sys.argv:
            return False
        import threading
        curThread = threading.currentThread()
        curThread.lang = dv.Get('install.os_install_locale_lang')
        currentLang = self.get_cache(sid,"install","lang")
        if currentLang != curThread.lang:
            dv.clearGroups()
            self.install_vars(dv)
            dv.reinit()
            return True
        else:
            return False

    # verification of compliance certificate and process (pid)
    def check_cert_pid (self, sid, pid):
        import threading
        curThread = threading.currentThread()
        cert = curThread.client_cert

        from cert_cmd import find_cert_id
        cert_id = find_cert_id(cert, self.data_path, self.certbase)
        cert_id = int(cert_id)
        if cert_id == 0:
            return 0

        # session file
        if not os.path.exists(self.sids):
            os.system('mkdir %s' %self.sids)

        check = 0
        try:
            fd = open(self.sids_file, 'r')
        except IOError:
            return 0
        while 1:
            try:
                # read all on one record
                list_sid = pickle.load(fd)
            except:
                break
            # find session id in sids file
            if cert_id == int(list_sid[1]):
                if sid == int(list_sid[0]):
                    check = 1

        if check == 0:
            return 0
        fd = open(self.sids_pids, 'r')
        while 1:
            try:
                #read out on 1 record
                list_pid = pickle.load(fd)
            except:
                break
            if sid == int(list_pid[0]):
                if pid == int(list_pid[1]):
                    fd.close()
                    return 1
        fd.close()
        return 0

    # send to client all new message
    def process_messages (self, pid,client_type):
        result = []
        while len(self.glob_frame_list[pid]) > \
                  self.glob_process_dict[pid]['counter']:
            item = self.glob_process_dict[pid]['counter']
            onlyShow = self.glob_frame_list[pid][item].onlyShow
            if not client_type or \
                not onlyShow or onlyShow == client_type:
                result.append(self.glob_frame_list[pid][item])
            self.glob_process_dict[pid]['counter'] += 1
        return result

    # send to client new message from frame
    def client_get_frame(self, sid, pid, client_type):
        if self.check_cert_pid(sid, pid):
            return self.process_messages(pid,client_type)
        return self.perm_denied

    # send to client new message from frame
    def client_get_entire_frame(self, sid, pid):
        if self.check_cert_pid(sid, pid):
            try:
                results = self.glob_frame_list[pid]
            except:
                return self.perm_denied
            self.glob_process_dict[pid]['counter'] = \
                                                len(self.glob_frame_list[pid])
            return results
        return self.perm_denied

    def client_get_table (self, sid, pid, id):
        if self.check_cert_pid(sid, pid):
            return self.glob_table_dict[pid][id]
        return self.perm_denied

    def client_get_progress (self, sid, pid, id):
        if self.check_cert_pid(sid, pid):
            return self.glob_progress_dict[pid][id]
        return ReturnProgress(0,control=403)

    # get message from client
    def client_send_message(self, sid, pid, text):
        if self.check_cert_pid(sid, pid):
            self.glob_process_dict[pid]['answer'] = text
            return Message (type = 'normal', message = "")
        return self.perm_denied

    @rpc(Integer, Integer, String, _returns = Array(Message))
    #@Dec.check_permissions()
    def get_frame (self, sid, pid, client_type):
        return self.client_get_frame (sid, pid, client_type)

    @rpc(Integer, Integer, _returns = Array(Message))
    def get_entire_frame(self, sid, pid):
        return self.client_get_entire_frame (sid, pid)

    @rpc(Integer, Integer, Integer, _returns = ReturnProgress)
    #@Dec.check_permissions()
    def get_progress ( self, sid, pid, id):
        return self.client_get_progress (sid, pid, id)

    @rpc(Integer, Integer, Integer, _returns = Table)
    #@Dec.check_permissions()
    def get_table ( self, sid, pid, id):
        return self.client_get_table (sid, pid, id)

    # get messages, transferred from client
    @rpc(Integer, Integer, String, _returns = Message)
    #@Dec.check_permissions()
    def send_message ( self, sid, pid, text):
        return self.client_send_message (sid, pid, text)


class WsdlAdapter(object):
    adapted_class = None

    def __init__(self, source):
        self.source = source

    @classmethod
    def from_detect(cls, source):
        if isinstance(source, (cls.adapted_class, WsdlAdapter)):
            return source
        else:
            return cls(source)

    def __getattr__(self, item):
        return getattr(self.source, item)

    @staticmethod
    def Array(field, field_type):
        def wrapper(self):
            if getattr(self.source, field):
                return [field_type(x)
                        for x in getattr(getattr(self.source, field),
                                         field_type.__name__[:-7])]
            else:
                return []

        return property(wrapper)

    @staticmethod
    def StringArray(field):
        def wrapper(self):
            source_field = getattr(self.source, field)
            return source_field.string if source_field else []

        return property(wrapper)


class ChoiceValueAdapter(WsdlAdapter):
    adapted_class = ChoiceValue

    values = WsdlAdapter.StringArray("values")

    comments = WsdlAdapter.StringArray("comments")

    onChanged = WsdlAdapter.StringArray("onChanged")


class TableAdapter(WsdlAdapter):
    adapted_class = Table

    fields = WsdlAdapter.StringArray("fields")
    head = WsdlAdapter.StringArray("head")

    @property
    def body(self):
        if hasattr(self.source.body, 'stringArray'):
            return [row.string
                    for row in self.source.body.stringArray
                    if hasattr(row, "string")]
        return []

    values = WsdlAdapter.Array("values", ChoiceValueAdapter)


class FieldAdapter(WsdlAdapter):
    adapted_class = Field

    choice = WsdlAdapter.StringArray("choice")
    listvalue = WsdlAdapter.StringArray("listvalue")
    comments = WsdlAdapter.StringArray("comments")

    @property
    def tablevalue(self):
        return TableAdapter(self.source.tablevalue)


class GroupFieldAdapter(WsdlAdapter):
    adapted_class = GroupField

    fields = WsdlAdapter.Array("fields", FieldAdapter)


class ViewInfoAdapter(WsdlAdapter):
    adapted_class = ViewInfo

    groups = WsdlAdapter.Array("groups", GroupFieldAdapter)


class ArrayReturnedMessage(WsdlAdapter):
    @classmethod
    def from_detect(cls, source):
        if isinstance(source, (list,tuple)):
            return source
        else:
            return source.ReturnedMessage
