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

# Copyright 2011-2016 Mir Calculate. http://www.calculate-linux.org
#
#  Licensed under the Apache License, Version 2.0 (the "License");
#  you may not use this file except in compliance with the License.
#  You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0 #
#  Unless required by applicable law or agreed to in writing, software
#  distributed under the License is distributed on an "AS IS" BASIS,
#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#  See the License for the specific language governing permissions and
#  limitations under the License.

import sys
import re
from os import path
from calculate.lib.datavars import (Variable, ReadonlyVariable, VariableError,
                                    TableVariable)
from calculate.core.server.api_types import LazyString

from calculate.lib.cl_lang import setLocalTranslate

_ = lambda x: x
setLocalTranslate('cl_core3', sys.modules[__name__])

from calculate.lib.utils.files import listDirectory
from itertools import *


class VarHelper(object):
    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>[^=]*?)(:?:[a-z]+)?)?$"
    #   "(?:!=(?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 fix_cmdline_params(fullname, location, v):
            if location.startswith("="):
                if re.match("^.*?:[a-z]+$", location):
                    location = location.rpartition(':')[2]
                else:
                    location = "system"
            return fullname, location, v

        def fix_value_for_append(fullname, location, v):
            if action == 'append':
                prevval = self.Get(fullname)
                typevar = self.parent.getInfo(fullname).type
                val = self.parent.unserialize(typevar, v)
                prevval.extend(filter(lambda x: x not in prevval, val))
            else:
                return fullname, location, v

        res = list(starmap(fix_value_for_append,
                           starmap(fix_cmdline_params,
                                   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):
        site_packages = 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)),
                     site_packages)):
            if path.exists(path.join(modDir, "datavars.py")):
                retlist.append(module)
        mod_map = {'lib': 0, 'install': 1, 'core': 2}
        return sorted(retlist, key=lambda x: mod_map.get(x, x))


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

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

    def get_all_var_name(self):
        """
        Init all variable modules and get names
        """
        for moduleName in self.Get('cl_variable_module_name'):
            module_section = self.mapSection.get(moduleName, moduleName)
            if module_section not 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] == module_section and
                        not varname.startswith('cl_variable')):
                    yield "%s.%s" % (module_section, varname)

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


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

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

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

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


class VariableClVariableValue(VarHelper, Variable):
    """
    Variable value
    """
    type = "list"

    class LazyVal(LazyString):
        """
        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):
            var_val = self.dv.Get(self.var)
            type_var = self.dv.getInfo(self.var).type
            return self.dv.serialize(type_var, var_val)

        def __str__(self):
            return self()

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

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

    def get(self):
        return map(self.fill_var,
                   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)
            type_var = self.parent.getInfo(var).type
            # convert list value to list (',' - separator)
            val = self.parent.unserialize(type_var, 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 fill_write(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, choice_val, val, error):
        re_index = re.compile("((?:\w+\.)?(\w+))(?:\[(\d+)\])?")
        varname = re_index.search(val)
        if varname and not varname.group(1) in choice_val:
            raise VariableError(_("Variable %s not found") % val)

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