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

# Copyright (c) 2021-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 argparse
import json
import os
import re
import sys


class TokenType(object):
    UNKNOWN = 0
    COMMENT = 1
    PACKAGE = 2
    IMPORT = 3
    INTERFACE = 4
    CALLBACK = 5
    ID = 6
    END_OF_FILE = 7


class Token(object):
    def __init__(self, file_name, token_type, value):
        self.token_type = token_type
        self.value = value
        self.row = 1
        self.col = 1
        self.file_name = file_name

    def clean(self):
        self.token_type = TokenType.UNKNOWN
        self.value = ""
        self.row = 1
        self.col = 1

    def dump(self):
        return "<{}:{}:{}: {},'{}'>".format(self.file_name, self.row, self.col,
                                            self.token_type, self.value)

    def info(self):
        return "{}:{}:{}".format(self.file_name, self.row, self.col)


class Char(object):
    def __init__(self, is_eof, char):
        self.is_eof = is_eof
        self.char = char

    def dump(self):
        return "{%s, %s}" % (self.is_eof, self.char)


class Lexer(object):
    _key_words = {
        "package": TokenType.PACKAGE,
        "import": TokenType.IMPORT,
        "interface": TokenType.INTERFACE,
        "callback": TokenType.CALLBACK,
    }

    def __init__(self, idl_file_path):
        self.have_peek = False
        with open(idl_file_path, 'r') as idl_file:
            file_info = idl_file.read()
        self.data = file_info
        self.data_len = len(self.data)
        self.read_index = 0
        self.cur_token = Token(os.path.basename(idl_file_path),
                               TokenType.UNKNOWN, "")
        self.cur_row = 1
        self.cur_col = 1

    def peek_char(self, peek_count=0):
        index = self.read_index + peek_count
        if index >= self.data_len:
            return Char(True, '0')
        return Char(False, self.data[index])

    def get_char(self):
        if self.read_index >= self.data_len:
            return Char(True, '0')
        read_index = self.read_index
        self.read_index += 1
        if self.data[read_index] == '\n':
            self.cur_row += 1
            self.cur_col = 1
        else:
            self.cur_col += 1
        return Char(False, self.data[read_index])

    def peek_token(self):
        if not self.have_peek:
            self.read_token()
            self.have_peek = True
        return self.cur_token

    def get_token(self):
        if not self.have_peek:
            self.read_token()
        self.have_peek = False
        return self.cur_token

    def read_token(self):
        self.cur_token.clean()
        while not self.peek_char().is_eof:
            new_char = self.peek_char()
            if new_char.char.isspace():
                self.get_char()
                continue
            self.cur_token.row = self.cur_row
            self.cur_token.col = self.cur_col
            if new_char.char.isalpha() or new_char.char == '_':
                self.read_id()
                return
            if new_char.char == '/':
                self.read_comment()
                return
            self.cur_token.value = new_char.char
            self.cur_token.token_type = TokenType.UNKNOWN
            self.get_char()
            return
        self.cur_token.token_type = TokenType.END_OF_FILE

    def read_id(self):
        token_value = []
        token_value.append(self.get_char().char)
        while not self.peek_char().is_eof:
            new_char = self.peek_char()
            if new_char.char.isalpha() or new_char.char.isdigit(
            ) or new_char.char == '_' or new_char.char == '.':
                token_value.append(new_char.char)
                self.get_char()
                continue
            break
        key = "".join(token_value)
        if key in self._key_words.keys():
            self.cur_token.token_type = self._key_words[key]
        else:
            self.cur_token.token_type = TokenType.ID
        self.cur_token.value = key

    def read_comment(self):
        token_value = []
        token_value.append(self.get_char().char)
        new_char = self.peek_char()
        if not new_char.is_eof:
            if new_char.char == '/':
                self.read_line_comment(token_value)
                return
            elif new_char.char == '*':
                self.read_block_comment(token_value)
                return
        self.cur_token.token_type = TokenType.UNKNOWN
        self.cur_token.value = "".join(token_value)

    def read_line_comment(self, token_value):
        token_value.append(self.get_char().char)
        while not self.peek_char().is_eof:
            new_char = self.get_char()
            if new_char.char == '\n':
                break
            token_value.append(new_char.char)
        self.cur_token.token_type = TokenType.COMMENT
        self.cur_token.value = "".join(token_value)

    def read_block_comment(self, token_value):
        # read *
        token_value.append(self.get_char().char)
        while not self.peek_char().is_eof:
            new_char = self.get_char()
            token_value.append(new_char.char)
            if new_char.char == '*' and self.peek_char().char == '/':
                token_value.append(self.get_char().char)
                break
        value = "".join(token_value)
        if value.endswith("*/"):
            self.cur_token.token_type = TokenType.COMMENT
        else:
            self.cur_token.token_type = TokenType.UNKNOWN
        self.cur_token.value = value


# module info of all idl
class ModuleInfo(object):
    package = ""
    version = ""
    include_dirs = set()
    out_dir = ""
    sources = []
    proxy_sources = []
    stub_sources = []
    proxy_deps = []
    stub_deps = []
    header_deps = []

    @staticmethod
    def json_info():
        include_dirs_ret = sorted(list(ModuleInfo.include_dirs))
        ModuleInfo.sources.sort()
        ModuleInfo.proxy_sources.sort()
        ModuleInfo.stub_sources.sort()

        result = {
            "package": ModuleInfo.package,
            "version": ModuleInfo.version,
            "include_dirs": include_dirs_ret,
            "out_dir": ModuleInfo.out_dir,
            "sources": ModuleInfo.sources,
            "proxy_sources": ModuleInfo.proxy_sources,
            "stub_sources": ModuleInfo.stub_sources,
            "proxy_deps": ModuleInfo.proxy_deps,
            "stub_deps": ModuleInfo.stub_deps,
            "header_deps": ModuleInfo.header_deps,
        }
        return json.dumps(result)


class Option(object):
    system = "full"
    mode = "ipc"
    language = "cpp"
    gen_dir = ""
    root_package = ""
    root_path = ""
    idl_sources = []
    imports = []

    @staticmethod
    def load(opt_args):
        Option.system = opt_args.system
        Option.mode = opt_args.mode
        Option.language = opt_args.language

        if opt_args.out == "":
            raise Exception(
                "the gen_dir '{}' is empty, please check input".format(
                    opt_args.out))
        else:
            Option.gen_dir = opt_args.out

        map_result = opt_args.root.split(":")
        if len(map_result) != 2:
            raise Exception(
                "the package path '{}' is valid, please check input".format(
                    opt_args.root))
        else:
            Option.root_package = map_result[0]
            Option.root_path = map_result[1]

        if len(opt_args.file) == 0:
            raise Exception("the idl sources is empty, please check input")
        else:
            Option.idl_sources = opt_args.file

        if opt_args.imports is not None:
            Option.imports = opt_args.imports

    @staticmethod
    def dump():
        result = {
            "system": Option.system,
            "kernel": Option.kernel,
            "mode": Option.mode,
            "language": Option.language,
            "gen_dir": Option.gen_dir,
            "root_package": Option.root_package,
            "root_path": Option.root_path,
            "idl_sources": Option.idl_sources
        }
        return json.dumps(result)


class IdlType(object):
    INTERFACE = 1
    CALL_INTERFACE = 2
    CALLBACK = 3
    TYPES = 4


# file detail of idl file
class IdlDetail(object):
    def __init__(self, path):
        self.package = ""
        self.idl_type = IdlType.TYPES
        self.imports = []
        self.file_path = path

        self.file_name = os.path.basename(self.file_path)
        self.name = self.file_name.split('.')[0]

    # package + file name, like 'ohos.hdi.foo.v1_0.IFoo'
    def full_name(self):
        return "{}.{}".format(self.package, self.name)

    def dump(self):
        result = {
            "package": self.package,
            "type": self.idl_type,
            "imports": self.imports,
            "path": self.file_path
        }
        return json.dumps(result)


class IdlParser(object):
    def parse(self, ):
        all_idl_details = {}
        if Option.language == "c":
            self.parse_c_idl_files(all_idl_details)
            self.parse_deps(all_idl_details)
            self.parse_module_info(all_idl_details)
            return

        for idl_file in Option.idl_sources:
            idl_detail = self.parse_one(idl_file)
            all_idl_details[idl_detail.full_name()] = idl_detail
        self.parse_deps(all_idl_details)
        self.parse_module_info(all_idl_details)

    def parse_one(self, file_path):
        cur_idl_detail = IdlDetail(file_path)
        lex = Lexer(file_path)
        while lex.peek_token().token_type != TokenType.END_OF_FILE:
            cur_token_type = lex.peek_token().token_type
            if cur_token_type == TokenType.PACKAGE:
                self.parse_package(lex, cur_idl_detail)
            elif cur_token_type == TokenType.IMPORT:
                self.parse_import(lex, cur_idl_detail)
            elif cur_token_type == TokenType.CALLBACK:
                cur_idl_detail.idl_type = IdlType.CALLBACK
                lex.get_token()
            elif cur_token_type == TokenType.INTERFACE:
                self.parse_interface(lex, cur_idl_detail)
            else:
                lex.get_token()
        return cur_idl_detail

    def parse_c_idl_files(self, all_idl_details):
        idl_sources_set = set()
        idl_queue = []
        for idl_file in Option.idl_sources:
            idl_queue.append(idl_file)
        while len(idl_queue) > 0:
            cur_idl_file = idl_queue.pop(0)
            if cur_idl_file in idl_sources_set:
                continue
            idl_sources_set.add(cur_idl_file)
            self.parse_c_idl_files_import(cur_idl_file, idl_queue)
        for idl_file in idl_sources_set:
            idl_detail = self.parse_one(idl_file)
            all_idl_details[idl_detail.full_name()] = idl_detail
        self.merged_idl_details(all_idl_details)

    def parse_c_idl_files_import(self, file_path, idl_queue):
        lex = Lexer(file_path)
        while lex.peek_token().token_type != TokenType.END_OF_FILE:
            cur_token_type = lex.peek_token().token_type
            if cur_token_type == TokenType.IMPORT:
                lex.get_token()
                token = lex.peek_token()
                if lex.peek_token().token_type != TokenType.ID:
                    raise Exception("{}: expected package name before '{}'".format(
                        token.info(), token.value))
                idl_queue.append(
                    CodeGen.get_package_path(token.value) + ".idl")
            lex.get_token()

    def update_imports(self, all_idl_details, idl_detail, merged_details):
        if idl_detail.full_name() not in merged_details:
            imports = []
            for import_name in idl_detail.imports:
                import_idl = all_idl_details[import_name]
                if import_idl.full_name() in imports:
                    continue
                if import_idl.full_name() != idl_detail.full_name():
                    imports.append(import_idl.full_name())
            idl_detail.imports = imports
            merged_details[idl_detail.full_name()] = idl_detail
        else:
            for import_name in idl_detail.imports:
                import_idl = all_idl_details[import_name]
                merged_detail = merged_details[idl_detail.full_name()]
                if import_idl.full_name() in merged_detail.imports:
                    continue
                if import_idl.full_name() != idl_detail.full_name():
                    merged_detail.imports.append(import_idl.full_name())

    def merged_idl_details(self, all_idl_details):
        merged_details = {}
        source_idl_detail = self.parse_one(Option.idl_sources[0])
        for _, idl_detail in all_idl_details.items():
            idl_detail.package = source_idl_detail.package
            idl_detail.version = source_idl_detail.version
        for _, idl_detail in all_idl_details.items():
            self.update_imports(all_idl_details, idl_detail, merged_details)
        all_idl_details.clear()
        for key, value in merged_details.items():
            all_idl_details[key] = value

    def parse_package(self, lex, cur_idl_detail):
        lex.get_token()  # package token
        token = lex.peek_token()
        if token.token_type != TokenType.ID:
            raise Exception("{}: expected package name before '{}'".format(
                token.info(), token.value))
        token = lex.get_token()
        if not self.parse_version(token.value, cur_idl_detail):
            raise Exception("{}: failed to parse package name '{}'".format(
                token.info(), token.vlaue))

    def parse_version(self, package_name, cur_idl_detail):
        result = re.findall(r'\w+(?:\.\w+)*\.[V|v](\d+)_(\d+)', package_name)
        if len(result) > 0:
            cur_idl_detail.package = package_name
            major_version = result[0][0]
            minor_version = result[0][1]
            cur_idl_detail.version = "{}.{}".format(major_version, minor_version)
            return True
        return False

    def parse_import(self, lex, cur_idl_detail):
        lex.get_token()  # import token
        if lex.peek_token().token_type != TokenType.ID:
            token = lex.peek_token()
            raise Exception("{}: expected package name before '{}'".format(
                token.info(), token.value))
        cur_idl_detail.imports.append(lex.get_token().value)

    def parse_interface(self, lex, cur_idl_detail):
        lex.get_token()  # interface token
        if lex.peek_token().token_type != TokenType.ID:
            token = lex.peek_token()
            raise Exception("{}: expected interface name before '{}'".format(
                token.info(), token.value))
        token = lex.get_token()
        interface_name = token.value
        if interface_name != cur_idl_detail.name:
            raise Exception(
                "{}: interface name '{}' does not match file name '{}'".format(
                    token.info(), interface_name, cur_idl_detail.file_name))
        if cur_idl_detail.idl_type != IdlType.CALLBACK:
            cur_idl_detail.idl_type = IdlType.INTERFACE

    def parse_deps(self, all_idl_details):
        for detail_name, idl_detail in all_idl_details.items():
            self.query_and_update_idl_type(idl_detail, all_idl_details)

    # update interface idl file type if the file import by other idl file
    def query_and_update_idl_type(self, idl_detail, all_idl_details):
        for other_name, other_detail in all_idl_details.items():
            if idl_detail.full_name() == other_name:
                continue
            if self.imported_by_other_idl(idl_detail, other_detail) and idl_detail.idl_type == IdlType.INTERFACE:
                idl_detail.idl_type = IdlType.CALL_INTERFACE
                break

    def imported_by_other_idl(self, idl_detail, other_detail):
        for import_name in other_detail.imports:
            if idl_detail.full_name() == import_name:
                return True
        return False

    def parse_module_info(self, all_idl_details):
        generator = CodeGenFactory.create_code_generate()
        if generator is None:
            return
        ModuleInfo.out_dir = Option.gen_dir
        self.parse_sources(all_idl_details, generator)
        ModuleInfo.proxy_deps, ModuleInfo.stub_deps, ModuleInfo.header_deps = CodeGen.get_lib_deps(Option.imports)

    def parse_sources(self, all_idl_details, generator):
        ModuleInfo.include_dirs.add(Option.gen_dir)
        for idl_detail in all_idl_details.values():
            ModuleInfo.package = idl_detail.package
            ModuleInfo.version = idl_detail.version
            ModuleInfo.include_dirs.add(
                generator.parse_include_dirs(idl_detail.package))

            sources, proxy_sources, sub_sources = generator.gen_code(
                idl_detail)
            ModuleInfo.sources.extend(sources)
            ModuleInfo.proxy_sources.extend(proxy_sources)
            ModuleInfo.stub_sources.extend(sub_sources)


# generate code file info of hdi
class CodeGen(object):
    # package is 'ohos.hdi.foo.v1_0'
    # -r ohos.hdi:./interface
    # sub_package is foo.v1_0
    @staticmethod
    def get_sub_package(package):
        if package.startswith(Option.root_package):
            root_package_len = len(Option.root_package)
            return package[root_package_len + 1:]
        return package

    @staticmethod
    def get_package_path(package):
        package_path = ""
        if package.startswith(Option.root_package):
            root_package_len = len(Option.root_package)
            sub_package = package[root_package_len:]
            sub_package_path = sub_package.replace(".", os.sep)
            package_path = "{}{}".format(Option.root_path, sub_package_path)
        else:
            raise Exception("find root package '{}' failed in '{}'".format(
                Option.root_package, package))

        return package_path

    @staticmethod
    def get_version(package):
        major_version = 0
        minor_version = 0
        result = re.findall(r'\w+(?:\.\w+)*\.[V|v](\d+)_(\d+)', package)
        if len(result) > 0:
            major_version = result[0][0]
            minor_version = result[0][1]
        return major_version, minor_version

    # transalte package name to include directory
    @staticmethod
    def parse_include_dirs(package):
        sub_package = CodeGen.get_sub_package(package)
        last_point_index = sub_package.rfind('.')
        package_without_version = sub_package[:last_point_index]
        package_dir_without_version = package_without_version.replace(
            '.', os.sep)
        return os.path.join(Option.gen_dir, package_dir_without_version)

    # translate package name to directory
    @staticmethod
    def get_source_file_dir(package):
        sub_package = CodeGen.get_sub_package(package)
        sub_package_dir = "{}{}".format(sub_package.replace('.', os.sep),
                                        os.sep)
        return os.path.join(Option.gen_dir, sub_package_dir)

    # translate idl file name to c/c++ file name
    @staticmethod
    def translate_file_name(file_name):
        under_line = '_'
        result = []
        name_len = len(file_name)
        for index in range(name_len):
            cur_char = file_name[index]
            if cur_char.isupper():
                if index > 1:
                    result.append(under_line)
                result.append(cur_char.lower())
            else:
                result.append(cur_char)
        return "".join(result)

    @staticmethod
    def translate_proxy_name(base_name):
        temp_name = "{}Proxy".format(base_name)
        return CodeGen.translate_file_name(temp_name)

    @staticmethod
    def translate_stub_name(base_name):
        temp_name = "{}Stub".format(base_name)
        return CodeGen.translate_file_name(temp_name)

    @staticmethod
    def translate_service_name(base_name):
        temp_name = "{}Service".format(base_name)
        return CodeGen.translate_file_name(temp_name)

    @staticmethod
    def translate_driver_name(base_name):
        temp_name = "{}Driver".format(base_name)
        return CodeGen.translate_file_name(temp_name)

    @staticmethod
    def get_type_names(name):
        base_name = CodeGen.translate_file_name(name)
        return base_name

    # translate idl file name to c/c++ file name
    # for example, IFoo -> ifoo, foo_proxy, foo_stub, foo_service, foo_driver, foo
    @staticmethod
    def get_file_names(idl_detail):
        interface_name = ""
        proxy_name = ""
        stub_name = ""
        service_name = ""
        driver_name = ""
        types_name = ""

        if idl_detail.idl_type == IdlType.TYPES:
            types_name = CodeGen.get_type_names(idl_detail.name)
            return interface_name, proxy_name, stub_name, service_name, driver_name, types_name

        base_name = idl_detail.name[1:] if idl_detail.name.startswith(
            "I") else idl_detail.name
        interface_name = CodeGen.translate_file_name(idl_detail.name)
        proxy_name = CodeGen.translate_proxy_name(base_name)
        stub_name = CodeGen.translate_stub_name(base_name)
        service_name = CodeGen.translate_service_name(base_name)
        driver_name = CodeGen.translate_driver_name(base_name)
        if idl_detail.idl_type == IdlType.INTERFACE:
            driver_name = CodeGen.translate_driver_name(base_name)
        return interface_name, proxy_name, stub_name, service_name, driver_name, types_name

    @staticmethod
    def header_file(file_dir, name):
        return os.path.join(file_dir, "{}.h".format(name))

    @staticmethod
    def c_source_file(file_dir, name):
        return os.path.join(file_dir, "{}.c".format(name))

    @staticmethod
    def cpp_source_file(file_dir, name):
        return os.path.join(file_dir, "{}.cpp".format(name))

    @staticmethod
    def get_import(imp):
        package = ""
        module_name = ""
        imp_result = imp.split(":")
        if len(imp_result) == 2:
            package = imp_result[0]
            module_name = imp_result[1]
        return package, module_name

    # get lib deps from imports
    @staticmethod
    def get_lib_deps(imports):
        proxy_deps = []
        stub_deps = []
        header_deps = []
        for imps in imports:
            package, module_name = CodeGen.get_import(imps)
            package_path = CodeGen.get_package_path(package)
            major_version, minor_version = CodeGen.get_version(package)
            proxy_lib_name = "lib{}_proxy_{}.{}".format(module_name, major_version, minor_version)
            stub_lib_name = "lib{}_stub_{}.{}".format(module_name, major_version, minor_version)
            header_config = "{}_idl_headers_{}.{}".format(module_name, major_version, minor_version)
            proxy_lib_dep = "{}:{}".format(package_path, proxy_lib_name)
            stub_lib_dep = "{}:{}".format(package_path, stub_lib_name)
            header_dep = "{}:{}".format(package_path, header_config)
            proxy_deps.append(proxy_lib_dep)
            stub_deps.append(stub_lib_dep)
            header_deps.append(header_dep)
        return proxy_deps, stub_deps, header_deps

    def gen_code(self, idl_detail):
        idl_detail
        return [], [], []


class LowCCodeGen(CodeGen):
    def gen_code(self, idl_detail):
        file_dir = self.get_source_file_dir(idl_detail.package)
        sources = []
        interface_name, _, _, service_name, driver_name, types_name = self.get_file_names(idl_detail)
        if idl_detail.idl_type == IdlType.TYPES:
            header_file = self.header_file(file_dir, types_name)
            sources.append(header_file)
        if idl_detail.idl_type == IdlType.INTERFACE:
            header_file = self.header_file(file_dir, interface_name)
            service_header_file = self.header_file(file_dir, service_name)
            service_source_file = self.c_source_file(file_dir, service_name)
            driver_source_file = self.c_source_file(file_dir, driver_name)
            sources.extend([header_file, service_header_file, service_source_file, driver_source_file])
        return sources, [], []


class LowCppCodeGen(CodeGen):
    def gen_code(self, idl_detail):
        file_dir = self.get_source_file_dir(idl_detail.package)
        sources = []
        interface_name, _, _, service_name, driver_name, types_name = self.get_file_names(idl_detail)
        if idl_detail.idl_type == IdlType.TYPES:
            header_file = self.header_file(file_dir, types_name)
            sources.append(header_file)
        if idl_detail.idl_type == IdlType.INTERFACE:
            header_file = self.header_file(file_dir, interface_name)
            service_header_file = self.header_file(file_dir, service_name)
            service_source_file = self.cpp_source_file(file_dir, service_name)
            driver_source_file = self.cpp_source_file(file_dir, driver_name)
            sources.extend([header_file, service_header_file, service_source_file, driver_source_file])
        return sources, [], []


# generate kernel c code file info of hdi
class KernelCodeGen(CodeGen):
    def gen_code(self, idl_detail):
        file_dir = self.get_source_file_dir(idl_detail.package)
        sources = []
        proxy_sources = []
        stub_sources = []
        interface_name, proxy_name, stub_name, service_name, driver_name, types_name = self.get_file_names(
            idl_detail)
        if idl_detail.idl_type == IdlType.TYPES:
            header_file = self.header_file(file_dir, types_name)
            source_file = self.c_source_file(file_dir, types_name)
            sources.extend([header_file, source_file])
            proxy_sources.append(source_file)
            stub_sources.append(source_file)
            return sources, proxy_sources, stub_sources

        if idl_detail.idl_type == IdlType.INTERFACE:
            iface_header_file = self.header_file(file_dir, interface_name)
            proxy_source_file = self.c_source_file(file_dir, proxy_name)
            stub_header_file = self.header_file(file_dir, stub_name)
            stub_source_file = self.c_source_file(file_dir, stub_name)
            service_header_file = self.header_file(file_dir, service_name)
            service_source_file = self.c_source_file(file_dir, service_name)
            driver_source_file = self.c_source_file(file_dir, driver_name)
            sources.extend([
                iface_header_file, proxy_source_file, stub_header_file,
                stub_source_file, service_header_file, service_source_file,
                driver_source_file
            ])
            proxy_sources.append(proxy_source_file)
            stub_sources.append(stub_source_file)
        return sources, proxy_sources, stub_sources


# generate passthrough c code file info of hdi
class PassthroughCCodeGen(CodeGen):

    def gen_code(self, idl_detail):
        file_dir = self.get_source_file_dir(idl_detail.package)
        sources = []
        proxy_sources = []
        stub_sources = []
        interface_name, proxy_name, _, service_name, _, types_name = self.get_file_names(
            idl_detail)

        iface_header_file = self.header_file(file_dir, interface_name)
        proxy_source_file = self.c_source_file(file_dir, proxy_name)
        service_header_file = self.header_file(file_dir, service_name)
        service_source_file = self.c_source_file(file_dir, service_name)
        types_header_file = self.header_file(file_dir, types_name)

        if idl_detail.idl_type == IdlType.INTERFACE:
            sources.extend(
                [iface_header_file, proxy_source_file, service_source_file])
            proxy_sources.append(proxy_source_file)
        elif idl_detail.idl_type == IdlType.CALL_INTERFACE:
            sources.extend(
                [iface_header_file, service_header_file, service_source_file])
        elif idl_detail.idl_type == IdlType.CALLBACK:
            sources.extend(
                [iface_header_file, service_header_file, service_source_file])
        else:
            sources.append(types_header_file)
        return sources, proxy_sources, stub_sources


# generate passthrough cpp code file info of hdi
class PassthroughCppCodeGen(CodeGen):

    def gen_code(self, idl_detail):
        file_dir = self.get_source_file_dir(idl_detail.package)
        sources = []
        proxy_sources = []
        stub_sources = []
        interface_name, proxy_name, _, service_name, _, types_name = self.get_file_names(
            idl_detail)
        iface_header_file = self.header_file(file_dir, interface_name)
        proxy_source_file = self.cpp_source_file(file_dir, proxy_name)
        service_header_file = self.header_file(file_dir, service_name)
        service_source_file = self.cpp_source_file(file_dir, service_name)
        types_header_file = self.header_file(file_dir, types_name)

        if idl_detail.idl_type == IdlType.INTERFACE:
            sources.extend([
                iface_header_file, proxy_source_file, service_header_file,
                service_source_file
            ])
            proxy_sources.append(proxy_source_file)
        elif idl_detail.idl_type == IdlType.CALL_INTERFACE:
            sources.extend(
                [iface_header_file, service_header_file, service_source_file])
        elif idl_detail.idl_type == IdlType.CALLBACK:
            sources.extend(
                [iface_header_file, service_header_file, service_source_file])
        else:
            sources.append(types_header_file)
        return sources, proxy_sources, stub_sources


# generate ipc c code file information of hdi
class IpcCCodeGen(CodeGen):

    def gen_code(self, idl_detail):
        file_dir = self.get_source_file_dir(idl_detail.package)
        sources = []
        proxy_sources = []
        stub_sources = []
        interface_name, proxy_name, stub_name, service_name, driver_name, types_name = self.get_file_names(
            idl_detail)
        iface_header_file = self.header_file(file_dir, interface_name)
        proxy_source_file = self.c_source_file(file_dir, proxy_name)
        stub_header_file = self.header_file(file_dir, stub_name)
        stub_source_file = self.c_source_file(file_dir, stub_name)
        service_header_file = self.header_file(file_dir, service_name)
        service_source_file = self.c_source_file(file_dir, service_name)
        driver_source_file = self.c_source_file(file_dir, driver_name)
        types_header_file = self.header_file(file_dir, types_name)
        types_source_file = self.c_source_file(file_dir, types_name)

        if idl_detail.idl_type == IdlType.INTERFACE:
            sources.extend([
                iface_header_file, proxy_source_file, stub_header_file,
                stub_source_file, service_source_file, driver_source_file
            ])
            proxy_sources.append(proxy_source_file)
            stub_sources.append(stub_source_file)
        elif idl_detail.idl_type == IdlType.CALL_INTERFACE:
            sources.extend([
                iface_header_file, proxy_source_file, stub_header_file,
                stub_source_file, service_header_file, service_source_file
            ])
            proxy_sources.append(proxy_source_file)
            stub_sources.append(stub_source_file)
        elif idl_detail.idl_type == IdlType.CALLBACK:
            sources.extend([
                iface_header_file, proxy_source_file, stub_header_file,
                stub_source_file, service_header_file, service_source_file
            ])
            proxy_sources.append(stub_source_file)
            stub_sources.append(proxy_source_file)
        else:
            sources.extend([types_header_file, types_source_file])
            proxy_sources.append(types_source_file)
            stub_sources.append(types_source_file)
        return sources, proxy_sources, stub_sources


# generate ipc cpp code file information of hdi
class IpcCppCodeGen(CodeGen):

    def gen_code(self, idl_detail):
        file_dir = self.get_source_file_dir(idl_detail.package)
        sources = []
        proxy_sources = []
        stub_sources = []
        interface_name, proxy_name, stub_name, service_name, driver_name, types_name = self.get_file_names(
            idl_detail)
        iface_header_file = self.header_file(file_dir, interface_name)
        proxy_header_file = self.header_file(file_dir, proxy_name)
        proxy_source_file = self.cpp_source_file(file_dir, proxy_name)
        stub_header_file = self.header_file(file_dir, stub_name)
        stub_source_file = self.cpp_source_file(file_dir, stub_name)
        service_header_file = self.header_file(file_dir, service_name)
        service_source_file = self.cpp_source_file(file_dir, service_name)
        driver_source_file = self.cpp_source_file(file_dir, driver_name)
        types_header_file = self.header_file(file_dir, types_name)
        types_source_file = self.cpp_source_file(file_dir, types_name)

        if idl_detail.idl_type == IdlType.INTERFACE:
            sources.extend([
                iface_header_file, proxy_header_file, proxy_source_file,
                stub_header_file, stub_source_file, service_header_file,
                service_source_file, driver_source_file
            ])
            proxy_sources.append(proxy_source_file)
            stub_sources.append(stub_source_file)
        elif idl_detail.idl_type == IdlType.CALL_INTERFACE:
            sources.extend([
                iface_header_file, proxy_header_file, proxy_source_file,
                stub_header_file, stub_source_file, service_header_file,
                service_source_file
            ])
            proxy_sources.append(proxy_source_file)
            stub_sources.append(stub_source_file)
        elif idl_detail.idl_type == IdlType.CALLBACK:
            sources.extend([
                iface_header_file, proxy_header_file, proxy_source_file,
                stub_header_file, stub_source_file, service_header_file,
                service_source_file
            ])
            proxy_sources.append(stub_source_file)
            stub_sources.append(proxy_source_file)
        else:
            sources.extend([types_header_file, types_source_file])
            proxy_sources.append(types_source_file)
            stub_sources.append(types_source_file)
        return sources, proxy_sources, stub_sources


class CodeGenFactory(object):
    action_config = {
        "mini": {
            "low": {
                "c": LowCCodeGen(),
                "cpp": LowCppCodeGen()
            }
        },
        "lite": {
            "kernel": {
                "c": KernelCodeGen()
            },
            "passthrough": {
                "c": PassthroughCCodeGen(),
                "cpp": PassthroughCppCodeGen()
            }
        },
        "full": {
            "kernel": {
                "c": KernelCodeGen()
            },
            "passthrough": {
                "c": PassthroughCCodeGen(),
                "cpp": PassthroughCppCodeGen()
            },
            "ipc": {
                "c": IpcCCodeGen(),
                "cpp": IpcCppCodeGen()
            }
        }
    }

    @staticmethod
    def create_code_generate():
        if Option.system not in CodeGenFactory.action_config:
            raise Exception("the '{}' system is not supported".format(
                Option.system))
        system_action = CodeGenFactory.action_config.get(
            Option.system)
        if Option.mode not in system_action:
            raise Exception(
                "the '{}' mode is not supported by '{}' system".format(
                    Option.mode, Option.system))
        mode_action = system_action.get(Option.mode)
        if Option.language not in mode_action:
            raise Exception(
                "the '{}' language is not support by '{}' mode of '{}' system"
                .format(Option.language, Option.mode, Option.system))
        return mode_action.get(Option.language)


def check_python_version():
    if sys.version_info < (3, 0):
        raise Exception("Please run with python version >= 3.0")


if __name__ == "__main__":
    check_python_version()
    option_parser = argparse.ArgumentParser(
        description="Tools for generating compilation infomation of idl files",
    )

    option_parser.add_argument("-s",
                               "--system",
                               choices=["mini", "lite", "full"],
                               default="full",
                               help="system: mini, lite, full")

    option_parser.add_argument(
        "-m",
        "--mode",
        choices=["ipc", "passthrough", "kernel", "low"],
        default="ipc",
        help="generate code of ipc, passthrough or kernel mode")

    option_parser.add_argument("-l",
                               "--language",
                               required=True,
                               choices=["c", "cpp"],
                               help="language of generating code")

    option_parser.add_argument("-o",
                               "--out",
                               required=True,
                               default=".",
                               help="direstory of generate file")

    option_parser.add_argument("-r",
                               "--root",
                               required=True,
                               help="mapping path: <root package>:<path>")

    option_parser.add_argument("-f",
                               "--file",
                               required=True,
                               action="append",
                               help="the idl file")

    option_parser.add_argument("--imports",
                               action="append",
                               help="the imports")

    Option.load(option_parser.parse_args())
    idl_parser = IdlParser()
    idl_parser.parse()
    sys.stdout.write(ModuleInfo.json_info())
    sys.stdout.flush()