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

# Copyright 2008-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 os
import sys
import re
from os import path
from calculate.lib.datavars import (Variable, VariableError,
    ReadonlyVariable, ReadonlyTableVariable, TableVariable, FieldValue)
from calculate.lib.utils.portage import searchProfile
from calculate.lib.utils.files import readLinesFile, readFile

from calculate.lib.cl_lang import setLocalTranslate
from calculate.update.emerge_parser import EmergeCache

setLocalTranslate('cl_update3',sys.modules[__name__])

class VariableAcUpdateSync(ReadonlyVariable):
    """
    Action variable which has value "up" for package install and
    install this package
    """
    def get(self):
        action = self.Get("cl_action")
        if action in ("sync",):
            if self.Get('cl_update_world'):
                return self.Get('cl_update_world')
        return ""

class VariableClUpdateWorld(Variable):
    def get(self):
        if self.Get('cl_rebuild_world_set') == "on":
            return "rebuild"
        else:
            return ""

class VariableClRebuildWorldSet(Variable):
    """
    List of action update world, rebuild world, 
    """
    type = "bool"
    opt = ["--rebuild-world"]
    untrusted = True
    value = "off"

    def init(self):
        self.help = _("Rebuild world")
        self.label = _("Rebuild world")

class VariableClUpdateRevSet(Variable):
    """
    List of action update world, rebuild world, 
    """
    type = "bool"
    opt = ["--update-rev"]
    untrusted = True
    value = "on"
    check_after = ["cl_update_sync_rep",
                    "cl_update_metadata_force",
                    "cl_update_other_set",
                    "cl_update_eixupdate_force"]

    def init(self):
        self.help = _("revision update")
        self.label = _("Revision update")

class VariableClUpdateRep(Variable):
    """
    Обновлять репозитории до конкретной ревизии или до последней
    """
    type = "choice"
    value = "rev"

    def choice(self):
        return ["last","rev"]

class VariableClUpdateRepData(ReadonlyTableVariable):
    """
    Информация о репозиториях
    """
    source = ['cl_update_rep_name',
              'cl_update_rep_url',
              'cl_update_rep_path',
              'cl_update_rep_rev',
              'cl_update_branch_name']

class VariableClUpdateRepName(Variable):
    """
    Список имен используемых репозиториев
    """
    type = "list"
    value = []

class VariableClUpdateRepUrl(Variable):
    """
    Список путей до репозиториев
    """
    type = "list"
    value = []

class VariableClUpdateSystemProfile(ReadonlyVariable):
    """
    Профиль системы (симлинк /etc/make.profile')
    """
    def get(self):
        try:
            make_profile = self.Get('cl_make_profile')
            return path.normpath(
                path.join(path.dirname(make_profile),
                          os.readlink(make_profile)))
        except:
            raise VariableError(_("Failed to determine system profile"))

class VariableClUpdateLaymanStorage(ReadonlyVariable):
    """
    Путь к репозиториям layman
    """
    def get(self):
        laymanConf = "/etc/layman/layman.cfg"
        reStorage = re.compile("^storage\s*:\s*(\S+)")
        if path.exists(laymanConf):
            for line in readLinesFile(laymanConf):
                match = reStorage.search(line)
                if match:
                    return match.group(1)
        return "/var/lib/layman"

class VariableClUpdateRepPath(ReadonlyVariable):
    """
    Пути до репозиториев
    """
    type = "list"
    mapPath = {'portage':'/usr/portage'}

    def get(self):
        repPath = self.Get('cl_update_layman_storage')
        def generatePaths(names):
            for name in names:
                if name in self.mapPath:
                    yield self.mapPath[name]
                else:
                    yield path.join(repPath,name)
        return list(generatePaths(self.Get('cl_update_rep_name')))

class VariableClUpdateRepRev(Variable):
    """
    Ревизии до которых необходимо обновить репозитории
    """
    type = "list"

    def get(self):
        if self.Get('cl_update_rep') == 'rev':
            revPaths = searchProfile(self.Get('cl_update_system_profile'),
                                     "rev")
            if revPaths:
                revPath = revPaths[-1]
                dictNamesRevs = dict(map(lambda x:x.strip().partition('=')[::2],
                                readLinesFile(revPath)))
                return map(lambda x:dictNamesRevs.get(x,"last"),
                       self.Get('cl_update_rep_name'))
        return ["last"]*len(self.Get('cl_update_rep_name'))

class VariableClUpdateBranch(TableVariable):
    """
    Выбор веток репозиториев до которых необходимо обновиться
    """
    opt = ["--branch"]
    metavalue = 'BRANCHES'
    untrusted = True
    source = ["cl_update_branch_rep",
              "cl_update_branch_name"]

    def init(self):
        self.help = _("set branches for repository (REPOSITORY:BRANCH)")
        self.label = _("Repositories branches")

    def raiseReadonlyIndexError(self,fieldname="",variablename="",
                                value=""):
        """
        Неизвестный оврелей
        """
        raise VariableError(_("Repository %s not found")%value)

class VariableClUpdateBranchRep(ReadonlyVariable):
    """
    Список доступных репозиторием
    """
    type = "list"

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

    def get(self):
        return self.Get('cl_update_rep_name')


class VariableClUpdateBranchName(Variable):
    """
    Список доступных репозиторием
    """
    type = "choiceedit-list"

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

    def choice(self):
        return ["master", "develop", "update"]

    def get(self):
        def generateBranch():
            for reppath in self.Get('cl_update_rep_path'):
                headPath = path.join(reppath, ".git/HEAD")
                yield readFile(headPath).rpartition('/')[2].strip() or "master"

        return list(generateBranch())


class VariableClUpdateSyncRep(Variable):
    """
    Обновляемый репозиторий
    """
    type = "choice-list"
    element = "selecttable"
    opt = ["cl_update_sync_rep"]
    metavalue = "REPOSITORIES"
    untrusted = True

    def init(self):
        self.help = _("synchronize repositories (all by default)")
        self.label = _("Synchronize repositories")

    def set(self,value):
        orderList = self.Get('cl_update_rep_name')
        return sorted(value,key=lambda x:
            (orderList.index(x) if x in orderList else -1),reverse=True)
    
    def get(self):
        return list(reversed(self.Get('cl_update_rep_name')))

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

class VariableClUpdateSyncOverlayRep(ReadonlyVariable):
    """
    Обновляемые репозитории (исключая portage)
    """
    type = "list"

    def get(self):
        return filter(lambda x:x!="portage",self.Get('cl_update_sync_rep'))

class VariableClUpdateOutdateSet(ReadonlyVariable):
    """
    Флаг устанавливаемый в ходе обновления репозиториев,
    сообщающий что хотя бы один из запланированных репозиториев
    обновлен и следует обновляет различные метаданные

    Если обновляются прочие оверлеи - данные считаются что устарели
    """
    type = "bool"
    value = "off"

    def get(self):
        return self.Get('cl_update_other_set')

class VariableClUpdateMetadataForce(Variable):
    """
    Принудительное действие с метаданными
    """
    type = "choice"
    value = "auto"
    opt = ["--update-metadata"]
    syntax = "--{choice}-update-metadata"
    metavalue = "MODE"
    #untrusted = True

    def init(self):
        self.help = ("'force' - " + _("force update ebuilds metadata") + 
                     ",\n'skip' - " + _("skip update ebuilds metadata") +
                     ",\n'auto' - " + _("update metadata if they are outdated"))
        self.label = _("Update metadata")

    def choice(self):
        return [("force", _("Force")),
                ("skip", _("Skip")),
                ("auto", _("By need"))]

class VariableClUpdateEixupdateForce(Variable):
    """
    Принудительное действие с eix-update
    """
    type = "choice"
    value = "auto"
    opt = ["--eix-update"]
    syntax = "--{choice}-eix-update"
    metavalue = "MODE"
    #untrusted = True

    def init(self):
        self.help = ("'force' - " + _("force update eix cache") + 
                     ",\n'skip' - " + _("skip update eix cache") +
                     ",\n'auto' - " + _("update eix cache if it is outdated"))
        self.label = _("Update eix cache")

    def choice(self):
        return [("force", _("Force")),
                ("skip", _("Skip")),
                ("auto", _("By need"))]

class VariableClUpdateOtherSet(Variable):
    """
    Обновить остальные оверлеи
    """
    type = "bool"
    value = "off"
    opt = ["--update-other"]

    def init(self):
        self.help = _("update other overlays")
        self.label = _("Update other overlays")

class VariableClUpdateOtherRepData(ReadonlyTableVariable):
    """
    Информация о прочих репозиториях
    """
    source = ['cl_update_other_rep_name',
              'cl_update_other_rep_path']

    def generator(self):
        repNames = self.Get('cl_update_rep_name')
        for rpath in self.Get('cl_portdir_overlay'):
            repo_file = path.join(rpath,"profiles/repo_name")
            rname = readFile(repo_file).strip() or path.basename(rpath)
            if not rname in repNames:
                yield (rname, rpath)

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

class VariableClUpdateOtherRepName(FieldValue,ReadonlyVariable):
    """
    Список имен прочих репозиториев
    """
    type = "list"
    source_variable = "cl_update_other_rep_data"
    column = 0

class VariableClUpdateOtherRepPath(FieldValue,ReadonlyVariable):
    """
    Список путей до прочих репозиториев
    """
    type = "list"
    source_variable = "cl_update_other_rep_data"
    column = 1

class VariableClUpdateLaymanInstalled(Variable):
    """
    Путь до файла layman installed.xml
    """
    # TODO: извлечь из layman.cfg
    value = "/var/lib/layman/installed.xml"

class VariableClUpdateLaymanMake(Variable):
    """
    Путь до файла make.conf изменяемого layman`ом
    """
    # TODO: извлечь из layman.cfg
    value = "/var/lib/layman/make.conf"

class VariableClUpdatePretendSet(Variable):
    """
    Запустить предварительную проверку на обновления
    """
    type = "bool"
    value = "off"
    opt = ["-p", "--pretend"]

    def init(self):
        self.label = _("Pretend package update")
        self.help = _("instead  of actually performing packages update, "
                       "simply only display what have been installed")

class VariableClUpdateSyncOnlySet(Variable):
    """
    Не выполнять установку/обновление пакетов при обновлении
    """
    type = "bool"
    value = "off"
    opt = ["-s","--sync-only"]

    def init(self):
        self.label = _("Only synchronize repositories")
        self.help = _("do not update packages")

    def check(self, value):
        if (value == "on" and self.Get('cl_rebuild_world_set') != 'on' and
                not self.Get('cl_update_sync_rep') and
                    self.Get('cl_update_other_set') == 'off' and
                    self.Get('cl_update_rev_set') == 'off' and
                    self.Get('cl_update_metadata_force') != "force" and
                    self.Get('cl_update_eixupdate_force') != "force"):
            raise VariableError(_("Select at least one sync repository"))

class VariableClUpdateWaitAnotherSet(Variable):
    """
    Ждать завершения другого процесса обновления
    """
    type = "bool"
    value = "on"
    opt = ["--wait-another-update"]

    def init(self):
        self.label = _("Wait for complete another update")
        self.help = _("wait until the other updates or abort")
