// Filename : controller.cpp

#include "controller.h"
#include "spacecomp_pre_cpp.h"
#include "space_chrono.h"
#include <cstdlib>
#include <cstdio>

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

controller::controller(SPACECOMP_CTOR_PARAMS(controller, INDEX))
	: abstract_module(SPACECOMP_MBASE_ARGS(controller, INDEX))
	, SPACECOMP_START_ILIST(controller, INDEX)
	// Other initializers may go here
{
	SPACECOMP_THREAD(controller, INDEX, thread);
}

void controller::thread(SPACECOMP_THREAD_PARAMS(controller, INDEX, thread)) {
	spacecomp_thread_initialize();
	
	uint32_t image_size;
	uint32_t header_size;
	uint32_t pixel_data_size = IMG_WIDTH * IMG_HEIGHT;

	uint32_t pixel_data_in_offset;
	uint32_t pixel_data_out_offset;

	// Expected PGM header
	char pgm_header[64];

	float execution_time = 0;
	
	// Initialization: read image file and write pixel data to DDR
	const bool initializing = spacecomp_thread_loop_start();
	if (initializing) {

		header_size = static_cast<uint32_t>(
			snprintf(pgm_header, sizeof(pgm_header), "P5\n%d %d\n%d\n", IMG_WIDTH, IMG_HEIGHT, PGM_DATA_MAX_VAL)
		);

		FILE* file = fopen("/root/image_in.pgm", "rb" );
		if (file == 0) {
			printf("error while opening input image\n");
			exit(1);
		}

        // Check the header of the input file, excluding comment lines
		char line_buf[64];
		uint32_t line_size;
		uint32_t n_chars_checked = 0;

		while (n_chars_checked < header_size) {
			fgets(line_buf, sizeof(line_buf), file);
			if (*line_buf == '#') { // skip comment lines
				continue;
			}
			line_size = strlen(line_buf);
			for(int i = 0; i < line_size; i++) {
				if(pgm_header[n_chars_checked+i] != line_buf[i]) {
					printf("unexpected image header\n");
					exit(1);
				}
			}
			n_chars_checked += line_size;
		}

		// Check the size of pixel data in the input file
		uint32_t pixel_data_start = ftell(file);
		fseek(file, 0, SEEK_END);
		uint32_t pixel_data_end = ftell(file);

		if ((pixel_data_end-pixel_data_start) != pixel_data_size) {
			printf("input image size does not match expected size\n");
			exit(1);
		}

		// Allocate an array for the pixel data
		uint8_t* content = new uint8_t[pixel_data_size];

		// Read the pixel data
		fseek(file, pixel_data_start, SEEK_SET);
		uint32_t read_bytes = fread(content, 1, pixel_data_size, file);
		if (read_bytes != pixel_data_size) {
			printf("error while reading\n");
			exit(1);
		}

		// Configure offset registers for DDR
		pixel_data_in_offset = 0;
		pixel_data_out_offset = pixel_data_size;
		RegisterWrite(REGISTER_FILE0_ID, PIXEL_DATA_IN_OFFSET_REG, pixel_data_in_offset);
		RegisterWrite(REGISTER_FILE0_ID, PIXEL_DATA_OUT_OFFSET_REG, pixel_data_out_offset);

		// Write pixel data into ddr
		DeviceWrite(ZYNQ_DDR0_ID, pixel_data_in_offset, content, pixel_data_size);

		delete[] content;
		fclose(file);
	} // end of initialization

	for (int i = 0; i < NUM_ITERATIONS; i++) {

		auto sobel_start = std::chrono::steady_clock::now();

		// Notify sobel modules to start working
		for (unsigned int index = 0; index < SOBEL_GROUP_SIZE; ++index) {
			ModuleWrite(SOBEL_GROUP[index], SPACE_BLOCKING);
		}

		// Wait for sobel modules to complete
		for (unsigned int index = 0; index < SOBEL_GROUP_SIZE; ++index) {
			ModuleRead(SOBEL_GROUP[index], SPACE_BLOCKING);
		}

		auto sobel_end = std::chrono::steady_clock::now();
		execution_time += static_cast<float>(std::chrono::duration_cast<std::chrono::milliseconds>(sobel_end-sobel_start).count());
	}

	printf("Execution time: %7.3f ms (%6.2f FPS)\n", execution_time/NUM_ITERATIONS, 1e3/(execution_time/NUM_ITERATIONS));

	// Retrieve the output data from DDR
	uint8_t* pixel_data_out = new uint8_t[pixel_data_size];
	DeviceRead(ZYNQ_DDR0_ID, pixel_data_out_offset, pixel_data_out, pixel_data_size);

#ifdef SPACE_SIMULATION
	// Save output image to image/ folder (via serialization device) during simulation
	DeviceWrite(SIMULATION_SERIALIZATION0_ID, START_REG, image_size);
	DeviceWrite(SIMULATION_SERIALIZATION0_ID, PIXEL_REG, pgm_header, header_size);
	DeviceWrite(SIMULATION_SERIALIZATION0_ID, PIXEL_REG+header_size, pixel_data_out, pixel_data_size);
	DeviceWrite(SIMULATION_SERIALIZATION0_ID, DONE_REG, 1);
#else // SPACE_IMPLEMENTATION
	// Save output image to the sd card during implementation
	FILE* file = fopen("/root/sdcard/image_out.pgm", "wb");

	if (file == 0) {
		printf("error while opening output image\n");
		exit(1);
	}
	else {
		uint32_t written_bytes_header = fwrite(pgm_header, 1, header_size, file);
		uint32_t written_bytes_pixel = fwrite(pixel_data_out, 1, pixel_data_size, file);

		if ((written_bytes_header != header_size) || written_bytes_pixel != pixel_data_size) {
			printf("error while writing\n");
			exit(1);
		}
		fclose(file);
	}
#endif
	
	delete[] pixel_data_out;
}

#include "spacecomp_post_cpp.h"