#include "png.h"
#include <assert.h>
#include <windows.h>

typedef unsigned int uint;
typedef unsigned char ubyte;
static const uint PNG_BYTES_TO_CHECK = 4;
static const uint PIXEL_BYTE_SIZE = 4; // r8g8b8a8
static const uint BIT_DEPTH = 8;
static const uint COLOR_TYPE = PNG_COLOR_TYPE_RGB_ALPHA; // PNG_COLOR_TYPE_RGBは変換、他はエラー

BOOL DllMain(HINSTANCE hInstance, ULONG ulReason, LPVOID pvReserved)
{
	return true;
}

struct PngReader
{
	png_struct* png;
	png_info* info;
};

extern "C" __declspec(dllexport)
	void* createAndGetSizePngReader(char* pngFileName, uint* width, uint* height)
{
	PngReader* result = new PngReader;
	
	FILE* file = fopen(pngFileName, "rb");
	if (!file)
	{
		delete result;
		return NULL;
	}
	
	unsigned char sig[PNG_BYTES_TO_CHECK];
	if (fread(sig, 1, PNG_BYTES_TO_CHECK, file) != PNG_BYTES_TO_CHECK
		|| !png_check_sig(sig, PNG_BYTES_TO_CHECK))
	{
		fclose(file);
		delete result;
		return NULL;
	}
	
	result->png = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
	assert(result->png);
	result->info = png_create_info_struct(result->png);
	assert(result->info);
	png_init_io(result->png, file);
	png_set_sig_bytes(result->png, PNG_BYTES_TO_CHECK);
	png_read_png(result->png, result->info, PNG_TRANSFORM_IDENTITY, NULL);
	
	fclose(file);
	
	*width = png_get_image_width(result->png, result->info);
	*height = png_get_image_height(result->png, result->info);
	
	if (BIT_DEPTH != png_get_bit_depth(result->png, result->info)
		|| (
			png_get_color_type(result->png, result->info) != PNG_COLOR_TYPE_RGB_ALPHA
			&&
			png_get_color_type(result->png, result->info) != PNG_COLOR_TYPE_RGB
		))
	{
		delete result;
		return NULL;
	}
	
	return result;
}

extern "C" __declspec(dllexport)
	void readAndDeletePngReader(void* handle, void* buffer)
{
	PngReader* reader = (PngReader*)handle;
	const uint width = png_get_image_width(reader->png, reader->info);
	const uint height = png_get_image_height(reader->png, reader->info);
	ubyte** rowList = png_get_rows(reader->png, reader->info);
	ubyte* i = (ubyte*)buffer;
	for (int y = 0; y < height; y++)
	{
		if (png_get_color_type(reader->png, reader->info) == PNG_COLOR_TYPE_RGB_ALPHA)
		{
			memcpy(i, rowList[y], PIXEL_BYTE_SIZE * width);
		}
		else if (png_get_color_type(reader->png, reader->info) == PNG_COLOR_TYPE_RGB)
		{
			int srcIndex = 0;
			for (int dstIndex = 0; dstIndex < PIXEL_BYTE_SIZE * width; dstIndex += PIXEL_BYTE_SIZE)
			{
				i[dstIndex + 0] = rowList[y][srcIndex + 0];
				i[dstIndex + 1] = rowList[y][srcIndex + 1];
				i[dstIndex + 2] = rowList[y][srcIndex + 2];
				i[dstIndex + 3] = 255;
				srcIndex += 3;
			}
		}
		i += PIXEL_BYTE_SIZE * width;
	}
	png_destroy_read_struct(&reader->png, &reader->info, NULL);
	delete reader;
}

void createWriteHandle(char* fileName, FILE** file, png_struct** png, png_info** info)
{
	*file = fopen(fileName, "wb");
	*png = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
	*info = png_create_info_struct(*png);
}

void deleteWriteHandle(FILE** file, png_struct** png, png_info** info)
{
	if (*png) png_destroy_write_struct(png, (*info) ? info : png_infopp_NULL);
	if (*file) fclose(*file);
}

extern "C" __declspec(dllexport)
	bool writePngWriter(char* fileName, void* buffer, uint width, uint height)
{
	FILE* file;
	png_struct* png;
	png_info* info;
	createWriteHandle(fileName, &file, &png, &info);
	if (!file || !png || !info)
	{
		deleteWriteHandle(&file, &png, &info);
		return false;
	}
	
	png_init_io(png, file);
	png_set_IHDR(png, info,
		width, height, BIT_DEPTH, COLOR_TYPE,
		PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
	
	ubyte* i = (ubyte*)buffer;
	ubyte** rowList = new (ubyte*)[height];
	for (int y = 0; y < height; y++)
	{
		rowList[y] = i;
		i += PIXEL_BYTE_SIZE * width;
	}
	
	// これか
	png_set_rows(png, info, rowList);
	png_write_png(png, info, PNG_TRANSFORM_IDENTITY, NULL);
	
	// これの、どちらでも
	//png_write_info(png, info);
	//png_write_image(png, rowList);
	//png_write_end(png, info);
	
	delete[] rowList;
	
	
	deleteWriteHandle(&file, &png, &info);
	return true;
}
