# Copyright 2018 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

import argparse
import importlib
import logging
import multiprocessing
import os
import sys

from common import GetHostArchFromPlatform

BUILTIN_TARGET_NAMES = ['qemu', 'device', 'fvdl']


def _AddTargetSpecificationArgs(arg_parser):
  """Returns a parser that handles the target type used for the test run."""

  device_args = arg_parser.add_argument_group(
      'target',
      'Arguments specifying the Fuchsia target type. To see a list of '
      'arguments available for a specific target type, specify the desired '
      'target to use and add the --help flag.')
  device_args.add_argument('--target-cpu',
                           default=GetHostArchFromPlatform(),
                           help='GN target_cpu setting for the build. Defaults '
                           'to the same architecture as host cpu.')
  device_args.add_argument('--device',
                           default=None,
                           choices=BUILTIN_TARGET_NAMES + ['custom'],
                           help='Choose to run on fvdl|qemu|device. '
                           'By default, Fuchsia will run on Fvdl on x64 '
                           'hosts and QEMU on arm64 hosts. Alternatively, '
                           'setting to custom will require specifying the '
                           'subclass of Target class used via the '
                           '--custom-device-target flag.')
  device_args.add_argument('-d',
                           action='store_const',
                           dest='device',
                           const='device',
                           help='Run on device instead of emulator.')
  device_args.add_argument('--custom-device-target',
                           default=None,
                           help='Specify path to file that contains the '
                           'subclass of Target that will be used. Only '
                           'needed if device specific operations is '
                           'required.')


def _GetPathToBuiltinTarget(target_name):
  return '%s_target' % target_name


def _LoadTargetClass(target_path):
  try:
    loaded_target = importlib.import_module(target_path)
  except ImportError:
    logging.error(
        'Cannot import from %s. Make sure that --custom-device-target '
        'is pointing to a file containing a target '
        'module.' % target_path)
    raise
  return loaded_target.GetTargetType()


def _GetDefaultEmulatedCpuCoreCount():
  # Revise the processor count on arm64, the trybots on arm64 are in
  # dockers and cannot use all processors.
  # For x64, fvdl always assumes hyperthreading is supported by intel
  # processors, but the cpu_count returns the number regarding if the core
  # is a physical one or a hyperthreading one, so the number should be
  # divided by 2 to avoid creating more threads than the processor
  # supports.
  if GetHostArchFromPlatform() == 'x64':
    return max(int(multiprocessing.cpu_count() / 2) - 1, 4)
  return 4


def AddCommonArgs(arg_parser):
  """Adds command line arguments to |arg_parser| for options which are shared
  across test and executable target types.

  Args:
    arg_parser: an ArgumentParser object."""

  common_args = arg_parser.add_argument_group('common', 'Common arguments')
  common_args.add_argument('--logs-dir', help='Directory to write logs to.')
  common_args.add_argument('--verbose',
                           '-v',
                           default=False,
                           action='store_true',
                           help='Enable debug-level logging.')
  common_args.add_argument(
      '--out-dir',
      type=os.path.realpath,
      help=('Path to the directory in which build files are located. '
            'Defaults to current directory.'))
  common_args.add_argument('--fuchsia-out-dir',
                           default=None,
                           help='Path to a Fuchsia build output directory. '
                           'Setting the GN arg '
                           '"default_fuchsia_build_dir_for_installation" '
                           'will cause it to be passed here.')

  package_args = arg_parser.add_argument_group('package', 'Fuchsia Packages')
  package_args.add_argument(
      '--package',
      action='append',
      help='Paths of the packages to install, including '
      'all dependencies.')
  package_args.add_argument(
      '--package-name',
      help='Name of the package to execute, defined in ' + 'package metadata.')
  package_args.add_argument('--component-version',
                            help='Component version of the package to execute',
                            default='1')

  emu_args = arg_parser.add_argument_group('emu', 'General emulator arguments')
  emu_args.add_argument('--cpu-cores',
                        type=int,
                        default=_GetDefaultEmulatedCpuCoreCount(),
                        help='Sets the number of CPU cores to provide.')
  emu_args.add_argument('--ram-size-mb',
                        type=int,
                        default=8192,
                        help='Sets the emulated RAM size (MB).'),
  emu_args.add_argument('--allow-no-kvm',
                        action='store_false',
                        dest='require_kvm',
                        default=True,
                        help='Do not require KVM acceleration for '
                        'emulators.')


# Register the arguments for all known target types and the optional custom
# target type (specified on the commandline).
def AddTargetSpecificArgs(arg_parser):
  # Parse the minimal set of arguments to determine if custom targets need to
  # be loaded so that their arguments can be registered.
  target_spec_parser = argparse.ArgumentParser(add_help=False)
  _AddTargetSpecificationArgs(target_spec_parser)
  target_spec_args, _ = target_spec_parser.parse_known_args()
  _AddTargetSpecificationArgs(arg_parser)

  for target in BUILTIN_TARGET_NAMES:
    _LoadTargetClass(_GetPathToBuiltinTarget(target)).RegisterArgs(arg_parser)
  if target_spec_args.custom_device_target:
    _LoadTargetClass(
        target_spec_args.custom_device_target).RegisterArgs(arg_parser)


def ConfigureLogging(args):
  """Configures the logging level based on command line |args|."""

  logging.basicConfig(level=(logging.DEBUG if args.verbose else logging.INFO),
                      format='%(asctime)s:%(levelname)s:%(name)s:%(message)s')

  # The test server spawner is too noisy with INFO level logging, so tweak
  # its verbosity a bit by adjusting its logging level.
  logging.getLogger('chrome_test_server_spawner').setLevel(
      logging.DEBUG if args.verbose else logging.WARN)

  # Verbose SCP output can be useful at times but oftentimes is just too noisy.
  # Only enable it if -vv is passed.
  logging.getLogger('ssh').setLevel(
      logging.DEBUG if args.verbose else logging.WARN)


def InitializeTargetArgs():
  """Set args for all targets to default values. This is used by test scripts
     that have their own parser but still uses the target classes."""
  parser = argparse.ArgumentParser()
  AddCommonArgs(parser)
  AddTargetSpecificArgs(parser)
  return parser.parse_args([])


def GetDeploymentTargetForArgs(args):
  """Constructs a deployment target object using command line arguments.
     If needed, an additional_args dict can be used to supplement the
     command line arguments."""

  if args.device == 'custom':
    return _LoadTargetClass(args.custom_device_target).CreateFromArgs(args)

  if args.device:
    device = args.device
  else:
    device = 'fvdl' if args.target_cpu == 'x64' else 'qemu'

  return _LoadTargetClass(_GetPathToBuiltinTarget(device)).CreateFromArgs(args)
