#!/usr/bin/python3
"""
Description: Find compilation dependency of source package
class: BuildDepend
"""
from packageship.application.apps.package.function.searchdb import SearchDB
from packageship.application.apps.package.function.install_depend import InstallDepend
from packageship.application.apps.package.function.constants import ResponseCode, ListNode


class BuildDepend():
    """
    Description: Find compilation dependency of source package
    Attributes:
        pkg_name_list: List of package names
        db_list: List of database names
        self_build: Compile dependency conditions
        history_dict: Query history dict
        search_db:Query an instance of a database class
        result_dict:A dictionary to store the data that needs to be echoed
        source_dict:A dictionary to store the searched source code package name
        not_found_components: Contain the package not found components
    """

    def __init__(self, pkg_name_list, db_list, self_build=0, history_dict=None):
        """
        init class
        """
        self.pkg_name_list = pkg_name_list
        self._self_build = self_build

        self.db_list = db_list
        self.search_db = SearchDB(db_list)

        self.result_dict = dict()
        self.source_dict = dict()

        self.history_dicts = history_dict if history_dict else {}
        self.not_found_components = set()

    def build_depend_main(self):
        """
        Description: Entry function
        Args:
        Returns:
            ResponseCode: response code
            result_dict: Dictionary of query results
            source_dict: Dictionary of source code package
            not_found_components: Set of package not found components
        Raises:
        """
        if not self.search_db.db_object_dict:
            return ResponseCode.DIS_CONNECTION_DB, None, None, set()

        if self._self_build == 0:
            code = self.build_depend(self.pkg_name_list)
            if None in self.result_dict:
                del self.result_dict[None]
            return code, self.result_dict, None, self.not_found_components

        if self._self_build == 1:
            self.self_build(self.pkg_name_list)
            if None in self.result_dict:
                del self.result_dict[None]
            # There are two reasons for the current status code to return SUCCESS
            # 1, Other branches return three return values.
            #    Here, a place holder is needed to prevent unpacking errors during call
            # 2, This function is an auxiliary function of other modules.
            #    The status code is not the final display status code
            return ResponseCode.SUCCESS, self.result_dict, self.source_dict, self.not_found_components

        return ResponseCode.PARAM_ERROR, None, None, set()

    def build_depend(self, pkg_list):
        """
        Description: Compile dependency query
        Args:
             pkg_list:You need to find the dependent source package name
        Returns:
             ResponseCode: response code
        Raises:
        """
        res_status, build_list, not_fd_com_build = self.search_db.get_build_depend(pkg_list)
        self.not_found_components.update(not_fd_com_build)
        if not build_list:
            return res_status if res_status == ResponseCode.DIS_CONNECTION_DB else \
                ResponseCode.PACK_NAME_NOT_FOUND

        # create root node and get next search list
        search_list = self._create_node_and_get_search_list(build_list, pkg_list)

        code, res_dict, not_fd_com_install = \
            InstallDepend(self.db_list).query_install_depend(search_list,
                                                             self.history_dicts)
        self.not_found_components.update(not_fd_com_install)
        if not res_dict:
            return code

        for k, values in res_dict.items():
            if k in self.result_dict:
                if ['root', None] in values[ListNode.PARENT_LIST]:
                    index = values[ListNode.PARENT_LIST].index(['root', None])
                    del values[ListNode.PARENT_LIST][index]

                self.result_dict[k][ListNode.PARENT_LIST].extend(values[ListNode.PARENT_LIST])
            else:
                self.result_dict[k] = values

        return ResponseCode.SUCCESS

    def _create_node_and_get_search_list(self, build_list, pkg_list):
        """
        Description: To create root node in self.result_dict and
            return the name of the source package to be found next time
        Args:
            build_list:List of binary package names
            pkg_list: List of binary package names
        Returns:
             the name of the source package to be found next time
        Raises:
        """
        search_set = set()
        search_list = []
        for obj in build_list:
            if not obj.search_name:
                continue

            if obj.search_name + "_src" not in self.result_dict:
                self.result_dict[obj.search_name + "_src"] = [
                    'source',
                    obj.search_version,
                    obj.db_name,
                    [
                        ['root', None]
                    ]
                ]
                search_set.add(obj.search_name)

            if not obj.bin_name:
                continue

            if obj.bin_name in self.history_dicts:
                self.result_dict[obj.bin_name] = [
                    self.history_dicts[obj.bin_name][ListNode.SOURCE_NAME],
                    self.history_dicts[obj.bin_name][ListNode.VERSION],
                    self.history_dicts[obj.bin_name][ListNode.DBNAME],
                    [
                        [obj.search_name, 'build']
                    ]
                ]
            else:
                if obj.bin_name in search_list:
                    self.result_dict[obj.bin_name][ListNode.PARENT_LIST].append([
                        obj.search_name, 'build'
                    ])
                else:
                    self.result_dict[obj.bin_name] = [
                        obj.source_name,
                        obj.version,
                        self.search_db.binary_search_database_for_first_time(obj.bin_name),
                        [
                            [obj.search_name, 'build']
                        ]
                    ]
                    search_list.append(obj.bin_name)

        if search_set and len(search_set) != len(pkg_list):
            temp_set = set(pkg_list) - search_set
            for name in temp_set:
                self.result_dict[name + "_src"] = [
                    None,
                    None,
                    'NOT_FOUND',
                    [
                        ['root', None]
                    ]
                ]
        return search_list

    def self_build(self, pkg_name_li):
        """
        Description: Using recursion to find compilation dependencies
        Args:
            pkg_name_li: Source package name list
        Returns:
        Raises:
        """
        if not pkg_name_li:
            return

        next_src_set = set()
        _, bin_info_lis, not_fd_com = self.search_db.get_build_depend(pkg_name_li)
        self.not_found_components.update(not_fd_com)
        if not bin_info_lis:
            return

            # generate data content
        search_name_set = set()
        for obj in bin_info_lis:

            search_name_set.add(obj.search_name)
            if obj.search_name not in self.source_dict:
                self.source_dict[obj.search_name] = [obj.db_name, obj.search_version]

            if not obj.bin_name:
                continue

            if obj.bin_name not in self.result_dict:
                self.result_dict[obj.bin_name] = [
                    obj.source_name if obj.source_name else None,
                    obj.version if obj.version else None,
                    self.search_db.binary_search_database_for_first_time(obj.bin_name),
                    [
                        [obj.search_name, "build"]
                    ]
                ]
            else:
                node = [obj.search_name, "build"]
                node_list = self.result_dict[obj.bin_name][-1]
                if node not in node_list:
                    node_list.append(node)

            if obj.source_name and \
                    obj.source_name not in self.source_dict and \
                    obj.source_name not in self.history_dicts:
                next_src_set.add(obj.source_name)

        not_found_pkg = set(pkg_name_li) - search_name_set
        for pkg_name in not_found_pkg:
            if pkg_name not in self.source_dict:
                self.source_dict[pkg_name] = ['NOT FOUND', 'NOT FOUND']
        not_found_pkg.clear()
        self.self_build(next_src_set)

        return
