#!/usr/bin/env python
# Copyright 2015 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

import json
import os.path
import sys
import optparse

_script_path = os.path.realpath(__file__)

sys.path.insert(0, os.path.normpath(_script_path + "/../../json_comment_eater"))
try:
  import json_comment_eater
finally:
  sys.path.pop(0)

sys.path.insert(0, os.path.normpath(_script_path + "/../../json_to_struct"))
try:
  import json_to_struct
finally:
  sys.path.pop(0)

_platforms = [
  'android',
  'android_webview',
  'chromeos',
  'fuchsia',
  'ios',
  'linux',
  'mac',
  'windows',
]

_form_factors = [
  'desktop',
  'phone',
  'tablet',
]


# Convert a platform argument to the matching Platform enum value in
# components/variations/proto/study.proto.
def _PlatformEnumValue(platform):
  assert platform in _platforms
  return 'Study::PLATFORM_' + platform.upper()


def _FormFactorEnumValue(form_factor):
  assert form_factor in _form_factors
  return 'Study::' + form_factor.upper()


def _Load(filename):
  """Loads a JSON file into a Python object and return this object."""
  with open(filename, 'r') as handle:
    result = json.loads(json_comment_eater.Nom(handle.read()))
  return result


def _LoadFieldTrialConfig(filename, platforms):
  """Loads a field trial config JSON and converts it into a format that can be
  used by json_to_struct.
  """
  return _FieldTrialConfigToDescription(_Load(filename), platforms)


def _CreateExperiment(
  experiment_data, platforms, form_factors, is_low_end_device
):
  """Creates an experiment dictionary with all necessary information.

  Args:
    experiment_data: An experiment json config.
    platforms: A list of platforms for this trial. This should be
      a subset of |_platforms|.
    form_factors: A list of form factors for this trial. This should be
      a subset of |_form_factors|.
    is_low_end_device: An optional parameter. This can either be True or
      False. None if not specified.

  Returns:
    An experiment dict.
  """
  experiment = {
    'name': experiment_data['name'],
    'platforms': [_PlatformEnumValue(p) for p in platforms],
    'form_factors': [_FormFactorEnumValue(f) for f in form_factors],
  }
  if is_low_end_device is not None:
    experiment['is_low_end_device'] = str(is_low_end_device).lower()
  forcing_flags_data = experiment_data.get('forcing_flag')
  if forcing_flags_data:
    experiment['forcing_flag'] = forcing_flags_data
  min_os_version_data = experiment_data.get('min_os_version')
  if min_os_version_data:
    experiment['min_os_version'] = min_os_version_data
  disable_benchmark_data = experiment_data.get('disable_benchmarking')
  if disable_benchmark_data:
    experiment['disable_benchmarking'] = disable_benchmark_data
  hardware_classes_data = experiment_data.get('hardware_classes')
  if hardware_classes_data:
    experiment['hardware_classes'] = hardware_classes_data
  exclude_hardware_classes_data = experiment_data.get(
    'exclude_hardware_classes'
  )
  if exclude_hardware_classes_data:
    experiment['exclude_hardware_classes'] = exclude_hardware_classes_data
  hardware_manufacturers_data = experiment_data.get('hardware_manufacturers')
  if hardware_manufacturers_data:
    experiment['hardware_manufacturers'] = hardware_manufacturers_data
  exclude_hardware_manufacturers_data = experiment_data.get(
    'exclude_hardware_manufacturers'
  )
  if exclude_hardware_manufacturers_data:
    experiment['exclude_hardware_manufacturers'] = (
      exclude_hardware_manufacturers_data
    )
  params_data = experiment_data.get('params')
  if params_data:
    experiment['params'] = [
      {'key': param, 'value': params_data[param]}
      for param in sorted(params_data.keys())
    ]
  enable_features_data = experiment_data.get('enable_features')
  if enable_features_data:
    experiment['enable_features'] = enable_features_data
  disable_features_data = experiment_data.get('disable_features')
  if disable_features_data:
    experiment['disable_features'] = disable_features_data
  return experiment


def _CreateTrial(study_name, experiment_configs, platforms):
  """Returns the applicable experiments for |study_name| and |platforms|.

  This iterates through all of the experiment_configs for |study_name|
  and picks out the applicable experiments based off of the valid platforms
  and device type settings if specified.
  """
  experiments = []
  for config in experiment_configs:
    platform_intersection = [p for p in platforms if p in config['platforms']]

    if platform_intersection:
      experiments += [
        _CreateExperiment(
          e,
          platform_intersection,
          config.get('form_factors', []),
          config.get('is_low_end_device'),
        )
        for e in config['experiments']
      ]
  return {
    'name': study_name,
    'experiments': experiments,
  }


def _GenerateTrials(config, platforms):
  for study_name in sorted(config.keys()):
    study = _CreateTrial(study_name, config[study_name], platforms)
    # To avoid converting studies with empty experiments (e.g. the study doesn't
    # apply to the target platforms), this generator only yields studies that
    # have non-empty experiments.
    if study['experiments']:
      yield study


def ConfigToStudies(config, platforms):
  """Returns the applicable studies from config for the platforms."""
  return [study for study in _GenerateTrials(config, platforms)]


def _FieldTrialConfigToDescription(config, platforms):
  return {
    'elements': {
      'kFieldTrialConfig': {'studies': ConfigToStudies(config, platforms)}
    }
  }


def main(arguments):
  parser = optparse.OptionParser(
    description='Generates a struct from a JSON description.',
    usage='usage: %prog [option] -s schema -p platform description',
  )
  parser.add_option(
    '-b', '--destbase', help='base directory of generated files.'
  )
  parser.add_option(
    '-d',
    '--destdir',
    help='directory to output generated files, relative to destbase.',
  )
  parser.add_option(
    '-n',
    '--namespace',
    help='C++ namespace for generated files. e.g search_providers.',
  )
  parser.add_option(
    '-p',
    '--platform',
    action='append',
    choices=_platforms,
    help='target platform for the field trial, mandatory.',
  )
  parser.add_option(
    '-s', '--schema', help='path to the schema file, mandatory.'
  )
  parser.add_option('-o', '--output', help='output filename, mandatory.')
  parser.add_option(
    '-j',
    '--java',
    action='store_true',
    help='specify this flag to generate a java class.',
  )
  parser.add_option('-y', '--year', help='year to put in the copy-right.')
  (opts, args) = parser.parse_args(args=arguments)

  if not opts.schema:
    parser.error('You must specify a --schema.')

  if not opts.platform:
    parser.error('You must specify at least 1 --platform.')

  description_filename = os.path.normpath(args[0])
  shortroot = opts.output
  if opts.destdir:
    output_root = os.path.join(os.path.normpath(opts.destdir), shortroot)
  else:
    output_root = shortroot

  if opts.destbase:
    basepath = os.path.normpath(opts.destbase)
  else:
    basepath = ''

  schema = _Load(opts.schema)
  description = _LoadFieldTrialConfig(description_filename, opts.platform)
  json_to_struct.GenerateStruct(
    basepath,
    output_root,
    opts.namespace,
    schema,
    description,
    os.path.split(description_filename)[1],
    os.path.split(opts.schema)[1],
    opts.year,
  )

  # TODO(peilinwang) filter the schema by platform, form_factor, etc.
  if opts.java:
    json_to_struct.GenerateClass(
      basepath,
      output_root,
      opts.namespace,
      schema,
      description,
      os.path.split(description_filename)[1],
      os.path.split(opts.schema)[1],
      opts.year,
    )


if __name__ == '__main__':
  main(sys.argv[1:])
