#!/usr/bin/env python3
#
# Copyright 2019, The Android Open Source Project
#
# 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.

"""Module Info class used to hold cached merged_module_info.json."""

import json
import logging
import os

from pathlib import Path

from atest import constants
from atest import module_info

from aidegen import constant
from aidegen.lib import common_util
from aidegen.lib import module_info_util
from aidegen.lib.singleton import Singleton


class AidegenModuleInfo(module_info.ModuleInfo, metaclass=Singleton):
    """Class that offers fast/easy lookup for Module related details."""

    def _load_module_info_file(self, module_file):
        """Loads the module file.

        Args:
            module_file: String of path to file to load up. Used for testing.

        Returns:
            Tuple of module_info_target and a json object.
        """
        # If module_file is specified, we're testing so we don't care if
        # module_info_target stays None.
        module_info_target = None
        file_path = module_file

        if not file_path:
            module_info_target, file_path = self._discover_mod_file_and_target(
                self.force_build)
            self.mod_info_file_path = Path(file_path)

        logging.debug('Loading %s as module-info.', file_path)
        with open(file_path, 'r', encoding='utf8') as json_file:
            mod_info = json.load(json_file)

        return module_info_target, mod_info

    @staticmethod
    def _discover_mod_file_and_target(force_build):
        """Find the module file.

        If force_build is True, we'll remove module_bp_java_deps.json first and
        let module_info_util.generate_merged_module_info regenerate it again.

        Args:
            force_build: Boolean to indicate if we should rebuild the
                         module_info file regardless if it's created or not.

        Returns:
            Tuple of the relative and absolute paths of the merged module info
            file.
        """
        module_file_path = common_util.get_blueprint_json_path(
            constant.BLUEPRINT_JAVA_JSONFILE_NAME)
        if force_build and os.path.isfile(module_file_path):
            os.remove(module_file_path)
        merged_file_path = os.path.join(common_util.get_soong_out_path(),
                                        constant.MERGED_MODULE_INFO)
        if not os.path.isfile(merged_file_path):
            logging.debug(
                'Generating %s - this is required for the initial runs.',
                merged_file_path)
        data = module_info_util.generate_merged_module_info()
        common_util.dump_json_dict(merged_file_path, data)
        merged_file_rel_path = os.path.relpath(
            merged_file_path, common_util.get_android_root_dir())
        return merged_file_rel_path, merged_file_path

    @staticmethod
    def is_target_module(mod_info):
        """Determine if the module is a target module.

        Determine if a module's class is in TARGET_CLASSES.

        Args:
            mod_info: A module's module-info dictionary to be checked.

        Returns:
            A boolean, true if it is a target module, otherwise false.
        """
        if mod_info:
            return any(
                x in mod_info.get(constants.MODULE_CLASS, [])
                for x in constant.TARGET_CLASSES)
        return False

    @staticmethod
    def is_project_path_relative_module(mod_info, rel_path):
        """Determine if the given project path is relative to the module.

        The rules:
           1. If constant.KEY_PATH not in mod_info, we can't tell if it's a
              module return False.
           2. If rel_path is empty, it's under Android root, return True.
           3. If module's path equals or starts with rel_path return True,
              otherwise return False.

        Args:
            mod_info: the module-info dictionary of the checked module.
            rel_path: project's relative path

        Returns:
            True if it's the given project path is relative to the module,
            otherwise False.
        """
        if (constant.KEY_PATH not in mod_info
                or not mod_info[constant.KEY_PATH]):
            return False
        path = mod_info[constant.KEY_PATH][0]
        if rel_path == '':
            return True
        if (constant.KEY_CLASS in mod_info
                and common_util.is_source_under_relative_path(path, rel_path)):
            return True
        return False
