// For std::unique_ptr
#include <memory>

// SystemC global header
#include <systemc>

// Include common routines
#include <sys/stat.h>  // mkdir
#include <verilated.h>
#include <verilated_vcd_sc.h>

// Include model header, generated from Verilating "demo.v"
#include "Vdemosaic2.h"

// Handle file
#include <fstream>
#include <iostream>

#define IM_WIDTH 512
#define IM_HEIGHT 256
#define IM_SIZE (IM_WIDTH * IM_HEIGHT)

using namespace std;
using namespace sc_core;
using namespace sc_dt;

int sc_main(int argc, char* argv[]) {
    // Open image
    ifstream in_image;
    ofstream out_image;
    in_image.open("./transform/test.bin", ios::in | ios::binary);
    out_image.open("./transform/out.bin", ios::out | ios::binary);
    if (!in_image.is_open()) {
        cout << "Open image fail" << endl;
        exit(0);
    } else {
        cout << "Ready to sim" << endl;
    }

    // Read image
    uint8_t buf[IM_SIZE * 2] = {0};
    in_image.read((char*)buf, IM_SIZE * 2);
    // for (uint32_t i = 0; i < IM_SIZE * 2; i++)
    //     printf("0x%02x\t", buf[i]);
    in_image.close();
    // Reshape data
    uint16_t image[IM_HEIGHT][IM_WIDTH] = {0};
    uint32_t i = 0;
    for (int y = 0; y < IM_HEIGHT; y++) {
        for (int x = 0; x < IM_WIDTH; x++) {
            image[y][x] = (uint16_t)buf[i] + ((uint16_t)buf[i + 1] << 8);
            i++;
        }
    }

    // This is a more complicated example, please also see the simpler
    // examples/make_hello_c.

    // Create logs/ directory in case we have traces to put under it
    Verilated::mkdir("logs");

    // Set debug level, 0 is off, 9 is highest presently used
    // May be overridden by commandArgs argument parsing
    Verilated::debug(0);

    // Randomization reset policy
    // May be overridden by commandArgs argument parsing
    Verilated::randReset(2);

    // Before any evaluation, need to know to calculate those signals only used
    // for tracing
    Verilated::traceEverOn(true);

    // Pass arguments so Verilated code can see them, e.g. $value$plusargs
    // This needs to be called before you create any model
    Verilated::commandArgs(argc, argv);

    // General logfile
    std::ios::sync_with_stdio();

    // Define clocks
    sc_clock clk{"clk", 10, SC_NS, 0.5, 3, SC_NS, true};

    // Define interconnect
    sc_signal<bool> reset;

    sc_signal<bool> in_en;
    sc_signal<bool> in_que;
    sc_signal<uint32_t> data_in[3];

    sc_signal<bool> out_en;
    sc_signal<uint32_t> out_r, out_g, out_b;

    // Construct the Verilated model, from inside Vtop.h
    // Using unique_ptr is similar to "Vtop* top = new Vtop" then deleting at
    // end
    const std::unique_ptr<Vdemosaic2> demo{new Vdemosaic2{"demo"}};

    // Attach Vtop's signals to this upper model
    demo->clk(clk);
    demo->reset(reset);
    demo->data_en(in_en);
    demo->data_que(in_que);
    demo->data_in[0](data_in[0]);
    demo->data_in[1](data_in[1]);
    demo->data_in[2](data_in[2]);
    demo->out_en(out_en);
    demo->out_r(out_r);
    demo->out_g(out_g);
    demo->out_b(out_b);

    // You must do one evaluation before enabling waves, in order to allow
    // SystemC to interconnect everything for testing.
    sc_start(SC_ZERO_TIME);

    // If verilator was invoked with --trace argument,
    // and if at run time passed the +trace argument, turn on tracing
    VerilatedVcdSc* tfp = nullptr;
    const char* flag = Verilated::commandArgsPlusMatch("trace");
    if (flag && 0 == std::strcmp(flag, "+trace")) {
        std::cout << "Enabling waves into logs/vlt_dump.vcd...\n";
        tfp = new VerilatedVcdSc;
        demo->trace(tfp, 99);  // Trace 99 levels of hierarchy
        Verilated::mkdir("logs");
        tfp->open("logs/vlt_dump.vcd");
    }

    // Simulate until $finish
    bool flag_posedge = 0;
    bool clk_last = 0, clk_now = 0;
    uint16_t pos_x = 0, pos_y = 0;
    uint16_t out[IM_SIZE] = {0};
    uint32_t out_head = 0;
    while (!Verilated::gotFinish()) {
        // Flush the wave files each cycle so we can immediately see the output
        // Don't do this in "real" programs, do it in an abort() handler instead
        if (tfp) tfp->flush();

        // Apply inputs
        if (sc_time_stamp() < sc_time(10, SC_NS)) {
            reset.write(1);  // Assert reset
        } else {
            reset.write(0);  // Deassert reset
        }

        // Clock posedge generatre
        clk_now = clk.read();
        if (!clk_last && clk_now)
            flag_posedge = 1;
        clk_last = clk_now;

        // Send image data and Read RGB image data
        if (sc_time_stamp() > sc_time(10, SC_NS) && flag_posedge) {
            flag_posedge = 0;
            // Send image data to demosaic
            if (in_que.read() && pos_y < IM_HEIGHT - 2) {
                in_en.write(1);

                printf("x=%3d, y=%3d, data=0x%04x\t", pos_x, pos_y, image[pos_y + 0][pos_x]);
                printf("x=%3d, y=%3d, data=0x%04x\t", pos_x, pos_y, image[pos_y + 1][pos_x]);
                printf("x=%3d, y=%3d, data=0x%04x\n", pos_x, pos_y, image[pos_y + 2][pos_x]);

                data_in[0].write(image[pos_y + 0][pos_x++]);
                data_in[1].write(image[pos_y + 1][pos_x++]);
                data_in[2].write(image[pos_y + 2][pos_x++]);

                if (pos_x >= IM_WIDTH) {
                    pos_x = 0;
                    pos_y++;
                }
            } else {
                in_en.write(0);
            }

            // Read image data
            if (out_en.read()) {
                out[out_head++] = ((uint8_t)(out_r.read() * 5 / 12) << 10) +
                                  ((uint8_t)(out_g.read() * 5 / 12) << 5) +
                                  ((uint8_t)(out_b.read() * 5 / 12) << 0);
            }
        }

        if (sc_time_stamp() > sc_time(2600, SC_US))
            break;
        // Simulate 1ns
        sc_start(1, SC_NS);
    }

    // Final model cleanup
    demo->final();

    // Close trace if opened
    if (tfp) {
        tfp->close();
        tfp = nullptr;
    }

    // Save final output image
    for (uint32_t i = 0; i < IM_SIZE; i++) {
        buf[i * 2] = (out[i] & 0xffff0000) >> 16;
        buf[i * 2 + 1] = (out[i] & 0x0000ffff);
    }
    out_image.write((const char*)buf, 2 * IM_SIZE);
    out_image.close();

    // Return good completion status
    return 0;
}
