# UpdateList.py

from gettext import gettext as _
import logging
import os
import json
import yaml
from gi.repository import Gio
from .errors import *
from .enums import *

class UpdateList():
    OUTPUT_JSON_PATH = '/var/lib/kylin-system-updater/json/'

    def __init__(self,parent):
        self.parent = parent
        self.uuconfigs = self.parent.uuconfigs
        self.push_groups = []
        self.push_steps = []
        self.push_singles = []
        self.pkgs_data = {}

        if 'XDG_CURRENT_DESKTOP' in os.environ:
            self.current_desktop = os.environ.get('XDG_CURRENT_DESKTOP')
        else:
            self.current_desktop = ''

        if 'XDG_DATA_DIRS' in os.environ and os.environ['XDG_DATA_DIRS']:
            data_dirs = os.environ['XDG_DATA_DIRS']
        else:
            data_dirs = '/usr/local/share/:/usr/share/'
            
        self.application_dirs = [os.path.join(base, 'applications')
                                for base in data_dirs.split(':')]
    
    def get_push(self):
        return self.push_groups + self.push_singles

    def push_pop(self,content):
        for cont in content:
            if cont in self.push_singles:
                self.push_singles.remove(cont)
            if cont in self.push_groups:
                self.push_groups.remove(cont)

    def classify_content(self,content):
        if content == []:
            steps = []
            if self.push_groups:
                steps = self.push_steps
            return self.push_singles,self.push_groups,steps
        else:
            singles = []
            groups = []
            steps = []
            for cont in content:
                if cont in self.push_singles:
                    singles.append(cont)
                elif cont in self.push_groups:
                    groups.append(cont)
            
            if groups != [] and self.push_steps != []:
                steps = self.push_steps
            return singles,groups,steps
    
    def copy_pkgs_data(self,content):
        updating_data = {}
        for con in content:
            updating_data.update({con:self.pkgs_data.get(con,{})})
        
        return updating_data

    def is_steps(self,content):
        for cont in content:
            if cont in self.push_singles:
                return False
            
        if self.push_steps == []:
            return False
        else:
            return True

    def _make_important_list(self,cache,pkgs_upgrade,important_list = []):
        upgradeable_pkgs = []
        # tmp = []
        upgradeable_groups = []

        logging.info("The Server Push List: %a",important_list)

        for pkg_name in important_list:
            if pkg_name in cache:
                pkg_obj = cache[pkg_name]
                if pkg_obj.is_installed:
                    if pkg_name in pkgs_upgrade:
                        pkgs_upgrade.remove(pkg_name)
                        upgradeable_pkgs.append(pkg_obj.name)
                else:
                    upgradeable_pkgs.append(pkg_obj.name)
            else:
                upgradeable_groups.append(pkg_name)

        logging.info("Push Single Packages: %a, Push Groups:%a",upgradeable_pkgs,upgradeable_groups)
        return upgradeable_groups,upgradeable_pkgs

    def _make_pkg_info_json(self,cache,pkgs_list):
        total_download_size = 0
        total_installed_size = 0
        pkgs_info_json = {}

        for pkg_name in pkgs_list:
            pkg = cache[pkg_name]
            cur_version = getattr(pkg.installed, "version", '')
            new_version = getattr(pkg.candidate, "version", '')

            download_size = getattr(pkg.candidate, "size", 0)
            installed_size = getattr(pkg.candidate, "installed_size", 0)

            total_download_size = total_download_size + download_size
            total_installed_size = total_installed_size + installed_size

            pkgs_info_json.update({pkg_name:{"cur_version":cur_version,"new_version":new_version,\
                                            "download_size":str(download_size),"install_size":str(installed_size)}})

        pkgs_info_json.update({"total_download_size":str(total_download_size)})
        pkgs_info_json.update({"total_install_size":str(total_installed_size)})
        return pkgs_info_json

    def _check_pkg_in_cache(self,cache,pkgs_list):
        new_pkgs_list = []
        for pkg_name in pkgs_list:
            #检查是否在cache 以及 是否安装检查 
            if pkg_name in cache and not cache[pkg_name].is_installed:
                new_pkgs_list.append(pkg_name)
            else:
                pass
        return new_pkgs_list

    def _make_group_output_json(self,data,data_yaml,upgrade_pkgs_json,install_pkgs_json,output_path):
        groups_base_info = {}
        output_json = {}
        
        output_config_name = output_path + data['package'] + '.json'

        groups_base_info.update({"package":data['package']})
        groups_base_info.update({"new_version":data['version']})
        groups_base_info.update({"name":data['name']})
        groups_base_info.update({"description":data['description']})
        groups_base_info.update({"icon":data['icon']})
        
        groups_base_info.update({"changelog":data_yaml['changelog']})

        output_json.update(groups_base_info)
        output_json.update({"upgrade_list":upgrade_pkgs_json})
        output_json.update({"install_list":install_pkgs_json})
        # output_json.update({"hold_list":hold_pkgs_list})
        # output_json.update({"remove_list":remove_pkgs_list})

        with open(output_config_name, 'w', encoding='utf-8') as f:
            json.dump(output_json, f, ensure_ascii=False, indent=4)                   
        logging.info("Generate Jsonfile(%s) to complete... ",output_config_name)

    def _split_package_id(self,package):
        """Return the name, the version number and the release of the
        specified package."""
        if "=" in package:
            name, version = package.split("=", 1)
            release = None
        elif "/" in package:
            name, release = package.split("/", 1)
            version = None
        else:
            name = package
            version = release = None
        return name, version, release

    def _make_downgrade(self,cache,downgrade_pkgs):
        output_downgrade = []
        adjust_pkgs = []
        for pkg_name, pkg_ver, pkg_rel in [self._split_package_id(pkg)
                                            for pkg in downgrade_pkgs]:
            try:
                pkg = cache[pkg_name]
            except KeyError:
                logging.warning("Package %s isn't available",pkg_name)
                continue
            if not pkg.is_installed:
                logging.warning("Package %s isn't installed",pkg_name)

            if pkg_ver:
                if pkg.installed and pkg.installed.version < pkg_ver:
                    logging.warning("The former version %s of %s is already installed",pkg.installed.version, pkg.name)
                    continue
                elif pkg.installed and pkg.installed.version == pkg_ver:
                    logging.warning("The version %s of %s is already installed",pkg.installed.version, pkg.name)
                    continue
                
                try:
                    pkg.candidate = pkg.versions[pkg_ver]
                except KeyError:
                    logging.warning("The version %s of %s isn't available",pkg_ver, pkg_name)
                    continue

                output_downgrade.append(pkg_name)
                adjust_pkgs.append(pkg_name+'='+pkg_ver)

        return output_downgrade,adjust_pkgs

    def _get_downgrade_list(self,cache,data):
        downgrade_pkgs = []

        try:
            downgrade_raw = data['force_install_list']
        except Exception as e:
            downgrade_raw = []

        for pkg_name, pkg_ver, pkg_rel in [self._split_package_id(pkg)
                                            for pkg in downgrade_raw]:
            
            if pkg_name in cache:
                downgrade_pkgs.append(pkg_name)
            else:
                logging.warning("Package %s isn't available",pkg_name)
                continue

        return downgrade_raw,downgrade_pkgs

    def _make_groups_pkgs(self,cache,data,pkgs_upgrade = []):

        upgrade_pkgs_list =  data['upgrade_list']
        new_install_list = self._check_pkg_in_cache(cache,data['install_list'])

        new_upgrade_list = list(set(pkgs_upgrade) & set(upgrade_pkgs_list))

        for pkg in new_upgrade_list:
            pkgs_upgrade.remove(pkg)

        for pkg in self.push_singles:
            if pkg in new_install_list:
                new_install_list.remove(pkg)

        return new_install_list,new_upgrade_list

    def _make_groups_upgrade(self,cache,group_list,is_openkylin,pkgs_install,pkgs_upgrade,output_path,config_path): 
        upgrade_list = []
        install_list = []

        if os.path.isdir(config_path) == False:
            logging.warning("configPath(%s) is not exists...",config_path)
            return

        files = os.listdir(config_path)

        for ifile in files:
            if ifile.endswith('.json'):
                with open(config_path+ifile,'r') as f:
                    try:
                        data = json.load(f)
                    except Exception as exc:
                        logging.error(exc)
                        raise UpdateBaseError(ERROR_LOAD_CONFIG_FAILED)

                group_name = data['package']
                with open(config_path + group_name + ".yaml", "r") as stream:
                    try:
                        data_yaml = yaml.safe_load(stream)
                    except Exception as exc:
                        logging.error(exc)
                        raise UpdateBaseError(ERROR_LOAD_CONFIG_FAILED)

                if not group_name in group_list:
                    continue

                if is_openkylin == True:
                    install_list,upgrade_list = pkgs_install,pkgs_upgrade
                else:
                    install_list,upgrade_list = self._make_groups_pkgs(cache,data,pkgs_upgrade)

                if len(install_list) == 0 and len(upgrade_list) == 0:
                    continue

                upgrade_pkgs_json = self._make_pkg_info_json(cache,upgrade_list)
                install_pkgs_json = self._make_pkg_info_json(cache,install_list)
                self._make_group_output_json(data,data_yaml,upgrade_pkgs_json,install_pkgs_json,output_path)

                # 读取策略下发 策略优先级最高
                if self.uuconfigs.getWithDefault("updateStrategiesManager","strategiesState",False) == True:
                    install_type = self.uuconfigs.getWithDefault("updateStrategiesManager","installType",DEFAULT_INSTALL_TYPE)
                    if install_type == DEFAULT_INSTALL_TYPE:
                        install_type = data.setdefault("install_type",RUNTIME_INSTALL_TYPE)
                else:
                    install_type = data.setdefault("install_type",RUNTIME_INSTALL_TYPE)

                poweroff_install = install_type == POWEROFF_INSTALL_TYPE
                
                progress_begin = 50
                if poweroff_install == True:
                    logging.info("Turn on shutdown installation mode...")
                    progress_begin = 0

                step_update = data.setdefault("step_update",{})
                if step_update != {}:
                    if poweroff_install == False:
                        progress_begin = self._make_steps_runtime(progress_begin,step_update,install_list,upgrade_list)
                    else:
                        progress_begin = self._make_steps_poweroff(progress_begin,step_update,install_list,upgrade_list)

                put_data = {group_name:{"progress_begin":progress_begin,"progress_end":100,"upgrade_list":upgrade_list,\
                                            "install_list":install_list,"script":data.setdefault("script",{}),\
                                                "install_type":install_type}}

                self.push_groups.append(group_name)
                self.pkgs_data.update(put_data)
                logging.info("Group(%s) upgrade:%d install:%d",group_name,len(upgrade_list),len(install_list))
            else:
                pass
    
    def _make_steps_poweroff(self,steps_begin,step_update,install_list,upgrade_list):
        delete_step = []
        for step_name in step_update:
            step_data = step_update.get(step_name,{})
            if step_data == {}:
                continue
            step_install = list(set(step_data.setdefault("install_list",[])) & set(install_list))
            step_upgrade = list(set(step_data.setdefault("upgrade_list",[])) & set(upgrade_list))

            if step_install == [] and  step_upgrade == []:
                delete_step.append(step_name)
                continue

            step_data["install_list"] = step_install
            step_data["upgrade_list"] = step_upgrade
        
        for step in delete_step:
            step_update.pop(step)

        for step_name in step_update:
            step_data = step_update.get(step_name,{})
            step_install = step_data["install_list"]
            step_upgrade = step_data["upgrade_list"]

            value = int((len(step_install) + len(step_upgrade))/(len(install_list) + len(upgrade_list)) * 50)
            if value == 0:
                value = 1                     
            progress_end = steps_begin + value
            step_data.update({"progress_begin":steps_begin})
            step_data.update({"progress_end":progress_end})
            steps_begin = progress_end
            
            logging.info("Steps(%s) upgrade:%d install:%d",step_name,len(step_upgrade),len(step_install))
            self.push_steps.append(step_name)
            self.pkgs_data.update({step_name:step_data})

        return steps_begin

    def _make_steps_runtime(self,steps_begin,step_update,install_list,upgrade_list):
        for step_name in step_update:
            step_data = step_update.get(step_name,{})
            if step_data == {}:
                continue
            step_install = list(set(step_data.setdefault("install_list",[])) & set(install_list))
            step_upgrade = list(set(step_data.setdefault("upgrade_list",[])) & set(upgrade_list))
            need_reboot = step_data.setdefault("need_reboot",False)
            if step_install == [] and  step_upgrade == []:
                continue
            step_data["install_list"] = step_install
            step_data["upgrade_list"] = step_upgrade

            value = int((len(step_install) + len(step_upgrade))/(len(install_list) + len(upgrade_list)) * 50)                        
            if need_reboot == True:
                step_data.update({"progress_begin":steps_begin})
                step_data.update({"progress_end":100})
            else:
                progress_end = steps_begin + value
                step_data.update({"progress_begin":steps_begin})
                step_data.update({"progress_end":progress_end})
                steps_begin = progress_end
            
            logging.info("Steps(%s) upgrade:%d install:%d",step_name,len(step_upgrade),len(step_install))

            self.push_steps.append(step_name)
            self.pkgs_data.update({step_name:step_data})

            if need_reboot == True:
                break
        return steps_begin


    def _rate_application_for_package(self, application, pkg):
        score = 0
        desktop_file = os.path.basename(application.get_filename())
        application_id = os.path.splitext(desktop_file)[0]

        if application.should_show():
            score += 1

            if application_id == pkg.name:
                score += 5

        return score

    def _file_is_application(self, file_path):
        # WARNING: This is called often if there's a lot of updates. A poor
        # performing call here has a huge impact on the overall performance!
        if not file_path.endswith(".desktop"):
            # First the obvious case: If the path doesn't end in a .desktop
            # extension, this isn't a desktop file.
            return False

        file_path = os.path.abspath(file_path)
        for app_dir in self.application_dirs:
            if file_path.startswith(app_dir):
                return True
        return False

    def get_application_for_package(self, pkg):
        desktop_files = []
        rated_applications = []

        for installed_file in pkg.installed_files:
            if self._file_is_application(installed_file):
                desktop_files.append(installed_file)

        for desktop_file in desktop_files:
            try:
                application = Gio.DesktopAppInfo.new_from_filename(
                    desktop_file)
                application.set_desktop_env(self.current_desktop)
            except Exception as e:
                logging.warning("Error loading .desktop file %s: %s" %
                                (desktop_file, e))
                continue
            score = self._rate_application_for_package(application, pkg)
            if score > 0:
                rated_applications.append((score, application))

        rated_applications.sort(key=lambda app: app[0], reverse=True)
        if len(rated_applications) > 0:
            return rated_applications[0][1]
        else:
            return None

    def _make_single_upgrade(self,cache,pkg_list,output_path):
        for pkg in pkg_list:
            zh_name = ''
            base_info = {}
            output_json = {}
            output_config_name = output_path + pkg + '.json'
            
            pkg_cache = cache[pkg]

            if pkg_cache.is_installed:
                app = self.get_application_for_package(pkg_cache)
                if app is not None:
                    zh_name = app.get_display_name()
            
            pkgs_json = self._make_pkg_info_json(cache,[pkg])
            en_name = getattr(pkg_cache.candidate, "summary", '')
            description_str = getattr(pkg_cache.candidate, "description", '')

            base_info.update({"package":pkg})

            base_info.update({"cur_version":getattr(pkg_cache.installed, "version", '')})
            base_info.update({"new_version":getattr(pkg_cache.candidate, "version", '')})
            base_info.update({"name":{"zh_CN":zh_name,"en_US":en_name}})
            base_info.update({"description":{"zh_CN":description_str,"en_US":description_str}})
            base_info.update({"icon":''})

            output_json.update(base_info)
            if pkg_cache.is_installed:
                output_json.update({"upgrade_list":pkgs_json})
                output_json.update({"install_list":{}})
            else:
                output_json.update({"upgrade_list":{}})
                output_json.update({"install_list":pkgs_json})
            
            with open(output_config_name, 'w', encoding='utf-8') as f:
                json.dump(output_json, f, ensure_ascii=False, indent=4)                
            logging.info("Generate Jsonfile(%s) to complete... ",output_config_name)

    def _make_distupgrade(self,cache):
        pkgs_upgrade = []
        pkgs_install = []
        if cache.get_changes():
            cache.clear()
        cache._depcache.upgrade(True)

        for pkg in cache:
            try:
                if pkg.marked_install:
                    pkgs_install.append(pkg.name)
                elif pkg.marked_upgrade:
                    pkgs_upgrade.append(pkg.name)
            except KeyError:
                pass
        return pkgs_install,pkgs_upgrade


    
    def _backup_current_serverid(self):
        import shutil

        SERVERIDPATH = "/var/lib/kylin-software-properties/config/serverID.conf"
        BACKUPPATH   = "/var/lib/kylin-system-updater/json/"

        try:
            if os.path.isfile(SERVERIDPATH):
                dst = shutil.copy(SERVERIDPATH, BACKUPPATH)
                logging.info("Backup serverID conf completed: %s...",dst)
            else:
                logging.info("ServerID conf is not exists...")
        except Exception as e:
            logging.error("%s",e)

    def update_kylin(self,cache,important_data,is_openkylin = False):
        system_install = []
        system_upgrade = []

        if is_openkylin == True:
            system_install,system_upgrade = self._make_distupgrade(cache)
        else:
            if cache.get_changes():
                cache.clear()
            for pkg in cache:
                if pkg.is_upgradable and pkg.is_installed:
                    system_upgrade.append(pkg.name)

        logging.info("System all upgradeable packages:upgrade:%d install:%d ",len(system_upgrade),len(system_install))

        group_list,single_pkgs = self._make_important_list(cache,system_upgrade,important_data)

        make_empty_dir(self.OUTPUT_JSON_PATH)

        if not group_list and not single_pkgs:
            return

        self.push_singles = single_pkgs

        self._make_single_upgrade(cache,single_pkgs,self.OUTPUT_JSON_PATH)

        self._make_groups_upgrade(cache,group_list,is_openkylin,\
                        system_install,system_upgrade,self.OUTPUT_JSON_PATH,get_config_patch())

        self._backup_current_serverid()
