# Copyright 2018, 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.

"""Suite Plan Finder class."""

import logging
import os
import re

from atest import constants
from atest.test_finders import test_filter_utils
from atest.test_finders import test_finder_base
from atest.test_finders import test_finder_utils
from atest.test_finders import test_info
from atest.test_runners import suite_plan_test_runner

_SUITE_PLAN_NAME_RE = re.compile(
    r'^.*\/(?P<suite>.*)-tradefed\/res\/config\/'
    r'(?P<suite_plan_name>.*).xml$'
)


class SuitePlanFinder(test_finder_base.TestFinderBase):
  """Suite Plan Finder class."""

  NAME = 'SUITE_PLAN'
  _SUITE_PLAN_TEST_RUNNER = suite_plan_test_runner.SuitePlanTestRunner.NAME

  def __init__(self, module_info=None):
    super().__init__()
    self.root_dir = os.environ.get(constants.ANDROID_BUILD_TOP)
    self.mod_info = module_info
    self.suite_plan_dirs = self._get_suite_plan_dirs()

  def _get_mod_paths(self, module_name):
    """Return the paths of the given module name."""
    if self.mod_info:
      return self.mod_info.get_paths(module_name)
    return []

  def _get_suite_plan_dirs(self):
    """Get suite plan dirs from MODULE_INFO based on targets.

    Strategy:
        Search module-info.json using SUITE_PLANS to get all the suite
        plan dirs.

    Returns:
        A tuple of lists of strings of suite plan dir rel to repo root.
        None if the path can not be found in module-info.json.
    """
    return [
        d
        for x in constants.SUITE_PLANS
        for d in self._get_mod_paths(x + '-tradefed')
        if d is not None
    ]

  def _get_test_info_from_path(self, path, suite_name=None):
    """Get the test info from the result of using regular expression

    matching with the give path.

    Args:
        path: A string of the test's absolute or relative path.
        suite_name: A string of the suite name.

    Returns:
        A populated TestInfo namedtuple if regular expression
        matches, else None.
    """
    # Don't use names that simply match the path,
    # must be the actual name used by *TS to run the test.
    match = _SUITE_PLAN_NAME_RE.match(path)
    if not match:
      logging.debug('Suite plan test outside config dir: %s', path)
      return None
    suite = match.group('suite')
    suite_plan_name = match.group('suite_plan_name')
    if suite_name:
      if suite_plan_name != suite_name:
        logging.debug(
            'Input (%s) not valid suite plan name, did you mean: %s?',
            suite_name,
            suite_plan_name,
        )
        return None
    return test_info.TestInfo(
        test_name=suite_plan_name,
        test_runner=self._SUITE_PLAN_TEST_RUNNER,
        build_targets=set([suite]),
        suite=suite,
    )

  def find_test_by_suite_path(self, suite_path):
    """Find the first test info matching the given path.

    Strategy:
        If suite_path is to file --> Return TestInfo if the file
        exists in the suite plan dirs, else return None.
        If suite_path is to dir --> Return None

    Args:
        suite_path: A string of the path to the test's file or dir.

    Returns:
        A list of populated TestInfo namedtuple if test found, else None.
        This is a list with at most 1 element.
    """
    path, _ = test_filter_utils.split_methods(suite_path)
    # Make sure we're looking for a config.
    if not path.endswith('.xml'):
      return None
    path = os.path.realpath(path)
    suite_plan_dir = test_finder_utils.get_int_dir_from_path(
        path, self.suite_plan_dirs
    )
    if suite_plan_dir:
      rel_config = os.path.relpath(path, self.root_dir)
      return [self._get_test_info_from_path(rel_config)]
    return None

  def find_test_by_suite_name(self, suite_name):
    """Find the test for the given suite name.

    Strategy:
        If suite_name is cts --> Return TestInfo to indicate suite runner
        to make cts and run test using cts-tradefed.
        If suite_name is cts-common --> Return TestInfo to indicate suite
        runner to make cts and run test using cts-tradefed if file exists
        in the suite plan dirs, else return None.

    Args:
        suite_name: A string of suite name.

    Returns:
        A list of populated TestInfo namedtuple if suite_name matches
        a suite in constants.SUITE_PLAN, else check if the file
        existing in the suite plan dirs, else return None.
    """
    logging.debug('Finding test by suite: %s', suite_name)
    test_infos = []
    if suite_name in constants.SUITE_PLANS:
      test_infos.append(
          test_info.TestInfo(
              test_name=suite_name,
              test_runner=self._SUITE_PLAN_TEST_RUNNER,
              build_targets=set([suite_name]),
              suite=suite_name,
          )
      )
    else:
      test_files = test_finder_utils.search_integration_dirs(
          suite_name, self.suite_plan_dirs
      )
      if not test_files:
        return None
      for test_file in test_files:
        _test_info = self._get_test_info_from_path(test_file, suite_name)
        if _test_info:
          test_infos.append(_test_info)
    return test_infos
