#!/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 sys
import os
import time
import platform
import threading
from enum import Enum

from containers.status import throw_exception
from exceptions.ohos_exception import OHOSException
from services.interface.build_file_generator_interface import BuildFileGeneratorInterface
from resources.config import Config
from containers.arg import Arg, ModuleType
from util.system_util import SystemUtil
from util.io_util import IoUtil
from util.log_util import LogUtil


class CMDTYPE(Enum):
    GEN = 1
    PATH = 2
    DESC = 3
    LS = 4
    REFS = 5
    FORMAT = 6
    CLEAN = 7


class Gn(BuildFileGeneratorInterface):

    def __init__(self):
        super().__init__()
        self.config = Config()
        self._regist_gn_path()

    def run(self):
        self.execute_gn_cmd(CMDTYPE.GEN)

    @throw_exception
    def execute_gn_cmd(self, cmd_type: int, **kwargs):
        if cmd_type == CMDTYPE.GEN:
            return self._execute_gn_gen_cmd()
        elif cmd_type == CMDTYPE.PATH:
            return self._execute_gn_path_cmd(**kwargs)
        elif cmd_type == CMDTYPE.DESC:
            return self._execute_gn_desc_cmd(**kwargs)
        elif cmd_type == CMDTYPE.LS:
            return self._execute_gn_ls_cmd(**kwargs)
        elif cmd_type == CMDTYPE.REFS:
            return self._execute_gn_refs_cmd(**kwargs)
        elif cmd_type == CMDTYPE.FORMAT:
            return self._execute_gn_format_cmd(**kwargs)
        elif cmd_type == CMDTYPE.CLEAN:
            return self._execute_gn_clean_cmd(**kwargs)
        else:
            raise OHOSException(
                'You are tring to use an unsupported gn cmd type "{}"'.format(cmd_type), '3001')

    '''Description: Get gn excutable path and regist it
    @parameter: none
    @return: Status
    '''

    @throw_exception
    def _regist_gn_path(self):
        if sys.platform == "linux" and platform.machine().lower() == "aarch64":
            gn_path = os.path.join(self.config.root_path, 'prebuilts/build-tools/{}-aarch64/bin/gn'
                .format(sys.platform))
        else:
            gn_path = os.path.join(self.config.root_path, 'prebuilts/build-tools/{}-x86/bin/gn'
                .format(sys.platform))
        if os.path.exists(gn_path):
            self.exec = gn_path
        else:
            raise OHOSException(
                'There is no gn executable file at {}'.format(gn_path), '0001')

    '''Description: Execute 'gn gen' command using registed args
    @parameter: kwargs TBD
    @return: None
    '''

    @throw_exception
    def _execute_gn_gen_cmd(self, **kwargs):
        gn_gen_cmd = [self.exec, 'gen', '--json=gn_log.json',
                      '--args={}'.format(' '.join(self._convert_args())),
                      self.config.out_path] + self._convert_flags()
        if self.config.os_level == 'mini' or self.config.os_level == 'small':
            gn_gen_cmd.append(f'--script-executable={sys.executable}')
        LogUtil.write_log(self.config.log_path, 'Excuting gn command: {} {} --args="{}" {}'.format(
            self.exec, 'gen',
            ' '.join(self._convert_args()).replace('"', "\\\""),
            ' '.join(gn_gen_cmd[3:])),
            'info')
        if self.config.log_mode == 'silent':
            def loading_animation(done_event):
                frames = ["|", "/", "-", "\\"]
                circle_times = 0
                while not done_event.is_set():
                    sys.stdout.write("\r" + "[OHOS INFO] GN parsing... " + frames[circle_times % len(frames)])
                    sys.stdout.flush()
                    time.sleep(0.1)
                    circle_times += 1

            def task(done_event):
                SystemUtil.exec_command(gn_gen_cmd, self.config.log_path, log_mode=self.config.log_mode)
                done_event.set()
                sys.stdout.write("\n" + "[OHOS INFO] GN parsing Done\n")
            done_event = threading.Event()
            animation_thread = threading.Thread(target=loading_animation, args=(done_event,))
            animation_thread.start()
            task(done_event)
            animation_thread.join()
        else:
            SystemUtil.exec_command(gn_gen_cmd, self.config.log_path)

    '''Description: Execute 'gn path' command using registed args
    @parameter: kwargs TBD
    @return: None
    '''

    @throw_exception
    def _execute_gn_path_cmd(self, **kwargs):
        out_dir = kwargs.get("out_dir")
        default_options = ['--all']
        args_file = Arg.read_args_file(ModuleType.TOOL)['path']
        if (os.path.exists(os.path.join(out_dir, "args.gn"))):
            gn_path_cmd = [self.exec, 'path', out_dir]
            for arg in kwargs.get('args_list'):
                if arg.startswith('-'):
                    self._check_options_validity(arg, args_file)
                gn_path_cmd.append(arg)
            gn_path_cmd.extend(default_options)
            sort_index = gn_path_cmd.index
            gn_path_cmd = list(set(gn_path_cmd))
            gn_path_cmd.sort(key=sort_index)
            SystemUtil.exec_command(gn_path_cmd)
        else:
            raise OHOSException(
                '"{}" Not a build directory.'.format(out_dir), '3004')

    '''Description: Execute 'gn desc' command using registed args
    @parameter: kwargs TBD
    @return: None
    '''

    @throw_exception
    def _execute_gn_desc_cmd(self, **kwargs):
        out_dir = kwargs.get("out_dir")
        default_options = ['--tree', '--blame']
        args_file = Arg.read_args_file(ModuleType.TOOL)['desc']
        if (os.path.exists(os.path.join(out_dir, "args.gn"))):
            gn_desc_cmd = [self.exec, 'desc', out_dir]
            for arg in kwargs.get('args_list'):
                if arg.startswith('-'):
                    self._check_options_validity(arg, args_file)
                gn_desc_cmd.append(arg)
            gn_desc_cmd.extend(default_options)
            sort_index = gn_desc_cmd.index
            gn_desc_cmd = list(set(gn_desc_cmd))
            gn_desc_cmd.sort(key=sort_index)
            SystemUtil.exec_command(gn_desc_cmd)
        else:
            raise OHOSException(
                '"{}" Not a build directory.'.format(out_dir), '3004')

    '''Description: Execute 'gn ls' command using registed args
    @parameter: kwargs TBD
    @return: None
    '''

    @throw_exception
    def _execute_gn_ls_cmd(self, **kwargs):
        out_dir = kwargs.get("out_dir")
        args_file = Arg.read_args_file(ModuleType.TOOL)['ls']
        if (os.path.exists(os.path.join(out_dir, "args.gn"))):
            gn_ls_cmd = [self.exec, 'ls', out_dir]
            for arg in kwargs.get('args_list'):
                if arg.startswith('-'):
                    self._check_options_validity(arg, args_file)
                gn_ls_cmd.append(arg)
            SystemUtil.exec_command(gn_ls_cmd)
        else:
            raise OHOSException(
                '"{}" Not a build directory.'.format(out_dir), '3004')

    '''Description: Execute 'gn refs' command using registed args
    @parameter: kwargs TBD
    @return: None
    '''

    @throw_exception
    def _execute_gn_refs_cmd(self, **kwargs):
        out_dir = kwargs.get("out_dir")
        args_file = Arg.read_args_file(ModuleType.TOOL)['refs']
        if (os.path.exists(os.path.join(out_dir, "args.gn"))):
            gn_refs_cmd = [self.exec, 'refs', out_dir]
            for arg in kwargs.get('args_list'):
                if arg.startswith('-'):
                    self._check_options_validity(arg, args_file)
                gn_refs_cmd.append(arg)
            SystemUtil.exec_command(gn_refs_cmd)
        else:
            raise OHOSException(
                '"{}" Not a build directory.'.format(out_dir), '3004')

    '''Description: Execute 'gn format' command using registed args
    @parameter: kwargs TBD
    @return: None
    '''

    @throw_exception
    def _execute_gn_format_cmd(self, **kwargs):
        gn_format_cmd = [self.exec, 'format']
        args_file = Arg.read_args_file(ModuleType.TOOL)['format']
        for arg in kwargs.get("args_list"):
            if (arg.endswith('.gn')):
                if (os.path.exists(arg)):
                    gn_format_cmd.append(arg)
                else:
                    raise OHOSException(
                        "ERROR Couldn't read '{}'".format(arg), '3005')
            else:
                if arg.startswith('-'):
                    self._check_options_validity(arg, args_file)
                gn_format_cmd.append(arg)
        SystemUtil.exec_command(gn_format_cmd)

    '''Description: Execute 'gn clean' command using registed args
    @parameter: kwargs TBD
    @return: None
    '''

    @throw_exception
    def _execute_gn_clean_cmd(self, **kwargs):
        out_dir = kwargs.get("out_dir")
        if (os.path.exists(os.path.join(out_dir, "args.gn"))):
            gn_clean_cmd = [self.exec, 'clean', out_dir]
            SystemUtil.exec_command(gn_clean_cmd)
        else:
            raise OHOSException('"{}" Not a build directory.'
                                'Usage: "gn clean <out_dir>"'.format(out_dir), '3004')

    '''Description: Convert all registed args into a list
    @parameter: none
    @return: list of all registed args
    '''

    def _convert_args(self) -> list:
        args_list = []

        for key, value in self.args_dict.items():
            if isinstance(value, bool):
                args_list.append('{}={}'.format(key, str(value).lower()))

            elif isinstance(value, str):
                args_list.append('{}="{}"'.format(key, value))

            elif isinstance(value, int):
                args_list.append('{}={}'.format(key, value))

            elif isinstance(value, list):
                args_list.append('{}="{}"'.format(key, "&&".join(value)))

        return args_list

    '''Description: Convert all registed flags into a list
    @parameter: none
    @return: list of all registed flags
    '''

    def _convert_flags(self) -> list:
        flags_list = []

        for key, value in self.flags_dict.items():
            if key == 'gn_flags' and isinstance(value, list):
                flags_list += value
            elif value == '':
                flags_list.append('{}'.format(key))
            else:
                flags_list.append('{}={}'.format(key, str(value)).lower())

        return flags_list

    '''Description: Option validity check
    @parameter: "option": Option to be checked
                "args_file": Option config file
    @return: Inspection result(True|False)
    '''

    def _check_options_validity(self, option: str, args_file: dict):
        support_sub_options = args_file.get(
            "arg_attribute").get("support_sub_options")
        option_name = option.lstrip('-')
        option_value = ""
        if '=' in option:
            option_name, option_value = option.lstrip('-').split('=')
        if option_name in support_sub_options:
            sub_optional_list = support_sub_options.get(
                option_name).get("arg_attribute").get("optional")
            if sub_optional_list and option_value not in sub_optional_list:
                if not len(option_value):
                    raise OHOSException('ERROR argument "--{}": Invalid choice "{}". '
                                        'choose from {}'.format(option_name, option_value, sub_optional_list), '3006')
                else:
                    raise OHOSException('ERROR argument "--{}": Invalid choice "{}". '
                                        'choose from {}'.format(option_name, option_value, sub_optional_list), '3003')
        else:
            raise OHOSException('ERROR argument "{}": Invalid choice "{}". '
                                'choose from {}'.format(args_file.get("arg_name"),
                                                        option, list(support_sub_options.keys())), '3003')