#include <iostream>
#include <iomanip>
#include "Vbit_repr_matrix.h"
#include <verilated.h>
#include <verilated_vcd_c.h>

// secret key
int S[2][8] = { {0x6, 0x5, 0x0, 0x3, 0x9, 0x3, 0x3, 0x6},   // 0-7
			    {0x9, 0x7, 0x6, 0x8, 0x2, 0x0, 0x6, 0x1}}; // 8-15

// l = 4
int S_star[2][32] = { {0x30, 0x18, 0xc, 0x6,   // 0-3
                0x28, 0x14, 0xa, 0x5,               // 4-7
                0x0, 0x0, 0x0, 0x0,                 // 7-11
                0x18, 0xc, 0x6, 0x3,                // 12-15
                0x48, 0x24, 0x12, 0x9,               // 16-19
                0x18, 0xc, 0x6, 0x3,                // 20-23
                0x18, 0xc, 0x6, 0x3,                // 24-27
                0x30, 0x18, 0xc, 0x6},              // 28-31
			   {0x48, 0x24, 0x12, 0x9,               // 0-3
                0x38, 0x1c, 0xe, 0x7,               // 4-7
                0x30, 0x18, 0xc, 0x6,               // 7-11
                0x40, 0x20, 0x10, 0x8,              // 12-15
                0x10, 0x8, 0x4, 0x2,                // 16-19
                0x0, 0x0, 0x0, 0x0,                 // 20-23
                0x30, 0x18, 0xc, 0x6,               // 24-27
                0x8, 0x4, 0x2, 0x1}};               // 28-31

int main(int argc, const char ** argv, const char ** env) {
  Verilated::commandArgs(argc, argv);

  // Treat the argument on the command-line as the place to start
  int width, length, ell;
  if (argc > 1 && argv[1][0] != '+') {
    width = atoi(argv[1]);
    length = atoi(argv[2]);
    ell = atoi(argv[3]);
  } else {
    width = 8; // Default
    length = 2; // Default
    ell = 4;  // Default
  }

  Vbit_repr_matrix * dut = new Vbit_repr_matrix;  // Instantiate a new module

  // Enable dumping a VCD file

  Verilated::traceEverOn(true);
  VerilatedVcdC * tfp = new VerilatedVcdC;
  dut->trace(tfp, 99);
  tfp->open("bit_repr_matrix.vcd");

  dut->clk = 0;
  dut->width = width;
  dut->length = length;
  dut->start = 0;
  dut->ell = ell;
  
  int time;
  std::cout << "width: " << width;
  std::cout << std::endl;
  std::cout << "length: " << length;
  std::cout << std::endl;
  std::cout << "ell: " << ell;
  std::cout << std::endl;
  int i = 0;
  int r = 0;
  int err = 0;
  int start_stamp = 100;
  // let run ((width * ell) * length) cycles for computations
  // extra 16 cycles are needed in addition for computations
  int read_stamp = start_stamp + length * width * ell * 20 + 16 * 20;
  
  for (time = 0 ; time < 10000 ; time += 10) {
    dut->clk = ((time % 20) >= 10) ? 1 : 0; // Simulate a 50 MHz clock
    if (time == 20) dut->reset = 1; // Handle "reset" on for four cycles
    if (time == 100) {
      dut->reset = 0;
      dut->start = 1;   // Put "start" on
    }

    // take inputs for (length) rows
    // for each row, take inputs for (width) cycles
    if (time >= start_stamp && time <= start_stamp + width * length * 20 && dut->clk == 1) {
        dut->S_ij = S[r][i];
        std::cout << "input received: " << (int) S[r][i];
        std::cout << std::endl;
        i += 1;
        if (i == width) {
            i = 0;
            r += 1;
            if (r == length) {
                r = 0;
            }
        }
    }

    if (time == start_stamp + width * length * 20) {
        dut->start = 0;
        int i = 0;
        int r = 0;
    }

    dut->eval();     // Run the simulation for a cycle
    tfp->dump(time); // Write the VCD file for this cycle

    // compare outputs for (length) rows
    // for each row, compare outputs for (width * ell) cycles
    if (time >= read_stamp && time <= read_stamp + width * length * ell * 20 && dut->clk == 1) {
        std::cout << ' ' <<  std::setfill('0') << std::setw(0) << (int) dut->data_out;
        if (dut->data_out == S_star[r][i])
            std::cout << " OK";
        else {
            std::cout << " INCORRECT expected " << std::setfill('0') << (int) S_star[r][i];
            err += 1;
        }
        std::cout << std::endl;
        i += 1;
        if (i == width * ell) {
            i = 0;
            r += 1;
            if (r == length) {
                r = 0;
            }
        }
    }

    if (dut->done) break;
  }

  tfp->close(); // Stop dumping the VCD file
  delete tfp;

  dut->final(); // Stop the simulation
  delete dut;

  return 0;
}