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

# Copyright 2015-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
from calculate.core.server.func import Action, Tasks
from calculate.lib.cl_lang import setLocalTranslate, getLazyLocalTranslate
from calculate.lib.cl_template import TemplatesError
from calculate.lib.utils.files import FilesError, isEmptyFile, readFile
from calculate.update.emerge_parser import EmergeError
from calculate.update.update import UpdateError
from ..datavars import BuilderError
from os import path
from calculate.lib.utils.git import GitError
from calculate.lib.utils.portage import (EmergeLogNamedTask,
                                         PackageList, EmergeLog, isPkgInstalled)
from calculate.install.distr import DistributiveError
from calculate.update.update_tasks import EmergeMark

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


class BuilderConditions(object):
    @staticmethod
    def was_installed(pkg, task_name):
        def func(Get):
            task = EmergeLog(EmergeLogNamedTask(task_name),
                             prefix=Get('cl_builder_path'))
            return bool(PackageList(task.list)[pkg])

        return func

    @staticmethod
    def need_depclean(pkg, task_name):
        def func(Get):
            task = EmergeLog(EmergeLogNamedTask(task_name),
                             prefix=Get('cl_builder_path'))
            return bool(PackageList(task.list)[pkg])

        return func

    @staticmethod
    def force_automagic(Get):
        cache_list = ("/var/calculate/tmp/%s.checkdep" %
                      Get("cl_builder_id_path"))
        return not isEmptyFile(cache_list, grab=True)

    @staticmethod
    def force_preserved(Get):
        pfile = path.join(Get("cl_builder_path"),
                          "var/lib/portage/preserved_libs_registry")
        content = readFile(pfile).strip()
        if not content or content[1:-1].strip() == '':
            return False
        else:
            return True

class ClBuilderUpdateAction(Action):
    """
    Действие обновление конфигурационных файлов
    """
    # ошибки, которые отображаются без подробностей
    native_error = (DistributiveError, FilesError, UpdateError,
                    TemplatesError,
                    BuilderError, GitError, EmergeError)

    successMessage = None
    failedMessage = None
    interruptMessage = __("System update manually interrupted")

    # список задач для действия
    tasks = [
                {'name': 'check_build_run',
                 'method': 'Builder.check_build_run()'},
                {'name': 'check_chroot_run',
                 'method': 'Builder.check_chroot_run()'},
                {'name': 'clear_log',
                 'method': 'Builder.clear_log(cl_builder_id_path)',
                 },
                {'name': 'allow_scan_binhost',
                 'method': 'Builder.setVariable("cl_builder_binhost_scan_set",'
                           '"on",True)'
                },
                {'name': 'apply_template',
                 'message': __("Configuring build"),
                 'method': 'Builder.applyTemplates(cl_builder_target,False,'
                           'False,None)',
                 },
                {'name': 'invalidate_vars',
                 'method': 'Builder.invalidateVariables('
                           '"cl_builder_linux_datavars")'
                 },
                {'name': 'reconfigure_vars',
                 'method': 'Builder.reconfigureProfileVars('
                           'cl_builder_linux_datavars,cl_builder_path)'
                 },
                {'name': 'apply_branch_vars',
                 'method': 'Builder.apply_branch_variables()'
                 },
                {'name': 'reps_synchronization',
                 'group': __("Repositories synchronization"),
                 'tasks': [
                     # запасная синхронизация, в ходе которой ветки
                     #  обновляются до master
                     {'name': 'sync_reps_fallback',
                      'foreach': 'cl_builder_sync_rep',
                      'message':
                          __("Fallback syncing the {eachvar:capitalize} "
                             "repository"),
                      'method': 'Builder.syncRepositories(eachvar,True)',
                      'condition': lambda Get: (
                          Get('cl_builder_binary_set') == "on" and
                          not Get('update.cl_update_binhost_data')[0])
                      },
                     # обновление переменных информации из binhost
                     {'name': 'invalidate_vars',
                      'method':
                          'Builder.invalidateVariables('
                          '"cl_builder_linux_datavars")',
                      'condition': lambda Get: (
                          Get('cl_builder_binary_set') == "on" and
                          not Get('update.cl_update_binhost_data')[0])
                      },
                     {'name': 'binhost_unavailable',
                      'error': __("Update server is unavailable"),
                      'condition': lambda Get: (
                          Get('cl_builder_binary_set') == "on" and
                          not Get('update.cl_update_binhost_data')[0])
                      },
                     {'name': 'sync_reps',
                      'foreach': 'cl_builder_sync_rep',
                      'message': __("Checking {eachvar:capitalize} updates"),
                      'method': 'Builder.syncRepositories(eachvar)',
                      'condition': lambda Get: Get('cl_builder_sync_rep')
                      },
                     {'name': 'check_binhost',
                      'method': 'Builder.check_binhost(True)',
                      'condition': lambda Get: Get(
                          'cl_builder_binary_set') == "on"
                      },
                     {'name': 'update_layman',
                      'message': __("Updating layman cache"),
                      'method': "Builder.update_layman(cl_builder_path)",
                      'condition': lambda Get: isPkgInstalled(
                          "app-portage/layman", prefix=Get('cl_chroot_path')),
                      'essential': False,
                      },
                     {'name': 'sync_other_reps',
                      'foreach': 'builder.cl_builder_other_rep_name',
                      'message': __(
                          "Syncing the {eachvar:capitalize} repository"),
                      'method': 'Builder.syncLaymanRepository(eachvar)',
                      'condition': lambda Get: Get(
                          'update.cl_update_other_set') == 'on'
                      },
                     {'name': 'trim_reps',
                      'foreach': 'cl_builder_sync_rep',
                      'message': __("Cleaning the history of the "
                                    "{eachvar:capitalize} repository"),
                      'method': 'Builder.trimRepositories(eachvar)',
                      'condition': lambda Get: (
                          Get('cl_builder_sync_rep') and
                          Get('update.cl_update_onedepth_set') == 'on')
                      },
                     {'name': 'regen_cache',
                      'foreach': 'cl_builder_sync_overlay_rep',
                      'essential': False,
                      'method': 'Builder.regenCache(eachvar)',
                      'condition': (lambda Get: (
                          Get('builder.cl_builder_outdate_set') == 'on' and
                          Get('update.cl_update_egencache_force') != 'skip' or
                          Get('update.cl_update_egencache_force') == 'force'))
                      },
                     {'name': 'eix_update',
                      'message': __("Updating the eix cache for "
                                    "{cl_builder_eix_repositories}"),
                      'method': 'Builder.eixUpdate(cl_builder_repository_name)',
                      'condition': (lambda Get: (
                          Get('builder.cl_builder_outdate_set') == 'on' and
                          Get('update.cl_update_eixupdate_force') != 'skip' or
                          Get('update.cl_update_eixupdate_force') == 'force'))
                      },
                     {'name': 'sync_reps:cleanpkg',
                      'message': __(
                          "Removing obsolete distfiles and binary packages"),
                      'method': 'Builder.cleanpkg()',
                      'condition': lambda Get: (
                          Get('builder.cl_builder_outdate_set') == 'on' and
                          Get('update.cl_update_cleanpkg_set') == 'on'),
                      'essential': False
                      },
                     {'name': 'update_packages_cache',
                      'message': __("Update packages index"),
                      'method': 'Builder.download_packages('
                                'update.cl_update_portage_binhost,'
                                'update.cl_update_package_cache)',
                      'essential': False,
                      'condition': lambda Get: (
                          Get('update.cl_update_package_cache') and (
                              Get('builder.cl_builder_outdate_set') == 'on' or
                              Get('update.cl_update_package_cache_set') == 'on'))
                      },
                     # сообщение удачного завершения при обновлении репозиториев
                     {'name': 'success_syncrep',
                      'message': __("Synchronization finished"),
                      'depend': (Tasks.success() & Tasks.has_any(
                          "sync_reps", "sync_other_reps", "emerge_metadata",
                          "eix_update")),
                      }
                 ]
                 },
                {'name': 'reps_synchronization',
                 'group': __("System configuration"),
                 'tasks': [
                     {'name': 'revision',
                      'message': __("Fixing the settings"),
                      'method': 'Builder.apply_templates(cl_builder_path,'
                                'cl_template_clt_set,True,None,False,"sync",'
                                'cl_builder_linux_datavars)',
                      'condition': lambda Get: Get('cl_templates_locate')
                      },
                     {'name': 'dispatch_conf',
                      'message': __("Updating configuration files"),
                      'method': 'Builder.dispatchConf(None,cl_builder_path)',
                      'condition': lambda Get: Get('cl_dispatch_conf') != 'skip'
                      },
                 ]
                 },
                {'name': 'emerge_update_world',
                 'group': __("Updating packages"),
                 'tasks': [
                     {'name': 'update_world',
                      'message': __("Calculating dependencies"),
                      'method': 'Builder.emerge_ask('
                                'update.cl_update_pretend_set,'
                                '"-uDN","--changed-deps",'
                                '"@world")'
                      }
                 ],
                 'condition': lambda Get: (
                     Get('update.cl_update_sync_only_set') == 'off')
                 },
                {'name': 'update_other',
                 'condition': lambda Get: (
                     Get('update.cl_update_pretend_set') == 'off' and
                     Get('update.cl_update_sync_only_set') == 'off')
                 },
                {'name': 'update_other:group_changed_packages',
                 'group': __("Rebuild modified packages"),
                 'tasks': [
                     {'name': 'changed_packages',
                      'message': __("Calculating dependencies"),
                      'method': 'Builder.rebuild_changed_packages('
                                'cl_builder_path,'
                                'cl_builder_repository_data)',
                      'condition': lambda Get:
                      Get('cl_builder_rebuild_changed_set') == 'on'
                      }
                 ]
                 },
                {'name': 'update_other:update_perl',
                 'group': __("Updating Perl"),
                 'tasks': [
                     {'name': 'perl_cleaner',
                      'message': __(
                          'Find & rebuild packages and Perl header files '
                          'broken due to a perl upgrade'),
                      'method': 'Builder.chroot_emergelike(cl_builder_path,'
                                '"perl-cleaner","all")',
                      'condition': BuilderConditions.was_installed(
                          'dev-lang/perl$', EmergeMark.PerlCleaner),
                      'decoration': 'Builder.update_task("%s")'
                                    % EmergeMark.PerlCleaner
                      },
                 ]
                 },
                {'name': 'update_other:depclean',
                 'group': __("Cleaning the system from needless packages"),
                 'tasks': [
                     {'name': 'update_depclean',
                      'message': __("Calculating dependencies"),
                      'method': 'Builder.depclean()',
                      },
                 ]
                 },
                {'name': 'update_world:update_modules',
                 'group': __("Rebuilding dependent modules"),
                 'tasks': [
                     {'name': 'update_world:module_rebuild',
                      'message': __('Updating Kernel modules'),
                      'method': 'Builder.emerge(cl_builder_path,"",'
                                '"@module-rebuild")',
                      'condition': BuilderConditions.was_installed(
                          'sys-kernel/.*source', EmergeMark.KernelModules),
                      'decoration': 'Builder.update_task("%s")' %
                                    EmergeMark.KernelModules
                      },
                     {'name': 'update_world:x11_module_rebuild',
                      'message': __('Updating X.Org server modules'),
                      'method': 'Builder.emerge(cl_builder_path,"",'
                                '"@x11-module-rebuild")',
                      'condition': BuilderConditions.was_installed(
                          'x11-base/xorg-server', EmergeMark.XorgModules),
                      'decoration': 'Builder.update_task("%s")' %
                                    EmergeMark.XorgModules
                      },
                     {'name': 'update_world:preserved_rebuild',
                      'message': __('Updating preserved libraries'),
                      'method': 'Builder.emerge(cl_builder_path,"",'
                                '"@preserved-rebuild")',
                      'condition': lambda Get: (
                          BuilderConditions.was_installed(
                              '.*', EmergeMark.PreservedLibs)(Get) or
                          BuilderConditions.force_preserved(Get)),
                      'decoration': 'Builder.update_task("%s")' %
                                    EmergeMark.PreservedLibs
                      },
                     {'name': 'update_world:revdev_rebuild',
                      'message': __('Checking reverse dependencies'),
                      'method': 'Builder.chroot_revdep_rebuild(cl_builder_path,'
                                '"revdep-rebuild")',
                      'condition': lambda Get: (
                          Get('update.cl_update_skip_rb_set') == 'off' and
                          BuilderConditions.was_installed(
                              '.*', EmergeMark.RevdepRebuild)(Get)),
                      'decoration': 'Builder.update_task("%s")' %
                                    EmergeMark.RevdepRebuild
                      },
                 ],
                 'depend': Tasks.has("update_other")
                 },
                {'name': 'update_other:check_automagic_group',
                 'group': __("Check for auto depends"),
                 'tasks': [
                     {'name': 'check_automagic',
                      'method': 'Builder.check_automagic(cl_builder_path)',
                      'condition': lambda Get: (
                          Get('builder.cl_builder_check_automagic_set') == 'on'
                          and (BuilderConditions.was_installed(
                              '.*', EmergeMark.Automagic)(Get) or
                               BuilderConditions.force_automagic(Get))),
                      'decoration': 'Builder.update_task("%s")' %
                                    EmergeMark.Automagic
                      },
                 ]
                 },
                {'name': 'update_world:update_configures',
                 'group': __("Completion of the system update"),
                 'tasks': [
                     {'name': 'update_world:dispatch_conf_end',
                      'message': __("Updating configuration files"),
                      'method': 'Builder.dispatchConf(None,cl_builder_path)',
                      'condition': lambda Get: Get('cl_dispatch_conf') != 'skip'
                      },
                     {'name': 'update_world:binary_cleaning',
                      'message': __("Cleaning the binary repository"),
                      'method': 'Builder.binaryCleaning()',
                      'condition': lambda Get: Get(
                          'cl_builder_binary_set') == "off"
                      },
                 ],
                 'depend': Tasks.has("update_other")
                 },
                {'name': 'update_other:reading_news_group',
                 'group': __("Last news:"),
                 'tasks': [
                     {'name': 'update_other:reading_news',
                      'method': 'Builder.reading_news(cl_builder_path)',
                      'essential': False
                      },
                 ]
                 },
                {'name': 'update_other:check_obsolete',
                 'method': 'Builder.check_obsolete(cl_builder_path)'
                 },
            ] + [
                {'name': 'failed',
                 'error': __("Failed to update the system"),
                 'depend': (Tasks.failed() & Tasks.hasnot("interrupt") &
                            Tasks.success_all("check_build_run"))
                 },
                # сообщение удачного завершения при обновлении ревизии
                {'name': 'update_other:success_rev',
                 'message': __("The system was successfully updated"),
                 }
            ]
