// Copyright 2015 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.

#ifndef TOOLS_BATTOR_AGENT_BATTOR_AGENT_H_
#define TOOLS_BATTOR_AGENT_BATTOR_AGENT_H_

#include <map>

#include "base/cancelable_callback.h"
#include "base/macros.h"
#include "base/memory/ref_counted.h"
#include "base/memory/weak_ptr.h"
#include "base/threading/thread_checker.h"
#include "tools/battor_agent/battor_connection.h"
#include "tools/battor_agent/battor_error.h"

namespace battor {

// A BattOrAgent is a class used to asynchronously communicate with a BattOr for
// the purpose of collecting power samples. A BattOr is an external USB device
// that's capable of recording accurate, high-frequency (2000Hz) power samples.
//
// The serial connection is automatically opened when the first command
// (e.g. StartTracing(), StopTracing(), etc.) is issued, and automatically
// closed when either StopTracing() or the destructor is called. For Telemetry,
// this means that the connection must be reinitialized for every command that's
// issued because a new BattOrAgent is constructed. For Chromium, we use the
// same BattOrAgent for multiple commands and thus avoid having to reinitialize
// the serial connection.
//
// This class is NOT thread safe. Any interactions with this class that involve
// IO (i.e. any interactions that require a callback) must be done from the
// same IO thread, which must also have a running MessageLoop.
class BattOrAgent : public BattOrConnection::Listener,
                    public base::SupportsWeakPtr<BattOrAgent> {
 public:
  // The listener interface that must be implemented in order to interact with
  // the BattOrAgent.
  class Listener {
   public:
    virtual void OnStartTracingComplete(BattOrError error) = 0;
    virtual void OnStopTracingComplete(const std::string& trace,
                                       BattOrError error) = 0;
    virtual void OnRecordClockSyncMarkerComplete(BattOrError error) = 0;
  };

  BattOrAgent(
      const std::string& path,
      Listener* listener,
      scoped_refptr<base::SingleThreadTaskRunner> file_thread_task_runner,
      scoped_refptr<base::SingleThreadTaskRunner> ui_thread_task_runner);
  virtual ~BattOrAgent();

  void StartTracing();
  void StopTracing();
  void RecordClockSyncMarker(const std::string& marker);

  // Returns whether the BattOr is able to record clock sync markers in its own
  // trace log.
  static bool SupportsExplicitClockSync() { return true; }

  // BattOrConnection::Listener implementation.
  void OnConnectionOpened(bool success) override;
  void OnBytesSent(bool success) override;
  void OnMessageRead(bool success,
                     BattOrMessageType type,
                     std::unique_ptr<std::vector<char>> bytes) override;

 protected:
  // The connection that knows how to communicate with the BattOr in terms of
  // protocol primitives. This is protected so that it can be replaced with a
  // fake in testing.
  std::unique_ptr<BattOrConnection> connection_;

  // Timeout for when an action isn't completed within the allotted time. This
  // is virtual and protected so that timeouts can be disabled in testing. The
  // testing task runner that runs delayed tasks immediately deals poorly with
  // timeouts posted as future tasks.
  virtual void OnActionTimeout();

 private:
  enum class Command {
    INVALID,
    START_TRACING,
    STOP_TRACING,
    RECORD_CLOCK_SYNC_MARKER,
  };

  enum class Action {
    INVALID,

    // Actions required to connect to a BattOr.
    REQUEST_CONNECTION,

    // Actions required for starting tracing.
    SEND_INIT,
    READ_INIT_ACK,
    SEND_SET_GAIN,
    READ_SET_GAIN_ACK,
    SEND_START_TRACING,
    READ_START_TRACING_ACK,

    // Actions required for stopping tracing.
    SEND_EEPROM_REQUEST,
    READ_EEPROM,
    SEND_SAMPLES_REQUEST,
    READ_CALIBRATION_FRAME,
    READ_DATA_FRAME,

    // Actions required for recording a clock sync marker.
    SEND_CURRENT_SAMPLE_REQUEST,
    READ_CURRENT_SAMPLE,
  };

  // Performs an action.
  void PerformAction(Action action);
  // Performs an action after a delay.
  void PerformDelayedAction(Action action, base::TimeDelta delay);



  // Requests a connection to the BattOr.
  void BeginConnect();

  // Sends a control message over the connection.
  void SendControlMessage(BattOrControlMessageType type,
                          uint16_t param1,
                          uint16_t param2);

  // Completes the command with the specified error.
  void CompleteCommand(BattOrError error);

  // Returns a formatted version of samples_ with timestamps and real units.
  std::string SamplesToString();

  // The listener that handles the commands' results. It must outlive the agent.
  Listener* listener_;

  // The last action executed by the agent. This should only be updated in
  // PerformAction().
  Action last_action_;

  // The tracing command currently being executed by the agent.
  Command command_;

  // A map from the sample number (including samples from the calibration frame)
  // to the ID of the clock sync marker that is associated with that sample
  // number. If we ever have to store a large number of these, consider using an
  // unordered map.
  std::map<uint32_t, std::string> clock_sync_markers_;

  // The clock sync marker being recorded (if we're currently recording one).
  std::string pending_clock_sync_marker_;

  // The time at which the last clock sync marker was recorded.
  base::TimeTicks last_clock_sync_time_;

  // Checker to make sure that this is only ever called on the IO thread.
  base::ThreadChecker thread_checker_;

  // The BattOr's EEPROM (which is required for calibration).
  std::unique_ptr<BattOrEEPROM> battor_eeprom_;

  // The first frame (required for calibration).
  std::vector<RawBattOrSample> calibration_frame_;

  // The actual data samples recorded.
  std::vector<RawBattOrSample> samples_;

  // The expected sequence number of the next frame. We use this to ensure that
  // we receive frames in order.
  uint32_t next_sequence_number_;

  // The number of times we've attempted to init the BattOr.
  uint8_t num_init_attempts_;

  // The number of times that we've attempted to read the last message.
  uint8_t num_read_attempts_;

  // The timeout that's run when an action times out.
  base::CancelableClosure timeout_callback_;

  DISALLOW_COPY_AND_ASSIGN(BattOrAgent);
};

}  // namespace battor

#endif  // TOOLS_BATTOR_AGENT_BATTOR_AGENT_H_
