#ifndef __TB_ISP_H__
#define __TB_ISP_H__

#include <cstdint>
#include <systemc>

// Write Pictures
#include "bitmap_image.hpp"

SC_MODULE(TB_ISP) {
  sc_core::sc_in_clk clk;
  sc_core::sc_in<bool> rst;

  sc_core::sc_in<bool> in_ready;         // next module ready to receive data
  sc_core::sc_out<bool> out_valid;       // next module data valid signal
  sc_core::sc_out<uint32_t> out_data[3]; // next module receive data

  sc_core::sc_in<bool> in_valid;    // this module receive data valid signal
  sc_core::sc_out<bool> out_ready;  // this module ready to receive data
  sc_core::sc_in<uint32_t> in_data; // this module receive data

  const uint16_t IN_WIDTH;
  const uint16_t IN_HEIGHT;
  const uint32_t IN_SIZE;
  const uint16_t OUT_WIDTH;
  const uint16_t OUT_HEIGHT;
  const uint32_t OUT_SIZE;
  const uint32_t FLAMES;
  const std::string OUT_DIR;

  bool is_done;                        // when receive all data
  std::vector<uint16_t> image;         // the data of image
  std::vector<uint32_t> process_image; // after isp process, the data of image

  SC_CTOR(TB_ISP, const uint16_t in_width, const uint16_t in_height,
          const uint16_t out_width, const uint16_t out_height,
          const uint32_t cnt_flame, const std::string& out_dir)
      : IN_WIDTH(in_width), IN_HEIGHT(in_height), IN_SIZE(in_width * in_height),
        OUT_WIDTH(out_width), OUT_HEIGHT(out_height),
        OUT_SIZE(out_width * out_height), FLAMES(cnt_flame),
        OUT_DIR(out_dir),
        process_image(std::vector<uint32_t>(out_width * out_height, 0)) {
    SC_CTHREAD(sendData, clk.pos()); // when clk posedge, exec sendData
    reset_signal_is(rst, true);      // set rst signal

    SC_CTHREAD(readData, clk.pos());
    reset_signal_is(rst, true); // set rst signal
  }

  void sendData(void) {
    // init var
    uint16_t pos_x = 0, pos_y = 0, cnt_flame = 0;
    bool is_finish = false; // when send all data
    // reset
    out_valid = false;
    for (auto &data : out_data)
      data = 0;

    while (true) {
      if (in_ready && !is_finish) {
        // valid and send data
        out_valid = true;
        out_data[0] = image[(pos_y + 0) * IN_WIDTH + pos_x];
        out_data[1] = image[(pos_y + 1) * IN_WIDTH + pos_x];
        out_data[2] = image[(pos_y + 2) * IN_WIDTH + pos_x];

        // print data
        std::printf("x=%4d, y=%4d, data=0x%04x\t", pos_x, pos_y,
                    image[(pos_y + 0) * IN_WIDTH + pos_x]);
        std::printf("x=%4d, y=%4d, data=0x%04x\t", pos_x, pos_y,
                    image[(pos_y + 1) * IN_WIDTH + pos_x]);
        std::printf("x=%4d, y=%4d, data=0x%04x\n", pos_x, pos_y,
                    image[(pos_y + 2) * IN_WIDTH + pos_x]);
        pos_x++;

        // calculate position and recognize when to finish
        if (pos_x >= IN_WIDTH) {
          pos_x = 0;
          pos_y++;
        }
        if (pos_y >= IN_HEIGHT - 1) {
          pos_y = 0;
          cnt_flame++;
        }
        if (cnt_flame >= FLAMES) {
          is_finish = true;
        }
      } else {
        out_valid = false;
      }

      // wait for next clk
      wait();
    }
  }

  void readData(void) {
    // init local var
    uint16_t pos_x = 0, pos_y = 0, cnt_flame = 0;
    uint32_t last_data = 0, cnt = 0;
    bool is_finish = false;
    // reset
    out_ready = false;
    is_done = false;

    while (true) {
      if (!is_finish) {
        out_ready = true;

        // when data valid, write it down
        if (in_valid) {
          process_image[pos_y * OUT_WIDTH + pos_x] = in_data;

          // calculate position
          pos_x++;

          if (pos_x >= OUT_WIDTH) {
            pos_x = 0;
            pos_y++;
          }
          if (pos_y >= OUT_HEIGHT) {
            pos_y = 0;
            saveData(
                ("output_img_" + std::to_string(cnt_flame) + ".bmp").c_str());
            cnt_flame++;
          }
          if (cnt_flame >= FLAMES) {
            is_finish = true;
          }
        }
      } else {
        out_ready = false;
      }

      // when no data send, give finish signal
      if (is_finish && (last_data == in_data)) {
        cnt++;
        if (cnt >= 100000L) { // when receive many times the same data
          is_done = true;
          std::printf("Finish Reading data; pos_x = %d, pos_y = %d\n", pos_x,
                      pos_y);
        }
      } else {
        cnt = 0;
      }
      last_data = in_data;

      // wait for next clk
      wait();
    }
  }

  bool saveData(const char *name) {
    bool ret = true;

    // Check Image Size
    if (process_image.size() > OUT_SIZE) {
      std::cout << "Process Image Over Size!!!\n"
                << "Image Size:" << process_image.size() << "\n";
      return false;
    }

    // Write BMP image
    bitmap_image bmp(OUT_WIDTH, OUT_HEIGHT);
    if (!bmp) {
      std::cout << "Output Image Open Failed!!!\n";
      return false;
    }
    for (int y = 0; y < OUT_HEIGHT; y++)
      for (int x = 0; x < OUT_WIDTH; x++)
        bmp.set_pixel(x, y,
                      (process_image[y * OUT_WIDTH + x] & 0x00ff0000) >> 16,
                      (process_image[y * OUT_WIDTH + x] & 0x0000ff00) >> 8,
                      (process_image[y * OUT_WIDTH + x] & 0x000000ff) >> 0);
    bmp.save_image(std::string(OUT_DIR) + name);
    return ret;
  }
};

#endif
