///////////////////////////////////////////////////////////////////////////////
///
///         Space Codesign System Inc. - (https://www.spacecodesign.com)
///         (c) All Rights Reserved. 2014
///
///         No authorization to modify or use this file for
///         commercial purposes without prior written consent
///         of its author(s)
///
///////////////////////////////////////////////////////////////////////////////
#include "basic_vga_controller.h"

#include <opencv2/opencv.hpp>

#include <cstring>
#include <cstdio>
#include <string>
#include <algorithm>
#include <stdexcept>

basic_vga_controller::basic_vga_controller(unsigned int width, unsigned int height, unsigned int refresh_rate)
:m_width(width), m_height(height), m_refresh_rate(refresh_rate), m_exit_requested(false), m_frame_count(0) {
	m_frame = new unsigned int[m_width*m_height];

	/* black screen */
	for (int i=0; i<m_width*m_height; i++)
		m_frame[i] = 0;

	m_main_thread = std::unique_ptr<std::thread>(new std::thread(&basic_vga_controller::vga_emulation, this));
}

basic_vga_controller::~basic_vga_controller() {
	m_main_thread.get()->join();
	delete [] m_frame;
}

void basic_vga_controller::request_display_frame(rgba** frame) {
	unsigned int index = 0;
	for (unsigned int i=0; i<m_height; i++) {
		for (unsigned int j=0; j<m_width; j++) {
			unsigned int pixel = (frame[i][j].alpha << 24) | (frame[i][j].red << 16) | (frame[i][j].green << 8) | frame[i][j].blue;
			m_frame[index++] = pixel;
		}
	}
}

void basic_vga_controller::synchronize() {
	unsigned int waiting_for_frame = m_frame_count;
	while(m_frame_count == waiting_for_frame && !m_exit_requested)
		std::this_thread::sleep_for(std::chrono::milliseconds(1));
}

void basic_vga_controller::vga_emulation() {
	cv::namedWindow("Emulated VGA");

	while (!m_exit_requested) {
	    cv::Mat image(m_height, m_width, CV_8UC4, m_frame);
	    cv::imshow("Emulated VGA", image);
	    int code = cv::waitKey(1);

	    /* ESC is pressed or the window disappeared */
	    if (code == 27 || cv::getWindowProperty("Emulated VGA", cv::WND_PROP_VISIBLE) < 1)
	    	m_exit_requested = true;

		std::this_thread::sleep_for(std::chrono::milliseconds(1000/m_refresh_rate));
		m_frame_count++;
	}
}

void basic_vga_controller::save_bitmap(rgba** frame, int frame_index) {
	FILE* file;
	char filename[30];
	unsigned int uiSize = 54 + m_width * m_height;

	memset(filename, 0, sizeof(filename));
	sprintf(filename, "basic_vga_controller_%03d.bmp", frame_index);

	if ((file = fopen(filename , "wb")) == NULL)
    	std::cout << "Failed to open output BMP file" << std::endl;

	/* header */
	fputc(0x42, file);
	fputc(0x4d, file);

	fputc(uiSize & 0xff, file);
	fputc(uiSize >> 8 & 0xff, file);
	fputc(uiSize >> 16 & 0xff, file);
	fputc(uiSize >> 24 & 0xff, file);

	fputc(0, file);
	fputc(0, file);
	fputc(0, file);
	fputc(0, file);

	fputc(54, file);
	fputc(0, file);
	fputc(0, file);
	fputc(0, file);

	fputc(40, file);
	fputc(0, file);
	fputc(0, file);
	fputc(0, file);

	fputc((unsigned short) m_width & 0xff, file);
	fputc((unsigned short) (m_width >> 8) & 0xff, file);
	fputc(0, file);
	fputc(0, file);

	fputc((unsigned short) m_height & 0xff, file);
	fputc((unsigned short) (m_height >> 8) & 0xff, file);
	fputc(0, file);
	fputc(0, file);

	fputc(1, file);
	fputc(0, file);
	fputc(24, file);
	fputc(0, file);

	fputc(0, file);
	fputc(0, file);
	fputc(0, file);
	fputc(0, file);

	fputc(0, file);
	fputc(0, file);
	fputc(0, file);
	fputc(0, file);

	fputc(96, file); /* x-pixel resolution */
	fputc(0, file);
	fputc(0, file);
	fputc(0, file);

	fputc(96, file); /* y-pixel resolution */
	fputc(0, file);
	fputc(0, file);
	fputc(0, file);

	fputc(0, file);
	fputc(0, file);
	fputc(0, file);
	fputc(0, file);

	fputc(0, file);
	fputc(0, file);
	fputc(0, file);
	fputc(0, file);

	/* flip image on x-axis */
	for(int i=m_width-1; i>=0; i--)
		for(unsigned int j=0; j<m_height; j++) {
			fputc(frame[i][j].blue, file);
			fputc(frame[i][j].green, file);
			fputc(frame[i][j].red, file);
		}

	if(fclose(file))
		std::cout << "Failed to close output BMP file" << std::endl;
}
