#-*- 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 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)
            if not onlyhelp or varObj.syntax or self.type == "table":
                self.label = varObj.label or varObj.name
                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)

            uniqBrief = list((set(info['brief']) | set(info['brief_force'])) - \
                            set(info['normal'])-
                             set(info['expert']))
            if 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))
            for varname in info['normal']:
                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 info['expert']:
                    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 = [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

    def __init__(self, type = None, field = None, message = None,
                 expert = False):
        self.type = type
        self.field = field
        self.message = message
        self.expert = expert

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

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

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
        fd = open(self.sids_file, 'r')
        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 self.perm_denied

    # 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)
