/* -*- mode: c++ -*-
 * Copyright (C) 2020  Eric Paniagua (epaniagua@google.com)
 *
 * This program is free software: you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free Software
 * Foundation, version 3.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program. If not, see <http://www.gnu.org/licenses/>.
 */

#pragma once

// IWYU pragma: no_include <__memory/unique_ptr.h>

#include <cstddef>           // for size_t
#include <cstdint>           // for uint32_t, int8_t, uint8_t
#include <initializer_list>  // for initializer_list
#include <memory>            // IWYU pragma: keep
#include <set>               // for set
#include <vector>            // for vector

#include "kaleidoscope/KeyAddr.h"            // for KeyAddr
#include "kaleidoscope/key_defs.h"           // for Key
#include "testing/ExpectedKeyboardReport.h"  // for ExpectedKeyboardReport
#include "testing/ExpectedMouseReport.h"     // for ExpectedMouseReport
#include "testing/HIDState.h"                // for HIDState
#include "testing/SimHarness.h"              // for SimHarness
#include "testing/State.h"                   // for State
#include "testing/gtest.h"                   // for Test
#include "testing/iostream.h"                // for string

namespace kaleidoscope {
namespace testing {

// -----------------------------------------------------------------------------
// Utility classes for use in `PressKey()`/`ReleaseKey()` & `ExpectReport()`
// method invocations. These make those calls simpler by providing
// differentiated types for those polymorphic functions.
class AddKeycodes : public std::set<Key> {
 public:
  AddKeycodes(std::initializer_list<Key> list)
    : std::set<Key>(list) {}
};
class RemoveKeycodes : public std::set<Key> {
 public:
  RemoveKeycodes(std::initializer_list<Key> list)
    : std::set<Key>(list) {}
};

class Keycodes : public std::set<Key> {
 public:
  Keycodes(std::initializer_list<Key> list)
    : std::set<Key>(list) {}
};


// -----------------------------------------------------------------------------
// The base class for testcases
class VirtualDeviceTest : public ::testing::Test {
 protected:
  void SetUp();

  std::unique_ptr<State> RunCycle();

  SimHarness sim_;

  // ---------------------------------------------------------------------------
  // A representation of the set of observed HID reports accumulated by the
  // simulator as a testcase executes. It starts out empty, and a call to
  // `LoadState()` is required in order to load those reports into this
  // `output_state_` variable so that they can be used.
  std::unique_ptr<State> output_state_ = nullptr;

  // Load any accumulated observed HID reports from the simulator (since the
  // previous call to this function).
  void LoadState();

  // Clear previous state. This should be used at the beginning of each
  // `TEST_F()` call to clear out any accumulated state in the input and output
  // queues.
  void ClearState();

  // Get a pointer to the current list of observed HID reports
  const HIDState *HIDReports() const;

  // Get the timestamp of a logged Keyboard HID report
  uint32_t ReportTimestamp(size_t index) const;

  // ---------------------------------------------------------------------------
  // A vector of timestamps for input events. Calls to `PressKey()` &
  // `ReleaseKey()` append timestamps to it when called.
  std::vector<uint32_t> input_timestamps_ = {};

  // Get the timestamp of a logged input event from `output_state_`
  uint32_t EventTimestamp(size_t index) const;

  // ---------------------------------------------------------------------------
  // Press/release a keyswitch & log the input event timestamp
  void PressKey(KeyAddr addr);
  void ReleaseKey(KeyAddr addr);


  // ---------------------------------------------------------------------------
  // The following functions all add an expected value of HID report to the
  // queue. They specify modifications to the current (expected) report, and a
  // message to display if verification fails. Some versions allow multiple
  // keycode changes in a single report; others only a single keycode. Some run
  // the simulator for a specified number of milliseconds or cycles first. These
  // expected-value reports are all stored in a vector:
  std::vector<ExpectedKeyboardReport> expected_keyboard_reports_ = {};

  void ExpectKeyboardReport(AddKeycodes added_keys,
                            RemoveKeycodes removed_keys,
                            std::string description);

  void ExpectKeyboardReport(Keycodes added_keys, std::string description);
  void ExpectKeyboardReport(AddKeycodes added_keys, std::string description);
  void ExpectKeyboardReport(RemoveKeycodes removed_keys, std::string description);

  std::vector<ExpectedMouseReport> expected_mouse_reports_ = {};

  void ExpectMouseReport(uint8_t buttons, int8_t x, int8_t y, int8_t v, int8_t h, std::string description);

  // ---------------------------------------------------------------------------
  std::set<uint8_t> current_keyboard_keycodes_ = {};
  // Manage the set of keycodes expected in the next report
  void ClearKeyboardReport();
  void AddToKeyboardReport(Key key);
  void RemoveFromKeyboardReport(Key key);

  // ---------------------------------------------------------------------------
  // Compare accumulated observed and expected reports, matching both timestamps
  // and keycodes.
  void CheckReports() const;
  void CheckKeyboardReports() const;
  void CheckMouseReports() const;
};

}  // namespace testing
}  // namespace kaleidoscope