// Filename : vga_controller.cpp
//
// This file was initially generated by SpaceStudio but is meant to be modified
// by the user.
#include "vga_controller.h"
#include "spacecomp_pre_cpp.h"

#include <chrono>
#include <thread>

// Do NOT move macros, functions and includes beginning with "spacecomp".

vga_controller::vga_controller(SPACECOMP_CTOR_PARAMS(vga_controller, INDEX))
	: sdevice(SPACECOMP_SDBASE_ARGS(vga_controller, INDEX))
	, SPACECOMP_START_ILIST(vga_controller, INDEX)
	, m_is_ready(true)
	, m_frame_count(0)
	, m_vga_controller(WIDTH_VGA, HEIGHT_VGA, REFRESH_RATE)
	// Other initializers may go here
{
	m_current_frame = new rgba*[HEIGHT_VGA];

	for(int i=0; i<HEIGHT_VGA; i++)
		m_current_frame[i] = new rgba[WIDTH_VGA];

	/* screen is black by default */
	for (int i=0; i<HEIGHT_VGA; i++)
		for (int j=0; j<WIDTH_VGA; j++) {
			m_current_frame[i][j].red = 0;
			m_current_frame[i][j].green = 0;
			m_current_frame[i][j].blue = 0;
			m_current_frame[i][j].alpha = 0;
		}

	m_refresh_rate = sc_core::sc_time(double((double)1/(double)REFRESH_RATE), sc_core::SC_SEC);

	SC_THREAD(refresh);

	set_stack_size(0x64000);
}

vga_controller::~vga_controller() {
	if (m_current_frame != 0) {
		for(int i=0; i<HEIGHT_VGA; i++)
			delete [] m_current_frame[i];
		delete [] m_current_frame;
	}

	for (std::vector<rgba**>::iterator it=m_frames.begin(); it<m_frames.end(); it++) {
		for(int i=0; i<HEIGHT_VGA; i++)
				delete [] (*(it))[i];
			delete [] *(it);
	}
}

void vga_controller::end_of_simulation() {
	if (m_frames.size() == 0) {
		create_capture();
	}
	m_vga_controller.request_display_frame(m_frames.back());

	if (is_verbose()) {
		play_frames();
		//save_bitmap();
	}
}

void vga_controller::save_bitmap() {
	int frame = 0;
	for (std::vector<rgba**>::iterator it=m_frames.begin(); it<m_frames.end(); it++)
		m_vga_controller.save_bitmap(*it, frame++);
}

void vga_controller::play_frames() {
	create_capture();
	while(m_vga_controller.can_continue()) {
		for (std::vector<rgba**>::iterator it=m_frames.begin(); it<m_frames.end() && m_vga_controller.can_continue(); it++) {
			m_vga_controller.request_display_frame(*it);
			m_vga_controller.synchronize();
		}
		std::this_thread::sleep_for(std::chrono::milliseconds(1));
	}
}

void vga_controller::refresh() {
	while(1) {
		#if FAST_FORWARD == 1
			wait(m_frame_completed);
		#else
			wait(m_refresh_rate);
		#endif

		create_capture();
	}
}

void vga_controller::create_capture() {
	rgba** pixels;
	pixels = new rgba*[HEIGHT_VGA];

	for(int i=0; i<HEIGHT_VGA; i++)
		pixels[i] = new rgba[WIDTH_VGA];

	for (int i=0; i<HEIGHT_VGA; i++)
		for (int j=0; j<WIDTH_VGA; j++) {
			pixels[i][j].red = m_current_frame[i][j].red;
			pixels[i][j].green = m_current_frame[i][j].green;
			pixels[i][j].blue = m_current_frame[i][j].blue;
			pixels[i][j].alpha = m_current_frame[i][j].alpha;
		}

	m_frames.push_back(pixels);
	m_is_ready = true;
}

/**
 * Called on slave access.
 *
 * @param trans TLM 2.0 generic payload.
 * @param delay TLM 2.0 delay parameter.
 */
void vga_controller::access(tlm::tlm_generic_payload& trans, sc_core::sc_time& delay) {
	// reached offset
	unsigned long offset = get_offset(trans);

	/* reading */
	if (trans.is_read()) {
		unsigned long* data = (unsigned long*)trans.get_data_ptr();
		if (offset == 0)
			*data = m_is_ready ? 1 : 0;
		else
			*data = m_frame_count;
	}
	else { /* writing */
		unsigned int row = 0;
		unsigned int column = 0;
		unsigned long current_pixel = 0;
		unsigned long data = *(unsigned long*)trans.get_data_ptr();

		// Are we done receiving the frame ?
		if (offset == FRAME_DONE) {
			m_is_ready = false;
			m_frame_count++;

			#if FAST_FORWARD == 1
				m_frame_completed.notify();
			#endif
		}
		else {
			current_pixel = offset / 4;
			row = current_pixel / WIDTH_VGA;
			column = current_pixel - (row * WIDTH_VGA);

			// Assign current pixel
			m_current_frame[row][column].red = data & 0xFF;
			m_current_frame[row][column].green = (data >> 8) & 0xFF;
			m_current_frame[row][column].blue = (data >> 16) & 0xFF;
			m_current_frame[row][column].alpha = (data >> 24) & 0xFF;

			if (is_verbose() && column == 0 && (row % 8) == 0)
				m_vga_controller.request_display_frame(m_current_frame);
		}
	}

	trans.set_response_status(tlm::TLM_OK_RESPONSE); // don't forget to set the TLM 2.0 response status
	delay += get_clock_period(); // simulate device access delay
}

#include "spacecomp_post_cpp.h"
