#!/usr/bin/env python3
# -*- coding: utf-8 -*-

#
# Copyright (c) 2023 Huawei Device Co., Ltd.
# 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 argparse
import sys

from enum import Enum
from resources.global_var import CURRENT_ARGS_DIR
from resources.global_var import CURRENT_BUILD_ARGS
from resources.global_var import DEFAULT_BUILD_ARGS
from resources.global_var import CURRENT_SET_ARGS
from resources.global_var import DEFAULT_SET_ARGS
from resources.global_var import CURRENT_CLEAN_ARGS
from resources.global_var import DEFAULT_CLEAN_ARGS
from resources.global_var import DEFAULT_ENV_ARGS
from resources.global_var import CURRENT_ENV_ARGS
from resources.global_var import DEFAULT_TOOL_ARGS
from resources.global_var import CURRENT_TOOL_ARGS
from resources.global_var import DEFAULT_INDEP_BUILD_ARGS
from resources.global_var import CURRENT_INDEP_BUILD_ARGS
from resources.global_var import DEFAULT_INSTALL_ARGS
from resources.global_var import CURRENT_INSTALL_ARGS
from resources.global_var import DEFAULT_PACKAGE_ARGS
from resources.global_var import CURRENT_PACKAGE_ARGS
from resources.global_var import DEFAULT_PUBLISH_ARGS
from resources.global_var import CURRENT_PUBLISH_ARGS
from resources.global_var import DEFAULT_UPDATE_ARGS
from resources.global_var import CURRENT_UPDATE_ARGS
from resources.global_var import DEFAULT_PUSH_ARGS
from resources.global_var import CURRENT_PUSH_ARGS
from resources.global_var import ARGS_DIR
from exceptions.ohos_exception import OHOSException
from util.log_util import LogUtil
from util.io_util import IoUtil
from util.type_check_util import TypeCheckUtil
from resolver.args_factory import ArgsFactory
from containers.status import throw_exception


class ModuleType(Enum):

    BUILD = 0
    SET = 1
    ENV = 2
    CLEAN = 3
    TOOL = 4
    INDEP_BUILD = 5
    INSTALL = 6
    PACKAGE = 7
    PUBLISH = 8
    UPDATE = 9
    PUSH = 10


class ArgType():

    NONE = 0
    BOOL = 1
    INT = 2
    STR = 3
    LIST = 4
    DICT = 5
    SUBPARSERS = 6

    @staticmethod
    def get_type(value: str):
        if value == 'bool':
            return ArgType.BOOL
        elif value == "int":
            return ArgType.INT
        elif value == 'str':
            return ArgType.STR
        elif value == "list":
            return ArgType.LIST
        elif value == 'dict':
            return ArgType.DICT
        elif value == 'subparsers':
            return ArgType.SUBPARSERS
        else:
            return ArgType.NONE


class BuildPhase():

    NONE = 0
    PRE_BUILD = 1
    PRE_LOAD = 2
    LOAD = 3
    PRE_TARGET_GENERATE = 4
    TARGET_GENERATE = 5
    POST_TARGET_GENERATE = 6
    PRE_TARGET_COMPILATION = 7
    TARGET_COMPILATION = 8
    POST_TARGET_COMPILATION = 9
    POST_BUILD = 10
    HPM_DOWNLOAD = 11
    INDEP_COMPILATION = 12

    @staticmethod
    def get_type(value: str):
        if value == 'prebuild':
            return BuildPhase.PRE_BUILD
        elif value == "preload":
            return BuildPhase.PRE_LOAD
        elif value == 'load':
            return BuildPhase.LOAD
        elif value == "preTargetGenerate":
            return BuildPhase.PRE_TARGET_GENERATE
        elif value == 'targetGenerate':
            return BuildPhase.TARGET_GENERATE
        elif value == 'postTargetGenerate':
            return BuildPhase.POST_TARGET_GENERATE
        elif value == 'preTargetCompilation':
            return BuildPhase.PRE_TARGET_COMPILATION
        elif value == 'targetCompilation':
            return BuildPhase.TARGET_COMPILATION
        elif value == 'postTargetCompilation':
            return BuildPhase.POST_TARGET_COMPILATION
        elif value == 'postbuild':
            return BuildPhase.POST_BUILD
        elif value == 'hpmDownload':
            return BuildPhase.HPM_DOWNLOAD
        elif value == 'indepCompilation':
            return BuildPhase.INDEP_COMPILATION
        else:
            return BuildPhase.NONE


class CleanPhase():

    REGULAR = 0
    DEEP = 1
    NONE = 2

    @staticmethod
    def get_type(value: str):
        if value == 'regular':
            return CleanPhase.REGULAR
        elif value == 'deep':
            return CleanPhase.DEEP
        else:
            return CleanPhase.NONE


class Arg():

    def __init__(self, name: str, helps: str, phase: str,
                 attribute: dict, argtype: ArgType, value,
                 resolve_function: str):
        self._arg_name = name
        self._arg_help = helps
        self._arg_phase = phase
        self._arg_attribute = attribute
        self._arg_type = argtype
        self._arg_value = value
        self._resolve_function = resolve_function
    
    def __str__(self):
        #return f"Arg(name={self._arg_name}, help={self._arg_help}, phase={self._arg_phase}, attribute={self._arg_attribute}, type={self._arg_type}, value={self._arg_value}, resolve_function={self._resolve_function})"
        return f"{self._arg_name}={self._arg_value}"
    
    def __repr__(self):
        return self.__str__()

    @property
    def arg_name(self):
        return self._arg_name

    @property
    def arg_value(self):
        return self._arg_value

    @property
    def arg_help(self):
        return self._arg_help

    @property
    def arg_attribute(self):
        return self._arg_attribute

    @property
    def arg_phase(self):
        return self._arg_phase

    @property
    def arg_type(self):
        return self._arg_type

    @property
    def resolve_function(self):
        return self._resolve_function

    @staticmethod
    @throw_exception
    def create_instance_by_dict(data: dict):
        arg_name = str(data['arg_name']).replace("-", "_")[2:]
        arg_help = str(data['arg_help'])
        arg_phase = data['arg_phase']
        if (isinstance(arg_phase, list)):
            arg_phase = [BuildPhase.get_type(str(phase_item)) for phase_item in arg_phase]
        else:
            arg_phase = BuildPhase.get_type(str(data['arg_phase']))
        arg_attibute = dict(data['arg_attribute'])
        arg_type = ArgType.get_type(data['arg_type'])
        arg_value = ''
        if arg_type == ArgType.BOOL:
            arg_value = data['argDefault']
        elif arg_type == ArgType.INT:
            arg_value = int(data['argDefault'])
        elif arg_type == ArgType.STR:
            arg_value = data['argDefault']
        elif arg_type == ArgType.LIST:
            arg_value = list(data['argDefault'])
        elif arg_type == ArgType.DICT:
            arg_value = dict(data['argDefault'])
        elif arg_type == ArgType.SUBPARSERS:
            arg_value = list(data['argDefault'])
        else:
            raise OHOSException('Unknown arg type "{}" for arg "{}"'.format(
                arg_type, arg_name), "0003")
        resolve_function = data['resolve_function']
        return Arg(arg_name, arg_help, arg_phase, arg_attibute, arg_type, arg_value, resolve_function)

    @staticmethod
    def get_help(module_type: ModuleType) -> str:
        parser = argparse.ArgumentParser()
        all_args = Arg.read_args_file(module_type)

        for arg in all_args.values():
            arg = dict(arg)
            ArgsFactory.genetic_add_option(parser, arg)
        if module_type == ModuleType.INDEP_BUILD:
            ArgsFactory.genetic_add_option(parser, {'arg_name': '-i', 'argDefault': '',
                                                    'arg_help': "Default:''. Help:specify independent build, run 'hb build {part_name} -i'",
                                                    'arg_phase': 'prebuild', 'arg_type': 'str', 'arg_attribute': {},
                                                    'resolve_function': '', 'testFunction': ''})
            ArgsFactory.genetic_add_option(parser, {'arg_name': '-t', 'argDefault': '',
                                                    'arg_help': "Default:''. Help:specify build test module, run 'hb build {part_name} -t'",
                                                    'arg_phase': 'prebuild', 'arg_type': 'str', 'arg_attribute': {},
                                                    'resolve_function': '', 'testFunction': ''})

        parser.usage = 'hb {} [option]'.format(module_type.name.lower())
        parser.parse_known_args(sys.argv[2:])

        return parser.format_help()

    @staticmethod
    def parse_all_args(module_type: ModuleType) -> dict:
        args_dict = {}
        parser = argparse.ArgumentParser()
        all_args = Arg.read_args_file(module_type)

        for arg in all_args.values():
            arg = dict(arg)
            ArgsFactory.genetic_add_option(parser, arg)
            oh_arg = Arg.create_instance_by_dict(arg)
            args_dict[oh_arg.arg_name] = oh_arg

        parser.usage = 'hb {} [option]'.format(module_type.name.lower())
        parser_args = parser.parse_known_args(sys.argv[2:])

        for oh_arg in args_dict.values():
            if isinstance(oh_arg, Arg):
                assigned_value = parser_args[0].__dict__[oh_arg.arg_name]
                if oh_arg.arg_type == ArgType.LIST:
                    convert_assigned_value = TypeCheckUtil.tile_list(assigned_value)
                    convert_assigned_value = list(set(convert_assigned_value))
                elif oh_arg.arg_type == ArgType.SUBPARSERS:
                    convert_assigned_value = TypeCheckUtil.tile_list(assigned_value)
                    if len(convert_assigned_value):
                        convert_assigned_value = list(set(convert_assigned_value))
                        convert_assigned_value.extend(parser_args[1])
                        convert_assigned_value.sort(key=sys.argv[2:].index)
                elif oh_arg.arg_type == ArgType.BOOL:
                    if str(assigned_value).lower() == 'false':
                        convert_assigned_value = False
                    elif str(assigned_value).lower() == 'true' or assigned_value is None:
                        convert_assigned_value = True
                else:
                    convert_assigned_value = assigned_value

                if oh_arg.arg_attribute.get('deprecated', None) and oh_arg.arg_value != convert_assigned_value:
                    LogUtil.hb_warning(
                        'compile option "{}" will be deprecated, \
                            please consider use other options'.format(oh_arg.arg_name))
                oh_arg.arg_value = convert_assigned_value
                Arg.write_args_file(
                    oh_arg.arg_name, oh_arg.arg_value, module_type)

        return args_dict
        
    @arg_value.setter
    def arg_value(self, value):
        self._arg_value = value

    @resolve_function.setter
    def resolve_function(self, value):
        self._resolve_function = value

    @staticmethod
    @throw_exception
    def write_args_file(key: str, value, module_type: ModuleType):
        args_file_path = ''
        if module_type == ModuleType.BUILD:
            args_file_path = CURRENT_BUILD_ARGS
        elif module_type == ModuleType.SET:
            args_file_path = CURRENT_SET_ARGS
        elif module_type == ModuleType.CLEAN:
            args_file_path = CURRENT_CLEAN_ARGS
        elif module_type == ModuleType.ENV:
            args_file_path = CURRENT_ENV_ARGS
        elif module_type == ModuleType.TOOL:
            args_file_path = CURRENT_TOOL_ARGS
        elif module_type == ModuleType.INDEP_BUILD:
            args_file_path = CURRENT_INDEP_BUILD_ARGS
        elif module_type == ModuleType.INSTALL:
            args_file_path = CURRENT_INSTALL_ARGS
        elif module_type == ModuleType.PACKAGE:
            args_file_path = CURRENT_PACKAGE_ARGS
        elif module_type == ModuleType.PUBLISH:
            args_file_path = CURRENT_PUBLISH_ARGS
        elif module_type == ModuleType.UPDATE:
            args_file_path = CURRENT_UPDATE_ARGS
        elif module_type == ModuleType.PUSH:
            args_file_path = CURRENT_PUSH_ARGS
        else:
            raise OHOSException(
                'You are trying to write args file, but there is no corresponding module "{}" args file'
                .format(module_type), "0002")
        args_file = Arg.read_args_file(module_type)
        args_file[key]["argDefault"] = value
        IoUtil.dump_json_file(args_file_path, args_file)

    @staticmethod
    @throw_exception
    def read_args_file(module_type: ModuleType):
        args_file_path = ''
        default_file_path = ''
        if module_type == ModuleType.BUILD:
            args_file_path = CURRENT_BUILD_ARGS
            default_file_path = DEFAULT_BUILD_ARGS
        elif module_type == ModuleType.SET:
            args_file_path = CURRENT_SET_ARGS
            default_file_path = DEFAULT_SET_ARGS
        elif module_type == ModuleType.CLEAN:
            args_file_path = CURRENT_CLEAN_ARGS
            default_file_path = DEFAULT_CLEAN_ARGS
        elif module_type == ModuleType.ENV:
            args_file_path = CURRENT_ENV_ARGS
            default_file_path = DEFAULT_ENV_ARGS
        elif module_type == ModuleType.TOOL:
            args_file_path = CURRENT_TOOL_ARGS
            default_file_path = DEFAULT_TOOL_ARGS
        elif module_type == ModuleType.INDEP_BUILD:
            args_file_path = CURRENT_INDEP_BUILD_ARGS
            default_file_path = DEFAULT_INDEP_BUILD_ARGS
        elif module_type == ModuleType.INSTALL:
            args_file_path = CURRENT_INSTALL_ARGS
            default_file_path = DEFAULT_INSTALL_ARGS
        elif module_type == ModuleType.PACKAGE:
            args_file_path = CURRENT_PACKAGE_ARGS
            default_file_path = DEFAULT_PACKAGE_ARGS
        elif module_type == ModuleType.PUBLISH:
            args_file_path = CURRENT_PUBLISH_ARGS
            default_file_path = DEFAULT_PUBLISH_ARGS
        elif module_type == ModuleType.UPDATE:
            args_file_path = CURRENT_UPDATE_ARGS
            default_file_path = DEFAULT_UPDATE_ARGS
        elif module_type == ModuleType.PUSH:
            args_file_path = CURRENT_PUSH_ARGS
            default_file_path = DEFAULT_PUSH_ARGS
        else:
            raise OHOSException(
                'You are trying to read args file, but there is no corresponding module "{}" args file'
                .format(module_type.name.lower()), "0018")
        if not os.path.exists(CURRENT_ARGS_DIR):
            os.makedirs(CURRENT_ARGS_DIR, exist_ok=True)
        if not os.path.exists(args_file_path):
            IoUtil.copy_file(src=default_file_path, dst=args_file_path)
        return IoUtil.read_json_file(args_file_path)

    @staticmethod
    def clean_args_file():
        if os.path.exists(CURRENT_ARGS_DIR):
            for file in os.listdir(CURRENT_ARGS_DIR):
                if file.endswith('.json') and os.path.exists(os.path.join(CURRENT_ARGS_DIR, file)):
                    os.remove(os.path.join(CURRENT_ARGS_DIR, file))

    @staticmethod
    def clean_args_file_by_type(module_type: ModuleType):
        args_file_path = ''
        if module_type == ModuleType.INSTALL:
            args_file_path = CURRENT_INSTALL_ARGS
        elif module_type == ModuleType.PACKAGE:
            args_file_path = CURRENT_PACKAGE_ARGS
        elif module_type == ModuleType.UPDATE:
            args_file_path = CURRENT_UPDATE_ARGS
        elif module_type == ModuleType.ENV:
            args_file_path = CURRENT_ENV_ARGS
        elif module_type == ModuleType.INDEP_BUILD:
            args_file_path = CURRENT_INDEP_BUILD_ARGS
        elif module_type == ModuleType.PUSH:
            args_file_path = CURRENT_PUSH_ARGS
        if os.path.exists(args_file_path):
            os.remove(args_file_path)