# Copyright 2016 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 atexit
import logging
import optparse
import py_utils

from battor import battor_wrapper
from devil.android import battery_utils
from devil.android import device_utils
from devil.utils import battor_device_mapping
from devil.utils import find_usb_devices
from py_trace_event import trace_time
from systrace import trace_result
from systrace import tracing_agents


def try_create_agent(config):
  if config.from_file is not None:
    return None
  if config.battor:
    return BattOrTraceAgent()
  return None


class BattOrConfig(tracing_agents.TracingConfig):
  def __init__(self, battor_categories, serial_map, battor_path,
               battor, target, from_file, device_serial_number):
    tracing_agents.TracingConfig.__init__(self)
    self.battor_categories = battor_categories
    self.serial_map = serial_map
    self.battor_path = battor_path
    self.battor = battor
    self.target = target
    self.from_file = from_file
    self.device_serial_number = device_serial_number


def add_options(parser):
  options = optparse.OptionGroup(parser, 'BattOr trace options')
  options.add_option('--battor-categories', dest='battor_categories',
                     help='Select battor categories with a comma-delimited '
                     'list, e.g. --battor-categories=cat1,cat2,cat3')
  options.add_option('--serial-map', dest='serial_map',
                    default='serial_map.json',
                    help='File containing pregenerated map of phone serial '
                    'numbers to BattOr serial numbers.')
  options.add_option('--battor-path', dest='battor_path', default=None,
                    type='string', help='specify a BattOr path to use')
  options.add_option('--battor', dest='battor', default=False,
                    action='store_true', help='Use the BattOr tracing agent.')
  return options

def get_config(options):
  return BattOrConfig(
      options.battor_categories, options.serial_map, options.battor_path,
      options.battor, options.target, options.from_file,
      options.device_serial_number)

def _reenable_charging_if_needed(battery):
  if not battery.GetCharging():
    battery.SetCharging(True)
  logging.info('Charging status checked at exit.')


class BattOrTraceAgent(tracing_agents.TracingAgent):
  # Class representing tracing agent that gets data from a BattOr.
  # BattOrs are high-frequency power monitors used for battery testing.
  def __init__(self):
    super(BattOrTraceAgent, self).__init__()
    self._collection_process = None
    self._recording_error = None
    self._battor_wrapper = None
    self._battery_utils = None

  @staticmethod
  def _FindBattOrPath(config):
    battor_path = config.battor_path
    if not config.battor_path and not config.serial_map:
      device_tree = find_usb_devices.GetBusNumberToDeviceTreeMap()
      battors = battor_device_mapping.GetBattOrList(device_tree)
      assert len(battors) == 1, ('Must specify BattOr path if there is not '
                                 'exactly one')
      battor_path = battors[0]
    return battor_path

  @py_utils.Timeout(tracing_agents.START_STOP_TIMEOUT)
  def StartAgentTracing(self, config, timeout=None):
    """Starts tracing.

    Args:
        config: Tracing config.

    Raises:
        RuntimeError: If trace already in progress.
        AssertionError: If There is no BattOr path given and more
            than one BattOr is attached.
    """
    battor_path = self._FindBattOrPath(config)
    self._battor_wrapper = battor_wrapper.BattOrWrapper(
        target_platform=config.target,
        android_device=config.device_serial_number,
        battor_path=battor_path,
        battor_map_file=config.serial_map)

    dev_utils = device_utils.DeviceUtils(config.device_serial_number)
    self._battery_utils = battery_utils.BatteryUtils(dev_utils)
    self._battery_utils.SetCharging(False)
    atexit.register(_reenable_charging_if_needed, self._battery_utils)
    self._battor_wrapper.StartShell()
    self._battor_wrapper.StartTracing()
    return True

  @py_utils.Timeout(tracing_agents.START_STOP_TIMEOUT)
  def StopAgentTracing(self, timeout=None):
    """Stops tracing and collects the results asynchronously.

    Creates a new process that stops the tracing and collects the results.
    Returns immediately after the process is created (does not wait for
    trace results to be collected).
    """
    self._battor_wrapper.StopTracing()
    self._battery_utils.SetCharging(True)
    return True

  def SupportsExplicitClockSync(self):
    """Returns whether this function supports explicit clock sync."""
    return self._battor_wrapper.SupportsExplicitClockSync()

  def RecordClockSyncMarker(self, sync_id, did_record_sync_marker_callback):
    """Records a clock sync marker.

    Args:
        sync_id: ID string for clock sync marker.
        did_record_sync_marker_callback: Callback function to call after
        the clock sync marker is recorded.
    """
    ts = trace_time.Now()
    self._battor_wrapper.RecordClockSyncMarker(sync_id)
    did_record_sync_marker_callback(ts, sync_id)

  @py_utils.Timeout(tracing_agents.GET_RESULTS_TIMEOUT)
  def GetResults(self, timeout=None):
    """Waits until data collection is completed and get the trace data.

    The trace data is the data that comes out of the BattOr, and is in the
    format with the following lines:

    time current voltage <sync_id>

    where the sync_id is only there if a clock sync marker was recorded
    during that sample.

    time = time since start of trace (ms)
    current = current through battery (mA) - this can be negative if the
        battery is charging
    voltage = voltage of battery (mV)

    Returns:
      The trace data.
    """
    return trace_result.TraceResult(
        'powerTraceAsString', self._battor_wrapper.CollectTraceData())
