#pragma region Copyright (c) 2014-2016 OpenRCT2 Developers
/*****************************************************************************
 * OpenRCT2, an open source clone of Roller Coaster Tycoon 2.
 *
 * OpenRCT2 is the work of many authors, a full list can be found in contributors.md
 * For more information, visit https://github.com/OpenRCT2/OpenRCT2
 *
 * OpenRCT2 is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * A full copy of the GNU General Public License can be found in licence.txt
 *****************************************************************************/
#pragma endregion

#include "../addresses.h"
#include "../common.h"
#include "../sprites.h"
#include "drawing.h"
#include "../platform/platform.h"
#include "../openrct2.h"

void *_g1Buffer = NULL;

rct_gx g2;

rct_g1_element *g1Elements = (rct_g1_element*)RCT2_ADDRESS_G1_ELEMENTS;

/**
 *
 *  rct2: 0x00678998
 */
int gfx_load_g1()
{
	log_verbose("loading g1 graphics");

	SDL_RWops *file;
	rct_g1_header header;
	unsigned int i;

	file = SDL_RWFromFile(get_file_path(PATH_ID_G1), "rb");
	if (file != NULL) {
		if (SDL_RWread(file, &header, 8, 1) == 1) {
			// number of elements is stored in g1.dat, but because the entry headers are static, this can't be variable until
			// made into a dynamic array
			header.num_entries = 29294;

			// Read element headers
			SDL_RWread(file, g1Elements, header.num_entries * sizeof(rct_g1_element), 1);

			// Read element data
			_g1Buffer = malloc(header.total_size);
			SDL_RWread(file, _g1Buffer, header.total_size, 1);

			SDL_RWclose(file);

			// Fix entry data offsets
			for (i = 0; i < header.num_entries; i++)
				g1Elements[i].offset += (int)_g1Buffer;

			// Successful
			return 1;
		}
		SDL_RWclose(file);
	}

	// Unsuccessful
	log_fatal("Unable to load g1 graphics");
	return 0;
}

void gfx_unload_g1()
{
	SafeFree(_g1Buffer);
}

void gfx_unload_g2()
{
	SafeFree(g2.elements);
}

int gfx_load_g2()
{
	log_verbose("loading g2 graphics");

	SDL_RWops *file;
	unsigned int i;

	char path[MAX_PATH];
	char dataPath[MAX_PATH];

	platform_get_openrct_data_path(dataPath);
	sprintf(path, "%s%cg2.dat", dataPath, platform_get_path_separator());
	file = SDL_RWFromFile(path, "rb");
	if (file != NULL) {
		if (SDL_RWread(file, &g2.header, 8, 1) == 1) {
			// Read element headers
			g2.elements = malloc(g2.header.num_entries * sizeof(rct_g1_element));
			SDL_RWread(file, g2.elements, g2.header.num_entries * sizeof(rct_g1_element), 1);

			// Read element data
			g2.data = malloc(g2.header.total_size);
			SDL_RWread(file, g2.data, g2.header.total_size, 1);

			SDL_RWclose(file);

			// Fix entry data offsets
			for (i = 0; i < g2.header.num_entries; i++)
				g2.elements[i].offset += (int)g2.data;

			// Successful
			return 1;
		}
		SDL_RWclose(file);
	}

	// Unsuccessful
	log_fatal("Unable to load g2 graphics");
	return 0;
}

/**
 * This function looks like it initialises the 0x009E3CE4 array which references sprites used for background / palette mixing or
 * something. Further investigation is needed.
 */
void sub_68371D()
{
	uint8 **unk_9E3CE4 = (uint8**)0x009E3CE4;

	unk_9E3CE4[0] = NULL;
	for (int i = 1; i < 8; i++)
		unk_9E3CE4[i] = g1Elements[23199 + i].offset;
}

/**
 * Copies a sprite onto the buffer. There is no compression used on the sprite
 * image.
 *  rct2: 0x0067A690
 */
void FASTCALL gfx_bmp_sprite_to_buffer(uint8* palette_pointer, uint8* unknown_pointer, uint8* source_pointer, uint8* dest_pointer, rct_g1_element* source_image, rct_drawpixelinfo *dest_dpi, int height, int width, int image_type){
	uint16 zoom_level = dest_dpi->zoom_level;
	uint8 zoom_amount = 1 << zoom_level;
	uint32 dest_line_width = (dest_dpi->width / zoom_amount) + dest_dpi->pitch;
	uint32 source_line_width = source_image->width * zoom_amount;
	//Requires use of palette?
	if (image_type & IMAGE_TYPE_USE_PALETTE){
		assert(palette_pointer != NULL);

		//Mix with another image?? and colour adjusted
		if (unknown_pointer!= NULL){ //Not tested. I can't actually work out when this code runs.
			unknown_pointer += source_pointer - source_image->offset;// RCT2_GLOBAL(0x9E3CE0, uint32);

			for (; height > 0; height -= zoom_amount){
				uint8* next_source_pointer = source_pointer + source_line_width;
				uint8* next_unknown_pointer = unknown_pointer + source_line_width;
				uint8* next_dest_pointer = dest_pointer + dest_line_width;

				for (int no_pixels = width; no_pixels > 0; no_pixels -= zoom_amount, source_pointer += zoom_amount, unknown_pointer += zoom_amount, dest_pointer++){
					uint8 pixel = *source_pointer;
					pixel = palette_pointer[pixel];
					pixel &= *unknown_pointer;
					if (pixel){
						*dest_pointer = pixel;
					}
				}
				source_pointer = next_source_pointer;
				dest_pointer = next_dest_pointer;
				unknown_pointer = next_unknown_pointer;
			}
			return;
		}

		//image colour adjusted?
		for (; height > 0; height -= zoom_amount){
			uint8* next_source_pointer = source_pointer + source_line_width;
			uint8* next_dest_pointer = dest_pointer + dest_line_width;
			for (int no_pixels = width; no_pixels > 0; no_pixels -= zoom_amount, source_pointer += zoom_amount, dest_pointer++){
				uint8 pixel = *source_pointer;
				pixel = palette_pointer[pixel];
				if (pixel){
					*dest_pointer = pixel;
				}
			}

			source_pointer = next_source_pointer;
			dest_pointer = next_dest_pointer;
		}
		return;
	}

	//Mix with background. It only uses source pointer for
	//telling if it needs to be drawn not for colour.
	if (image_type & IMAGE_TYPE_MIX_BACKGROUND){//Not tested
		assert(palette_pointer != NULL);
		for (; height > 0; height -= zoom_amount){
			uint8* next_source_pointer = source_pointer + source_line_width;
			uint8* next_dest_pointer = dest_pointer + dest_line_width;

			for (int no_pixels = width; no_pixels > 0; no_pixels -= zoom_amount, source_pointer += zoom_amount, dest_pointer++){
				uint8 pixel = *source_pointer;
				if (pixel){
					pixel = *dest_pointer;
					pixel = palette_pointer[pixel];
					*dest_pointer = pixel;
				}
			}

			source_pointer = next_source_pointer;
			dest_pointer = next_dest_pointer;
		}
		return;
	}

	//Basic bitmap no fancy stuff
	if (!(source_image->flags & G1_FLAG_BMP)){//Not tested
		for (; height > 0; height -= zoom_amount){
			uint8* next_source_pointer = source_pointer + source_line_width;
			uint8* next_dest_pointer = dest_pointer + dest_line_width;

			for (int no_pixels = width; no_pixels > 0; no_pixels -= zoom_amount, dest_pointer++, source_pointer += zoom_amount){
				*dest_pointer = *source_pointer;
			}

			dest_pointer = next_dest_pointer;
			source_pointer = next_source_pointer;
		}
		return;
	}

	if (RCT2_GLOBAL(0x9E3CDC, uint32) != 0){//Not tested. I can't actually work out when this code runs.
		unknown_pointer += source_pointer - source_image->offset;

		for (; height > 0; height -= zoom_amount){
			uint8* next_source_pointer = source_pointer + source_line_width;
			uint8* next_unknown_pointer = unknown_pointer + source_line_width;
			uint8* next_dest_pointer = dest_pointer + dest_line_width;

			for (int no_pixels = width; no_pixels > 0; no_pixels -= zoom_amount, dest_pointer++, source_pointer += zoom_amount, unknown_pointer += zoom_amount){
				uint8 pixel = *source_pointer;
				pixel &= *unknown_pointer;
				if (pixel){
					*dest_pointer = pixel;
				}
			}
			dest_pointer = next_dest_pointer;
			source_pointer = next_source_pointer;
			unknown_pointer = next_unknown_pointer;
		}
	}

	//Basic bitmap with no draw pixels
	for (; height > 0; height -= zoom_amount){
		uint8* next_source_pointer = source_pointer + source_line_width;
		uint8* next_dest_pointer = dest_pointer + dest_line_width;

		for (int no_pixels = width; no_pixels > 0; no_pixels -= zoom_amount, dest_pointer++, source_pointer += zoom_amount){
			uint8 pixel = *source_pointer;
			if (pixel){
				*dest_pointer = pixel;
			}
		}
		dest_pointer = next_dest_pointer;
		source_pointer = next_source_pointer;
	}
	return;
}

/**
 *
 *  rct2: 0x0067A28E
 * image_id (ebx)
 * image_id as below
 * 0b_111X_XXXX_XXXX_XXXX_XXXX_XXXX_XXXX_XXXX image_type
 * 0b_XXX1_11XX_XXXX_XXXX_XXXX_XXXX_XXXX_XXXX image_sub_type (unknown pointer)
 * 0b_XXX1_1111_XXXX_XXXX_XXXX_XXXX_XXXX_XXXX secondary_colour
 * 0b_XXXX_XXXX_1111_1XXX_XXXX_XXXX_XXXX_XXXX primary_colour
 * 0b_XXXX_X111_1111_1XXX_XXXX_XXXX_XXXX_XXXX palette_ref
 * 0b_XXXX_XXXX_XXXX_X111_1111_1111_1111_1111 image_id (offset to g1)
 * x (cx)
 * y (dx)
 * dpi (esi)
 * tertiary_colour (ebp)
 */
void FASTCALL gfx_draw_sprite(rct_drawpixelinfo *dpi, int image_id, int x, int y, uint32 tertiary_colour)
{
	int image_type = (image_id & 0xE0000000) >> 28;
	int image_sub_type = (image_id & 0x1C000000) >> 26;

	uint8* palette_pointer = NULL;

	RCT2_GLOBAL(0x00EDF81C, uint32) = image_id & 0xE0000000;

	uint8* unknown_pointer = (uint8*)(RCT2_ADDRESS(0x9E3CE4, uint32*)[image_sub_type]);
	RCT2_GLOBAL(0x009E3CDC, uint32) = (uint32)unknown_pointer;

	if (image_type && !(image_type & IMAGE_TYPE_UNKNOWN)) {
		uint8 palette_ref = (image_id >> 19) & 0xFF;
		if (image_type & IMAGE_TYPE_MIX_BACKGROUND){
			unknown_pointer = NULL;
			RCT2_GLOBAL(0x009E3CDC, uint32) = 0;
		}
		else{
			palette_ref &= 0x7F;
		}

		uint16 palette_offset = palette_to_g1_offset[palette_ref];
		palette_pointer = g1Elements[palette_offset].offset;
	}
	else if (image_type && !(image_type & IMAGE_TYPE_USE_PALETTE)){
		RCT2_GLOBAL(0x9E3CDC, uint32) = 0;
		unknown_pointer = NULL;
		palette_pointer = RCT2_ADDRESS(0x9ABF0C, uint8);

		uint32 primary_offset = palette_to_g1_offset[(image_id >> 19) & 0x1F];
		uint32 secondary_offset = palette_to_g1_offset[(image_id >> 24) & 0x1F];

#if DEBUG_LEVEL_2
		assert(tertiary_colour < PALETTE_TO_G1_OFFSET_COUNT);
#endif // DEBUG_LEVEL_2
		uint32 tertiary_offset = palette_to_g1_offset[tertiary_colour];

		rct_g1_element* primary_colour = &g1Elements[primary_offset];
		rct_g1_element* secondary_colour = &g1Elements[secondary_offset];
		rct_g1_element* tertiary_colour = &g1Elements[tertiary_offset];

		memcpy(palette_pointer + 0xF3, &primary_colour->offset[0xF3], 12);
		memcpy(palette_pointer + 0xCA, &secondary_colour->offset[0xF3], 12);
		memcpy(palette_pointer + 0x2E, &tertiary_colour->offset[0xF3], 12);

		//image_id
		RCT2_GLOBAL(0xEDF81C, uint32) |= 0x20000000;
		image_id |= IMAGE_TYPE_USE_PALETTE << 28;
	}
	else if (image_type){
		RCT2_GLOBAL(0x9E3CDC, uint32) = 0;
		unknown_pointer = NULL;

		palette_pointer = RCT2_ADDRESS(0x9ABE0C, uint8);

		//Top
		int top_type = (image_id >> 19) & 0x1f;
		uint32 top_offset = palette_to_g1_offset[top_type]; //RCT2_ADDRESS(0x97FCBC, uint32)[top_type];
		rct_g1_element top_palette = g1Elements[top_offset];
		memcpy(palette_pointer + 0xF3, top_palette.offset + 0xF3, 12);

		//Trousers
		int trouser_type = (image_id >> 24) & 0x1f;
		uint32 trouser_offset = palette_to_g1_offset[trouser_type]; //RCT2_ADDRESS(0x97FCBC, uint32)[trouser_type];
		rct_g1_element trouser_palette = g1Elements[trouser_offset];
		memcpy(palette_pointer + 0xCA, trouser_palette.offset + 0xF3, 12);
	}

	//For backwards compatibility
	RCT2_GLOBAL(0x9ABDA4, uint8*) = palette_pointer;

	gfx_draw_sprite_palette_set(dpi, image_id, x, y, palette_pointer, unknown_pointer);
}

/*
* rct: 0x0067A46E
* image_id (ebx) and also (0x00EDF81C)
* palette_pointer (0x9ABDA4)
* unknown_pointer (0x9E3CDC)
* dpi (edi)
* x (cx)
* y (dx)
*/
void FASTCALL gfx_draw_sprite_palette_set(rct_drawpixelinfo *dpi, int image_id, int x, int y, uint8* palette_pointer, uint8* unknown_pointer)
{
	int image_element = image_id & 0x7FFFF;
	int image_type = (image_id & 0xE0000000) >> 28;

	rct_g1_element *g1_source = gfx_get_g1_element(image_element);

	if ( dpi->zoom_level && (g1_source->flags & (1<<4)) ){
		rct_drawpixelinfo zoomed_dpi = {
			.bits = dpi->bits,
			.x = dpi->x >> 1,
			.y = dpi->y >> 1,
			.height = dpi->height>>1,
			.width = dpi->width>>1,
			.pitch = dpi->pitch,
			.zoom_level = dpi->zoom_level - 1
		};
		gfx_draw_sprite_palette_set(&zoomed_dpi, (image_type << 28) | (image_element - g1_source->zoomed_offset), x >> 1, y >> 1, palette_pointer, unknown_pointer);
		return;
	}

	if ( dpi->zoom_level && (g1_source->flags & (1<<5)) ){
		return;
	}

	//Its used super often so we will define it to a separate variable.
	int zoom_level = dpi->zoom_level;
	int zoom_mask = 0xFFFFFFFF << zoom_level;

	if (zoom_level && g1_source->flags & G1_FLAG_RLE_COMPRESSION){
		x -= ~zoom_mask;
		y -= ~zoom_mask;
	}

	//This will be the height of the drawn image
	int height = g1_source->height;
	//This is the start y coordinate on the destination
	sint16 dest_start_y = y + g1_source->y_offset;

	// For whatever reason the RLE version does not use
	// the zoom mask on the y coordinate but does on x.
	if (g1_source->flags & G1_FLAG_RLE_COMPRESSION){
		dest_start_y -= dpi->y;
	}
	else{
		dest_start_y = (dest_start_y&zoom_mask) - dpi->y;
	}
	//This is the start y coordinate on the source
	int source_start_y = 0;

	if (dest_start_y < 0){
		//If the destination y is negative reduce the height of the
		//image as we will cut off the bottom
		height += dest_start_y;
		//If the image is no longer visible nothing to draw
		if (height <= 0){
			return;
		}
		//The source image will start a further up the image
		source_start_y -= dest_start_y;
		//The destination start is now reset to 0
		dest_start_y = 0;
	}
	else{
		if (g1_source->flags & G1_FLAG_RLE_COMPRESSION && zoom_level){
			source_start_y -= dest_start_y & ~zoom_mask;
			height += dest_start_y & ~zoom_mask;
		}
	}

	int dest_end_y = dest_start_y + height;

	if (dest_end_y > dpi->height){
		//If the destination y is outside of the drawing
		//image reduce the height of the image
		height -= dest_end_y - dpi->height;
	}
	//If the image no longer has anything to draw
	if (height <= 0)return;

	dest_start_y >>= zoom_level;
	dest_end_y >>= zoom_level;

	//This will be the width of the drawn image
	int width = g1_source->width;
	//This is the source start x coordinate
	int source_start_x = 0;
	//This is the destination start x coordinate
	sint16 dest_start_x = ((x + g1_source->x_offset + ~zoom_mask)&zoom_mask) - dpi->x;

	if (dest_start_x < 0){
		//If the destination is negative reduce the width
		//image will cut off the side
		width += dest_start_x;
		//If there is no image to draw
		if (width <= 0){
			return;
		}
		//The source start will also need to cut off the side
		source_start_x -= dest_start_x;
		//Reset the destination to 0
		dest_start_x = 0;
	}
	else{
		if (g1_source->flags & G1_FLAG_RLE_COMPRESSION && zoom_level){
			source_start_x -= dest_start_x & ~zoom_mask;
		}
	}

	int dest_end_x = dest_start_x + width;

	if (dest_end_x > dpi->width){
		//If the destination x is outside of the drawing area
		//reduce the image width.
		width -= dest_end_x - dpi->width;
		//If there is no image to draw.
		if (width <= 0)return;
	}

	dest_start_x >>= zoom_level;
	dest_end_x >>= zoom_level;

	uint8* dest_pointer = (uint8*)dpi->bits;
	//Move the pointer to the start point of the destination
	dest_pointer += ((dpi->width >> zoom_level) + dpi->pitch) * dest_start_y + dest_start_x;

	if (g1_source->flags & G1_FLAG_RLE_COMPRESSION){
		//We have to use a different method to move the source pointer for
		//rle encoded sprites so that will be handled within this function
		gfx_rle_sprite_to_buffer(g1_source->offset, dest_pointer, palette_pointer, dpi, image_type, source_start_y, height, source_start_x, width);
		return;
	}
	uint8* source_pointer = g1_source->offset;
	//Move the pointer to the start point of the source
	source_pointer += g1_source->width*source_start_y + source_start_x;

	if (!(g1_source->flags & 0x02)){
		gfx_bmp_sprite_to_buffer(palette_pointer, unknown_pointer, source_pointer, dest_pointer, g1_source, dpi, height, width, image_type);
		return;
	}
	//0x67A60A Not tested
	int total_no_pixels = g1_source->width*g1_source->height;
	source_pointer = g1_source->offset;
	uint8* new_source_pointer_start = malloc(total_no_pixels);
	uint8* new_source_pointer = new_source_pointer_start;// 0x9E3D28;
	int ebx, ecx;
	while (total_no_pixels>0){
		sint8 no_pixels = *source_pointer;
		if (no_pixels >= 0){
			source_pointer++;
			total_no_pixels -= no_pixels;
			memcpy((char*)new_source_pointer, (char*)source_pointer, no_pixels);
			new_source_pointer += no_pixels;
			source_pointer += no_pixels;
			continue;
		}
		ecx = no_pixels;
		no_pixels &= 0x7;
		ecx >>= 3;//SAR
		int eax = ((int)no_pixels)<<8;
		ecx = -ecx;//Odd
		eax = (eax & 0xFF00) + *(source_pointer+1);
		total_no_pixels -= ecx;
		source_pointer += 2;
		ebx = (uint32)new_source_pointer - eax;
		eax = (uint32)source_pointer;
		source_pointer = (uint8*)ebx;
		ebx = eax;
		eax = 0;
		memcpy((char*)new_source_pointer, (char*)source_pointer, ecx);
		new_source_pointer += ecx;
		source_pointer = (uint8*)ebx;
	}
	source_pointer = new_source_pointer_start + g1_source->width*source_start_y + source_start_x;
	gfx_bmp_sprite_to_buffer(palette_pointer, unknown_pointer, source_pointer, dest_pointer, g1_source, dpi, height, width, image_type);
	free(new_source_pointer_start);
	return;
}

rct_g1_element *gfx_get_g1_element(int image_id) {
	if (image_id < SPR_G2_BEGIN) {
		return &g1Elements[image_id];
	}

	return &g2.elements[image_id - SPR_G2_BEGIN];
}
