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


import sys, os
from shutil import copy2
import importlib
from os import path
import time
import re

from calculate.core.datavars import DataVarsCore,DataVars
from calculate.lib.datavars import Variable
from calculate.lib.cl_log import log
from calculate.lib import datavars
from calculate.lib.utils.files import (runOsCommand,scanDirectory,
                                       pathJoin,readFile,process)
from calculate.lib.utils.common import getPasswdUsers,getTupleVersion
from calculate.lib.utils.portage import isPkgInstalled,reVerSplitToPV
from calculate.lib.utils.content import getCfgFiles,PkgContents
import pwd
import glob
from calculate.lib.utils.files import getModeFile
import calculate.lib.cl_template as cl_template

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

__ = getLazyLocalTranslate(_)

class SetupPackageError(Exception):
    """
    Исключение вызванное во время настройки пакета
    """

class UpdateLogger:
    """
    Логгер для обновления настроек системы
    """

    logger = log("apply-templates",
                 filename="/var/log/calculate/update_config.log",
                 formatter="%(asctime)s - %(levelname)s - %(message)s")

    def ERROR(self, *arg, **argv):
        """
        Вывести ошибку в лог и на экран
        """
        self.logger.error(arg[0])
        self.printERROR(*arg, **argv)

    def SUCCESS(self, *arg, **argv):
        """
        Вывести сообщение в лог и на экран
        """
        self.logger.info(arg[0])
        self.printSUCCESS(*arg, **argv)

    def WARNING(self, *arg, **argv):
        """
        Вывести предупреждение в лог и на экран
        """
        self.logger.warn(arg[0])
        self.printWARNING(*arg, **argv)

class ChainProgressTemplate(cl_template.ProgressTemplate):
    """
    Наложение шаблонов с определением перенастройки зависимых пакетов
    """
    def __init__(self,startTask,endTask,*args,**kwargs):
        self.startTask = startTask
        self.endTask = endTask
        cl_template.ProgressTemplate.__init__(self,*args,**kwargs)

    def changeMergePackage(self,packages):
        """
        Изменился настраиваемый пакет (по зависимостям)
        """
        self.endTask()
        packages = filter(isPkgInstalled,
                   packages)
        self.startTask(_("Configuring dependencies: %s")%
                    ",".join(packages))
        return True

class StubVariable(Variable):
    """
    Переменная-заглушка используется при обновлении настроек пакета
    в emerge. Если переменная не найдена, то будет возвращена пустая
    строка.
    """
    value = ""

class UpdateConfigs(UpdateLogger):
    """
    Обновить настройки пакета в пользовательских профилях
    """

    def getXUsers(self):
        """
        Получить пользователей в X сессии
        """
        return self.clVars.Get('desktop.cl_desktop_online_user')

    def getConfiguredPasswdUsers(self):
        """
        Получить пользоватлей, которые есть в /etc/passwd (UID>=1000)
        и при этом у них есть настройка профиля (.calculate/ini.env)
        """
        USER,DIR = 0,1
        iniEnv = ".calculate/ini.env"
        return map(lambda x:x[USER], 
               filter(lambda x:path.exists(path.join(x[DIR],iniEnv)), 
               map(lambda x:(x,pwd.getpwnam(x).pw_dir),
               getPasswdUsers())))

    def _setClMergePkg(self,clVars,category,nameProgram):
        """
        Установить переменную cl_merge_pkg в зависимости от category и nameProgram
        """
        # выбрана перенастройка всех пакетов, установленных в системе
        if nameProgram == "all":
            clVars.Set("cl_merge_pkg",
                       map(lambda x:"{CATEGORY}/{PN}".format(**x),
                       filter(None,
                       map(reVerSplitToPV,
                       glob.glob('/var/db/pkg/*/*')))),
                       True)
        else:
            clVars.Set("cl_merge_pkg", ["%s/%s"%(category,nameProgram)], True)
        clVars.Set("cl_merge_set","on",True)

    def updateDesktopConfig(self,nameProgram,version,slot,category,configPath,
                           rootSet,verbose,dispatchConf,templates_locate,
                           ebuildPhase, useClt):
        """
        Настроить пакеты в профилях пользователей
        """
        # настраиватся будут пользователи из активных X сессии
        # и сконфигурированные
        xUsers = filter(lambda x:not "(unknown)" in x,
                 self.getXUsers())
        if not xUsers:
            self.logger.info(_("Package %s") %nameProgram)
            self.logger.warn(_("X session users not found"))
            return True
        self.logger.info(_("Package %s") %nameProgram)
        self.logger.info(_("Updating user configuration files"))
        mergeProgram = "calculate-utilities"
        firstValue = True

        clVars = DataVars()
        try:
            clVars.importData()
            clVars.flIniFile()
            for userName in xUsers:
                clVars.Set("cl_root_path", '/', True)
                clVars.Set("ur_login", userName, True)
                clVars.Set("cl_action", "desktop", True)
                clVars.Set("cl_verbose_set", verbose, True)
                clVars.Set("cl_protect_use_set", "off", True)
                clVars.Set("cl_template_path_use", templates_locate, True)

                self._setClMergePkg(clVars,category,nameProgram)

                clTempl = ChainProgressTemplate(self.startTask,
                                   self.endTask,
                                   self.setProgress,
                                   clVars, cltObj = False,
                                   printSUCCESS=self.printSUCCESS,
                                   printERROR=self.printERROR,
                                   askConfirm=self.askConfirm,
                                   printWARNING=self.printWARNING,
                                   printWarning=False)
                clTempl.onFirstValue = lambda *args: \
                        self.startTask(
                            _("User configuring the {nameProgram} package by "
                              "Calculate Utilities").format(
                              nameProgram=nameProgram))
                clTempl.firstValue = firstValue
                clTempl.applyTemplates()
                firstValue = clTempl.firstValue
        finally:
            clVars.close()
            self.endTask()
        return True


    def updateSystemConfig(self,nameProgram,version,slot,category,configPath,
                           rootSet,verbose,dispatchConf,templates_locate,
                           ebuildPhase, useClt):
        """
        Обновить конфигурационные файлы системы
        """
        self.logger.info(_("Package %s") %nameProgram)
        self.logger.info(_("Updating system cofiguration files"))
        if not os.path.exists(configPath):
            self.ERROR(_("Path '%s' does not exist")%configPath)
            return False

        clTempl = False
        mergeProgram = "calculate-utilities"
        clVars = DataVars()
        try:
            clVars.importData()
            clVars.flIniFile()
            clVars.Set("cl_root_path", configPath, True)

            # если конфигурирование пакета происходит не в корне
            if rootSet:
                # остальные пакеты настраиваются в корень
                clVars.Set("cl_root_path_next", '/', True)

            self._setClMergePkg(clVars,category,nameProgram)
            clVars.Set("cl_action", 'merge', True)
            clVars.Set("cl_verbose_set", verbose, True)
            clVars.Set("cl_dispatch_conf", dispatchConf, True)
            clVars.Set("cl_template_path_use", templates_locate, True)
            useClt = useClt in (True,"on")

            dictVer = {slot:version}
            cl_template.templateFunction.installProg.update(
                        {"%s/%s"%(category,nameProgram):dictVer,
                            "%s"%(nameProgram):dictVer})

            configFiles = []
            # используем объект шаблонов
            # с clt шаблонами, clt фильтром, без использования postDispatchConf
            clTempl = ChainProgressTemplate( self.startTask,
                               self.endTask,
                               self.setProgress,
                               clVars, cltObj=useClt,
                               cltFilter = True,
                               printSUCCESS=self.printSUCCESS,
                               printERROR=self.printERROR,
                               printWARNING=self.printWARNING,
                               askConfirm=self.askConfirm,
                               dispatchConf= self.dispatchConf
                                    if not ebuildPhase and \
                                        self.isInteractive() else None,
                               printWarning=False)
            # выводим сообщение о настройке пакета только если действительно
            # менялись файлы
            clTempl.onFirstValue = lambda *args: \
                self.startTask(_("System configuring for {nameProgram} "
                                 "package by Calculate Utilities").format(
                                 nameProgram=nameProgram))
            clTempl.applyTemplates()
        finally:
            clVars.close()
            self.endTask()
        return True

    def patchPackage(self,configPath,nameProgram):
        """
        Наложить патчи на пакет
        """
        self.clVars.Set("cl_root_path", configPath, True)
        clTempl = ChainProgressTemplate( self.startTask,
                           self.endTask,
                           self.setProgress,
                           self.clVars, cltObj = False,
                           printSUCCESS=self.printSUCCESS,
                           printERROR=self.printERROR,
                           askConfirm=self.askConfirm,
                           printWARNING=self.printWARNING,
                           printWarning=False)
        clTempl.onFirstValue = lambda *args: \
                self.startTask(
                    _("Using patches for the {nameProgram} package by "
                      "Calculate Utilities").format(
                      nameProgram=nameProgram),
                      progress=True)
        clTempl.applyTemplates()
        return True


class PackageUpdater(UpdateConfigs): #,updateUserConfigs):
    def __init__(self):
        self.clVars = None
        self.clTempl = None

    def initVars(self,datavars=None):
        """Primary variables initialization"""
        if not datavars:
            self.clVars = DataVars()
            self.clVars.importData()
            self.clVars.flIniFile()
        else:
            self.clVars = datavars

    def closeClTemplate(self):
        if self.clTempl:
            if self.clTempl.cltObj:
                self.clTempl.cltObj.closeFiles()
            self.clTempl.closeFiles()
            self.clTempl = None

    def postAction(self,error):
        """
        Post action for umount user res and write status_sync=error
        """
        self.closeClTemplate()
        return True


    def updateConfig(self):
        if not os.path.exists('/etc/calculate/calculate.env') and \
            not (dv.Get('cl_core_pkg_name') in ('calculate-utilities',
                                                'calculate-install') and
                 getTupleVersion(dv.Get('cl_core_pkg_version')) >=
                 getTupleVersion("3.1.0_alpha4")):
            self.printWARNING(
                _("Configuration skipping until "
                  "calculate-utilities are updated"))
            return True
        self.initVars(dv)
        clVars = DataVars()
        clVars.importData()
        clVars.flIniFile()
        slot = dv.Get('cl_core_pkg_slot')
        version = dv.Get('cl_core_pkg_version')
        category = dv.Get('cl_core_pkg_category')
        nameProgram = dv.Get('cl_core_pkg_name')
        # define that category/nameProgram installed
        if dv.Get('cl_ebuild_phase') in ('prerm','postrm'):
            version = ""
        dictVer = {slot:version}
        cl_template.templateFunction.installProg.update(\
                    {"%s/%s"%(category,nameProgram):dictVer,
                        "%s"%(nameProgram):dictVer})
        try:
            if os.environ.get("EBUILD_PHASE",""):
                clVars.raiseVariableNotFound = \
                    lambda name,*args,**kwargs: StubVariable(*args,
                                                             **kwargs)
                clVars.raiseModuleError = \
                    lambda name,*args,**kwargs: False
            if dv.Get('cl_core_pkg_system_set') == 'on':
                self.updateSystemConfig(dv,clVars)
            if dv.Get('cl_core_pkg_desktop_set') == 'on' and \
                not dv.Get('cl_ebuild_phase') in ("preinst","prerm"):
                self.updateDesktopConfig(dv,clVars)
        finally:
            if clVars:
                clVars.close()
        return True
