#include "Vhub75e.h"
#include "devices.hpp"
#include "tests.hpp"
#include <array>
#include <catch2/catch_test_macros.hpp>
#include <catch2/generators/catch_generators_all.hpp>
#include <memory>
#include <vector>

TEST_CASE("HUB75E Driver Test") {
  auto fixture = VerilatorTestFixture<Vhub75e>();
  // very simple done checker.
  auto done_check = [](Vhub75e &dut, unsigned long time) {
    return dut.done == 1;
  };
  fixture.set_done_callback(done_check);
  const Vhub75e &dut = fixture.get();
  // stimulus to start the transaction.
  auto stim = std::make_shared<PulseStimulus>(dut.write_trig, 4);
  fixture.add_module(stim);
  fixture.set_timeout(250000);
  auto bram = std::make_shared<FakeBRAM>(1, dut.clk, dut.pixbuf_addr,
                                         dut.pixbuf_data);
  fixture.add_module(bram);
  auto display = std::make_shared<HUB75Reciever>(128, 64, dut);
  fixture.add_module(display);


  SECTION("Smoke Tests") {

    fixture.exec();

    CHECK(fixture.get_reason() ==
          VerilatorTestFixture<Vhub75e>::FinishReason::Ok);

    auto rows = display->get_past_rows();
    CHECK(rows.size() == 8);
    for (int i = 0; i < rows.size(); i++) {
      auto &[r0, r1] = rows[i];
      CHECK(r0.size() == 128);
      CHECK(r1.size() == 128);
    }
    // pulse width smoke tests.
    auto pulses = display->get_pulse_widths();
    REQUIRE(pulses.size() == rows.size());
    for (int i = 1; i < pulses.size(); i++) {
      REQUIRE(pulses[i] == pulses[i - 1] / 2);
    }
    auto [row0, row1] = display->transpose();
    REQUIRE(row0.size() == 128);
    REQUIRE(row1.size() == 128);
    auto ram_ref = bram->get();

    CAPTURE(row0);
    CHECK(std::equal(ram_ref.begin(), ram_ref.begin() + 128, row0.begin(),
                     row0.end()));
  }
  SECTION("Line Correctness") {
    // this is the part where we validate that the line in = line out.
    // we have to generate different values since the
    fixture.enable_trace("testing.vcd");
    // generate an entire block of RAM
    auto line = GENERATE(take(1, chunk(512, random(0, 0xFFFFFF))));
    std::copy(line.begin(), line.end(), bram->get().begin());

    fixture.exec();

    REQUIRE(fixture.get_reason() ==
          VerilatorTestFixture<Vhub75e>::FinishReason::Ok);

    auto [row0, row1] = display->transpose();
    REQUIRE(row0.size() == 128);
    REQUIRE(row1.size() == 128);
    auto ram_ref = bram->get();

    for (int i = 0; i < 128; i++) {
      CAPTURE(i);
      CAPTURE(ram_ref[i], row0[i]);
      CHECK(ram_ref[i] == row0[i]);
      CAPTURE(ram_ref[i+256], row1[i]);
      CHECK(ram_ref[i+256] == row1[i]);

    }
    // CHECK(std::equal(ram_ref.begin(), ram_ref.begin() + 128, row0.begin(),
    //                  row0.end()));
  }
}