# Copyright (c) 2023, AgiBot Inc.
# All rights reserved.

from dataclasses import dataclass
from jinja2 import Template

from aimrt_cli.generator import GeneratorBase


@dataclass
class BuildModeExpandInfo:
    build_option: str
    items: list


@dataclass
class DefinitionExpandInfo:
    name: str
    status: str


@dataclass
class CustomizeBuildOptionsExpandInfo:
    name: str
    description: str
    status: str


@dataclass
class DependsStdModuleExpandInfo:
    name: str
    std_depend_name: str
    git_version: str
    git_repository: str
    import_options: dict = None


@dataclass
class WorkspaceExpandInfo:
    depends_std_modules: list
    aimrt_import_options: list
    definitions: list
    build_options: list
    protocols_build_modes: list
    modules_build_modes: list
    pkgs_build_modes: list


aimrt_core_options = [
    'AIMRT_BUILD_TESTS',
    'AIMRT_BUILD_EXAMPLES',
    'AIMRT_BUILD_DOCUMENT',
    'AIMRT_BUILD_RUNTIME',
    'AIMRT_BUILD_CLI_TOOLS',
    'AIMRT_USE_FMT_LIB',
    'AIMRT_BUILD_WITH_PROTOBUF',
    'AIMRT_USE_LOCAL_PROTOC_COMPILER',
    'AIMRT_USE_PROTOC_PYTHON_PLUGIN',
    'AIMRT_BUILD_WITH_ROS2',
    'AIMRT_BUILD_NET_PLUGIN',
    'AIMRT_BUILD_ROS2_PLUGIN',
    'AIMRT_BUILD_MQTT_PLUGIN',
    'AIMRT_BUILD_ZENOH_PLUGIN',
    'AIMRT_BUILD_ICEORYX_PLUGIN',
    'AIMRT_BUILD_RECORD_PLAYBACK_PLUGIN',
    'AIMRT_BUILD_TIME_MANIPULATOR_PLUGIN',
    'AIMRT_BUILD_PARAMETER_PLUGIN',
    'AIMRT_BUILD_LOG_CONTROL_PLUGIN',
    'AIMRT_BUILD_OPENTELEMETRY_PLUGIN',
]

workspace_template_infos = [
    {
        'template_name': 'src_cmakelists',
        'template_package': 'templates.workspace',
        'template_file': 'src_CMakeLists.txt.jinja2',
        'output_path': "./src",
        'output_file': "CMakeLists.txt",
    },
    {
        'template_name': 'ws_cmakelists',
        'template_package': 'templates.workspace',
        'template_file': 'ws_CMakeLists.txt.jinja2',
        'output_path': "./",
        'output_file': "CMakeLists.txt",
    },
    {
        'template_name': 'build_sh',
        'template_package': 'templates.workspace',
        'template_file': 'template.sh.jinja2',
        'output_path': "./",
        'output_file': "build.sh",
    },
    {
        'template_name': 'test_sh',
        'template_package': 'templates.workspace',
        'template_file': 'template.sh.jinja2',
        'output_path': "./",
        'output_file': "test.sh",
    },
    {
        'template_name': 'get_depends_cmake',
        'template_package': 'templates.workspace.cmake',
        'template_file': 'get_depends_template.cmake.jinja2',
        'output_path': "./cmake",
        'output_file': Template("Get{{ name }}.cmake"),
    },
    {
        'template_name': 'get_aimrt_cmake',
        'template_package': 'templates.workspace.cmake',
        'template_file': 'GetAimRT.cmake.jinja2',
        'output_path': "./cmake",
        'output_file': "GetAimRT.cmake",
    },
]

static_ws_template_infos = [
    {
        'template_name': 'get_gtest_cmake',
        'template_package': 'templates.workspace.cmake',
        'template_file': 'GetGTest.cmake.jinja2',
        'output_path': "./cmake",
        'output_file': "GetGTest.cmake",
    },
    {
        'template_name': 'get_namespace_tool_cmake',
        'template_package': 'templates.workspace.cmake',
        'template_file': 'NamespaceTool.cmake.jinja2',
        'output_path': "./cmake",
        'output_file': "NamespaceTool.cmake",
    },
    {
        'template_name': 'clang_format',
        'template_package': 'templates.workspace',
        'template_file': 'ws_clang_format.jinja2',
        'output_path': "./",
        'output_file': ".clang-format",
    },
    {
        'template_name': 'cmake_format',
        'template_package': 'templates.workspace',
        'template_file': 'ws_cmake_format.py.jinja2',
        'output_path': "./",
        'output_file': ".cmake-format.py",
    },
    {
        'template_name': 'format_shell',
        'template_package': 'templates.workspace',
        'template_file': 'ws_format.sh.jinja2',
        'output_path': "./",
        'output_file': "format.sh",
    },
    {
        'template_name': 'gitignore',
        'template_package': 'templates.workspace',
        'template_file': 'ws_gitignore.jinja2',
        'output_path': "./",
        'output_file': ".gitignore",
    },
    {
        'template_name': 'pycodestyle',
        'template_package': 'templates.workspace',
        'template_file': 'ws_pycodestyle.jinja2',
        'output_path': "./",
        'output_file': ".pycodestyle",
    },
    {
        'template_name': 'readme',
        'template_package': 'templates.workspace',
        'template_file': 'ws_readme.jinja2',
        'output_path': "./",
        'output_file': "README.md",
    },
]


class WorkspaceGenerator(GeneratorBase):
    def __init__(self, *, base_info, depends_std_modules, deploy_modes, output_dir=None):
        super().__init__()
        self.base_info_ = base_info
        self.depends_std_modules_ = depends_std_modules
        self.module_depends_ = []
        if depends_std_modules:
            for depend_module in depends_std_modules:
                self.module_depends_.append(depend_module['name'].replace('-', '_'))
        self.deploy_modes_ = deploy_modes
        if 'project_name' not in self.base_info_.keys() and self.base_info_['project_name'] is None:
            raise Exception("You need to designate your project name in your yaml file!")
        else:
            self.project_name_ = self.base_info_['project_name'].replace('-', '_')
        self.build_mode_tags_ = self.base_info_['build_mode_tags']
        self.aimrt_import_options_ = self.base_info_['aimrt_import_options']
        self.test_marco_ = self.project_name_.upper() + '_BUILD_TESTS'
        self.expand_info_ = None
        if output_dir is None:
            self.output_dir_ = './' + self.project_name_
        else:
            self.output_dir_ = output_dir

    def parse(self, *, build_modes=None):
        depends_std_modules, depend_import_options = self.parse_depends_std_modules()
        aimrt_import_options, definitions = self.parse_import_options(depend_import_options)
        self.expand_info_ = WorkspaceExpandInfo(
            depends_std_modules=depends_std_modules,
            aimrt_import_options=aimrt_import_options,
            definitions=definitions,
            build_options=self.parse_customize_build_options(),
            protocols_build_modes=self.parse_build_mode(build_modes['protocol']),
            modules_build_modes=self.parse_build_mode(build_modes['module']),
            pkgs_build_modes=self.parse_build_mode(build_modes['pkg'])
        )

    def parse_import_options(self, depend_import_options):
        # parse the aimrt import options.
        import_options = []
        definitions = []
        if self.aimrt_import_options_:
            for aimrt_import_option, status in self.aimrt_import_options_.items():
                if aimrt_import_option in aimrt_core_options:
                    import_options.append(DefinitionExpandInfo(name=aimrt_import_option, status=status))
                else:
                    raise Exception("The aimrt import option: " + aimrt_import_option +
                                    " is not defined in aimrt, Please Check it!")
        definitions.extend(depend_import_options)
        # default OFF for build.sh
        definitions.append(DefinitionExpandInfo(name=self.test_marco_, status='OFF'))
        return import_options, definitions

    def parse_customize_build_options(self):
        # parse the build options.
        customize_build_options = []
        if self.build_mode_tags_:
            for build_mode_tag in self.build_mode_tags_:
                option_name = self.project_name_.upper() + '_' + build_mode_tag.upper()
                option_description = "Build " + build_mode_tag.lower()
                option_status = "OFF"
                customize_build_options.append(CustomizeBuildOptionsExpandInfo(
                    name=option_name,
                    description=option_description,
                    status=option_status))
        return customize_build_options

    @staticmethod
    def check_depend_name(git_repository):
        split_names = git_repository.split('/')
        name = split_names[-1].replace('.git', '')
        return name

    def parse_depends_std_modules(self):
        # parse depends std modules.
        depends_std_modules = []
        import_options = []
        if self.depends_std_modules_:
            for depends_std_module in self.depends_std_modules_:
                depend_module_name = self.check_depend_name(depends_std_module['git_repository'])
                split_names = depend_module_name.split("-")
                std_depend_name = ''
                for name in split_names:
                    std_depend_name += name.capitalize()

                depends_std_modules.append(DependsStdModuleExpandInfo(
                    name=depend_module_name,
                    std_depend_name=std_depend_name,
                    git_version=depends_std_module['git_tag'],
                    git_repository=depends_std_module['git_repository']
                ))
                if 'import_options' in depends_std_module.keys() and depends_std_module['import_options']:
                    for option, status in depends_std_module['import_options'].items():
                        import_options.append(DefinitionExpandInfo(name=option, status=status))
        return depends_std_modules, import_options

    def parse_build_mode(self, build_mode):
        # parse the build mode
        build_mode_expand_infos = []
        if build_mode:
            build_mode_dict = {'global': []}
            for name, build_mode_tags in build_mode.items():
                if build_mode_tags:
                    for build_mode_tag in build_mode_tags:
                        build_option = self.project_name_.upper() + '_' + build_mode_tag.upper()
                        if build_mode_tag in self.build_mode_tags_:
                            if build_option not in build_mode_dict.keys():
                                build_mode_dict[build_option] = []
                            build_mode_dict[build_option].append(name)
                        else:
                            raise Exception("Build mode tag: " + build_mode_tag + " Not found, Please check it!")
                else:
                    build_mode_dict['global'].append(name)

            for option, names in build_mode_dict.items():
                build_mode_expand_infos.append(BuildModeExpandInfo(build_option=option, items=names))
        return build_mode_expand_infos

    def generate(self):
        print("Workspace Files Generating...")
        for workspace_template_info in workspace_template_infos:
            template = self.get_template_environment(workspace_template_info)

            if workspace_template_info['template_name'] == "get_depends_cmake":
                for depends_std_module in self.expand_info_.depends_std_modules:
                    output_stream = template.render(depends_std_module=depends_std_module)
                    self.output(workspace_template_info, depends_std_module.std_depend_name, output_stream)
                continue

            if workspace_template_info['template_name'] == "test_sh":
                for definition in self.expand_info_.definitions:
                    if definition.name == self.test_marco_:
                        definition.status = 'ON'

            output_stream = template.render(workspace=self.expand_info_, project_name=self.project_name_)
            self.output(workspace_template_info, '', output_stream)

        for static_ws_template_info in static_ws_template_infos:
            template = self.get_template_environment(static_ws_template_info)
            output_stream = template.render()
            self.output(static_ws_template_info, '', output_stream)

    def get_project_name(self):
        return self.project_name_

    def get_std_module_depends(self):
        return self.module_depends_

    def get_output_dir(self):
        return self.output_dir_
