/*
 *  psychlops_g_PNG_bridge.cpp
 *  Psychlops Standard Library (Universal)
 *
 *  Last Modified 2006/08/22 by Kenchi HOSOKAWA
 *  (C) 2006- Kenchi HOSOKAWA, Kazushi MARUYA and Takao SATO
 */

#include <stdio.h>
#include <stdlib.h>
#include "png.h"

#include "../../../core/ApplicationInterfaces/psychlops_code_exception.h"
#include "../../../core/graphic/psychlops_g_image.h"
#include "psychlops_g_PNG_bridge.h"


namespace Psychlops {
namespace IMAGE_FORMATS {


	PNG_BRIDGE::PNG_BRIDGE() {
		is_opened_ = false;
	}
	PNG_BRIDGE::~PNG_BRIDGE() {
		close();
	}


	void PNG_BRIDGE::load(const char *file_name, Image * target) {
		open(file_name, "rb");

		check_before_read();
		read(target);

		png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL);
	}
	void PNG_BRIDGE::save(const char *file_name, Image * target) {
		open(file_name, "wb");

		prepare_before_write();
		write(target);


		png_destroy_write_struct(&png_ptr, &info_ptr);
	}
	void PNG_BRIDGE::open(const char *file_name, const char *mode) {
		if(!is_opened_)
			if ((fp = fopen(file_name, mode)) == NULL)
					throw Exception(typeid(*this), "FILE ACCESS ERROR", "Failed to open the requested file.");
		is_opened_ = true;
	}
	void PNG_BRIDGE::close() {
		if(is_opened_) fclose(fp);
		is_opened_ = false;
	}


	void PNG_BRIDGE::check_before_read() {
		png_byte sig[PNG_BYTES_TO_CHECK];

		if (fread(sig, 1, PNG_BYTES_TO_CHECK, fp) != PNG_BYTES_TO_CHECK) {
			close();
			throw Exception(typeid(*this), "FILE FORMAT ERROR", "The requested file is not PNG.");
		}
		if (!png_check_sig(sig, PNG_BYTES_TO_CHECK)) {
			close();
			throw Exception(typeid(*this), "FILE FORMAT ERROR", "The requested file is not PNG.");
		}
		png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
		if (!png_ptr) {
			close();
			throw Exception(typeid(*this), "FILE FORMAT ERROR", "The requested file is not PNG.");
		}
		info_ptr = png_create_info_struct(png_ptr);
		if (!info_ptr) {
			png_destroy_read_struct(&png_ptr, (png_infopp)NULL, (png_infopp)NULL);
			close();
			throw Exception(typeid(*this), "FILE FORMAT ERROR", "The requested file is not PNG.");
		}
		end_info_ptr = png_create_info_struct(png_ptr);
		if (!end_info_ptr) {
			png_destroy_read_struct(&png_ptr, (png_infopp)NULL, (png_infopp)NULL);
			close();
			throw Exception(typeid(*this), "FILE FORMAT ERROR", "The requested file is not PNG.");
		}
		if (setjmp(png_jmpbuf(png_ptr))) {
			png_destroy_read_struct(&png_ptr, &info_ptr, &end_info_ptr);
			close();
			throw Exception(typeid(*this), "FILE FORMAT ERROR", "The requested file is not PNG.");
		}
		png_init_io(png_ptr, fp);
		png_set_sig_bytes(png_ptr, PNG_BYTES_TO_CHECK);
		png_read_info(png_ptr, info_ptr);
	}

	void PNG_BRIDGE::read(Image * target) {
		unsigned int width = png_get_image_width(png_ptr, info_ptr);
		unsigned int height = png_get_image_height(png_ptr, info_ptr);
		Image::PixelComponentsCnt pix_component;
		switch (png_get_color_type(png_ptr, info_ptr)) {
			case PNG_COLOR_TYPE_GRAY:
				pix_component = Image::GRAY;
				break;
			case PNG_COLOR_TYPE_RGB:
				pix_component = Image::RGB;
				break;
			case PNG_COLOR_TYPE_RGB_ALPHA:
				pix_component = Image::RGBA;
				break;
			default:
				pix_component = Image::RGBA;
				break;
		}
		png_bytep *row_pointers = new png_bytep[height];

		target->set(width, height, pix_component);
		readTargetMemoryAlignment(target);
		for(unsigned int i=0; i<height; i++) row_pointers[i] = target_bitmap_ub_ + (height-i-1)*(target_bytes_per_line_);

		png_read_image(png_ptr, row_pointers);
		png_read_end(png_ptr, end_info_ptr);
		delete [] row_pointers;
	}




	void PNG_BRIDGE::prepare_before_write() {
		png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
		if (!png_ptr) {
			close();
			throw Exception(typeid(*this), "FILE FORMAT ERROR", "The requested data is not able to be put as a PNG file.");
		}
		info_ptr = png_create_info_struct(png_ptr);
		if (!info_ptr) {
			png_destroy_write_struct(&png_ptr, (png_infopp)NULL);
			close();
			throw Exception(typeid(*this), "FILE FORMAT ERROR", "The requested data is not able to be put as a PNG file.");
		}
		if (setjmp(png_jmpbuf(png_ptr))) {
			png_destroy_read_struct(&png_ptr, &info_ptr, &end_info_ptr);
			close();
			throw Exception(typeid(*this), "FILE FORMAT ERROR", "The requested data is not able to be put as a PNG file.");
		}
		png_init_io(png_ptr, fp);
		png_set_write_status_fn(png_ptr, NULL);	//callback_function_per_row(png_ptr, png_uint_32 row, int pass)
	}

	void PNG_BRIDGE::write(Image * target) {
		readTargetMemoryAlignment(target);

		unsigned int width = (unsigned int)target->getWidth();
		unsigned int height = (unsigned int)target->getHeight();

		int color_type;
		switch (pix_components_) {
			case Image::GRAY:
				color_type = PNG_COLOR_TYPE_GRAY;
				break;
			case Image::RGB:
				color_type = PNG_COLOR_TYPE_RGB;
				break;
			case Image::RGBA:
				color_type = PNG_COLOR_TYPE_RGB_ALPHA;
				break;
			default:
				color_type = PNG_COLOR_TYPE_RGB_ALPHA;
				break;
		}

		unsigned int bit_depth;
		switch(pix_precision_) {
			case Image::FLOAT:
				throw Exception(typeid(*this), "FILE FORMAT ERROR", "The requested data is not able to be put as a PNG file.");
				break;
			case Image::BYTE:
				bit_depth = 8;
				break;
			default:
				bit_depth = 8;
				break;
		}

		int interlace_type = PNG_INTERLACE_NONE;
		int compression_type = PNG_COMPRESSION_TYPE_DEFAULT;
		int filter_method = PNG_FILTER_TYPE_DEFAULT;

		png_set_IHDR(png_ptr, info_ptr, width, height, bit_depth, color_type, interlace_type, compression_type, filter_method);


		png_write_info(png_ptr, info_ptr);

		png_bytep *row_pointers = new png_bytep[height];
		for(unsigned int i=0; i<height; i++) row_pointers[i] = target_bitmap_ub_ + (height-i-1)*(target_bytes_per_line_);
		png_write_image(png_ptr, row_pointers);

		png_write_end(png_ptr, NULL);
		delete [] row_pointers;
	}


}
}
