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

# Copyright 2011-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 calculate.lib.datavars import (Variable,ReadonlyVariable,VariableError,
                                    DataVarsError, TableVariable)
import sys, re,os
from os import path

from calculate.lib.cl_lang import setLocalTranslate
from calculate.core.server.decorators import Dec
#from calculate.api.cert_cmd import find_id_cert
setLocalTranslate('cl_core3',sys.modules[__name__])

from calculate.lib.utils.files import listDirectory
from calculate.lib.utils.portage import isPkgInstalled,reVerSplitToPV
from calculate.lib.utils.common import getTupleVersion
from itertools import *
import importlib

class VarHelper:
    aliases = (('main','lib'),)
    mapName = dict(aliases)
    mapMethod = {'importLib':'importVariables',
                 'importConsolegui':'importGui'}
    mapObject = {'DataVarsLib':'DataVars',
                 'DataVarsConsolegui':'DataVarsGui'}
    mapSection = dict(map(lambda x:(x[1],x[0]),aliases))

class VariableClVariableData(TableVariable):
    """
    Table for modification variables
    """
    type = "table"
    opt = ["--set"]
    metavalue = "VAR[=VALUE[:LOCATION]]"
    # (arg-0:VAR)(arg-1:=(arg-2:VALUE):WRITE)
    syntax = "^(?P<arg_0>[^=:]+)(?P<arg_1>=(?P<arg_2>[^:=]*)(:?\w+)?)?$"
          #   "(?:!=(?P<arg_1>\w*)))$")
    source = ['cl_variable_fullname',
              'cl_variable_type',
              'cl_variable_location',
              'cl_variable_value']

    def set(self,value):
        """
        Fix data received from cmdline
        """
        action = self.Get('cl_action')
        def fixCmdLineParams(fullname,location,value):
            if location.startswith("="):
                location = location.partition(':')[2] or "system"
            return fullname,location,value
        def fixValueForAppend(fullname,location,value):
            if action == 'append':
                prevVal = self.Get(fullname)
                typeVar = self.parent.getInfo(fullname).type
                val = self.parent.unserialize(typeVar,value)
                prevVal.extend(filter(lambda x: not x in prevVal,
                               val))
            else:
                return fullname,location,value

        res = list(starmap(fixValueForAppend,
                   starmap(fixCmdLineParams,
                   value)))
        return res

    def init(self):
        self.label = _("Variables")
        self.help = _("write to the variable. VAR is the variable name. "
                      "VALUE is the new variable value. LOCATION is where "
                      "the env file is located (system,local,remote). "
                      "Variable will be removed from all env files if "
                      "only VAR is specified.")

    def raiseReadonlyIndexError(self,fieldname="",variablename="",
                                value=""):
        """
        Behavior on change readonly index
        """
        raise VariableError(_("Variable %s not found")%value)

class VariableClVariableModuleName(ReadonlyVariable):
    """
    Available modules
    """
    type = "list"

    def get(self):
        sitePackages = map(lambda x:path.join(x,"calculate"),
                       filter(lambda x:x.endswith('site-packages') and \
                                       x.startswith('/usr/lib'),sys.path))
        retList = []
        for module,modDir in chain(
                             *map(lambda x:map(lambda y:(path.basename(y),y),
                             listDirectory(x,True,True)),sitePackages)):
            if path.exists(path.join(modDir,"datavars.py")):
                retList.append(module)
        return sorted(retList,
               key=lambda x:{'lib':0,'install':1,'core':2}.get(x,x))

class VariableClVariableFullname(VarHelper,ReadonlyVariable):
    """
    Variable name
    """
    type = "list"
    def init(self):
        self.label = _("Name")

    def getAllVarName(self):
        """
        Init all variable modules and get names
        """
        for moduleName in self.Get('cl_variable_module_name'):
            moduleSection = self.mapSection.get(moduleName,moduleName)
            if not moduleSection in self.parent.importedModules:
                self.parent.importVariables('calculate.%s.variables'%moduleName)
                self.parent.flIniFile()
            for varname,vardata in sorted(self.parent.allVars.items()):
                if vardata[0] == moduleSection and \
                    not varname.startswith('cl_variable'):
                    yield "%s.%s"%(moduleSection,varname)

    def get(self):
        return list(self.getAllVarName())

class VariableClVariableType(VarHelper,ReadonlyVariable):
    """
    Variable type
    """
    type = "list"

    def init(self):
        self.label = _("Type")

    def fillType(self,var):
        varobj = self.parent.getInfo(var)
        return "%s%s"%(varobj.mode,varobj.getCharType())

    def get(self):
        return map(self.fillType,
               self.Get('cl_variable_fullname'))

class VariableClVariableValue(VarHelper,Variable):
    """
    Variable value
    """
    type = "list"
    class LazyVal:
        """
        Lazy value return string value only before using
        """
        def __init__(self,dv,var):
            self.var = var
            self.dv = dv
            self.dv.flIniFile()

        def __call__(self):
            isListOrTuple = lambda x: type(x) in (list,tuple)
            fixEmpty = lambda x: str(x) or "''"
            varVal = self.dv.Get(self.var)
            typeVar = self.dv.getInfo(self.var).type
            return self.dv.serialize(typeVar,varVal)

        def __str__(self):
            return self()

    def init(self):
        self.label = _("Value")

    def fillVar(self,var):
        """
        Fill all value of variables only lazy call
        """
        if not var in ('cl_variable_value','cl_variable_data'):
            return self.LazyVal(self.parent,var)
        else:
            return ""

    def get(self):
        return map(self.fillVar,
               self.Get('cl_variable_fullname'))

    def check(self,value):
        checklist = []
        for var,write,val in filter(lambda x:x[1]!="",
                             zip(self.Get('cl_variable_fullname'),
                                 self.Get('cl_variable_location'),
                                 value)):
            val = str(val)
            typeVar = self.parent.getInfo(var).type
            # convert list value to list (',' - separator)
            val = self.parent.unserialize(typeVar,val)
            # increase verbosity of error on set variable
            checklist.append((var,val))
        # double check
        for i in [0,1]:
            for var,val in checklist:
                try:
                    self.parent.Set(var,val)
                except VariableError as e:
                    raise VariableError([
                        VariableError(_("Error writing to variable %s:")%var),
                        e])

class VariableClVariableLocation(VarHelper,Variable):
    """
    Flag write variable to ini
    """
    type = "choice-list"

    def init(self):
        self.label = _("Location")

    def choice(self):
        return [("","Default")]+map(lambda x:(x,x),self.Get('cl_env_location'))

    def fillWrite(self,var):
        return self.parent.isFromIni(var)

    def get(self):
        return map(lambda x:self.parent.isFromIni(x),
               self.Get('cl_variable_fullname'))

class VariableClVariableFilter(VarHelper,Variable):
    """
    Display variables
    """
    type = "choiceedit"
    value = "all"

    opt = ["--filter"]
    metavalue = "FILTER"

    def init(self):
        self.label = _("Filter")
        self.help = _("display variables ('userset' displays user set "
                      "variables, 'writable' displays only writable "
                      "variables, 'system' displays user set variables "
                      "from the system env file, 'local' displays user "
                      "set variables from the local env file, 'remote' "
                      "displays user set variables from the remote env "
                      "file, 'all' displays all variables or filters them "
                      "by part of name)")

    def choice(self):
        return (("userset",_("User set")),
                ("writable",_("Writable")),
                ("system",_("System")),
                ("local",_("Local")),
                ("remote",_("Remote")),
                ("all",_("All")))


class VariableClVariableShow(VarHelper,Variable):
    """
    Display only value
    """
    type = "choice"
    value = ""

    opt = ["--only-value"]
    metavalue = "VARIABLE"

    def init(self):
        self.label = _("Show the value")
        self.help = _("show the variable value")

    def raiseWrongChoice(self,name,choiceVal,val,error):
        reIndex = re.compile("((?:\w+\.)?(\w+))(?:\[(\d+)\])?")
        varname = reIndex.search(val)
        if varname and not varname.group(1) in choiceVal:
            raise VariableError(_("Variable %s not found")%val)

    def choice(self):
        return self.Get('cl_variable_fullname')
