#include "PngImage.h"

#include <Utility/NativeAssetFile.h>
#include <Utility/Log.h>

#include <png.h>
#include <pngstruct.h>
#include <pnginfo.h>
#include <string>
#include <stdexcept>

//////////////////////////////////////////////////////////////
namespace Dubious {
namespace Renderer {

namespace {

//////////////////////////////////////////////////////////////
void ReadDataFromInputStream( png_structp pPng, png_bytep pBuffer, png_size_t Count)
{
	if (pPng->io_ptr == NULL) {
		LOG_WARN( "Could not find NativeAssetFile in pPng" );
		png_error( pPng, "Could not find NativeAssetFile in pPng" );
		return;
	}
	Utility::NativeAssetFile* pFile = reinterpret_cast<Utility::NativeAssetFile*>( pPng->io_ptr );
	const size_t BytesRead = pFile->Read( pBuffer, Count );
	if (BytesRead !=Count) {
		LOG_WARN( "Read incorrect number of bytes from file" );
		png_error( pPng, "Read incorrect number of bytes from file" );
		return;
	}
}

//////////////////////////////////////////////////////////////
void ReadIntoBuffer( Utility::NativeAssetFilePtr pFile, std::vector<unsigned long>& Data, Math::Size& Dimensions )
{
	// The first step is to read in 8 bytes and check that
	// this is a valid PNG structure
	const int HEADER_SIZE = 8;
	unsigned char HeaderBuffer[HEADER_SIZE];
	if (HEADER_SIZE != pFile->Read( HeaderBuffer, HEADER_SIZE )) {
		throw std::runtime_error( "The PNG isn't at least 8 bytes long" );
	}
	if (!png_check_sig( HeaderBuffer, HEADER_SIZE )) {
		throw std::runtime_error( "The PNG header check failed" );
	}

	// get PNG file info struct (memory is allocated by libpng)
	png_structp pPng = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
	if (pPng == NULL) {
	   throw std::runtime_error( "Failed to create png read struct" );
	}

	// get PNG image data info struct (memory is allocated by libpng)
	png_infop pInfo = png_create_info_struct(pPng);
	if (pInfo == NULL) {
	   png_destroy_read_struct(&pPng, NULL, NULL);
	   throw std::runtime_error( "Failed to create png info struct" );
	}

	png_set_read_fn(pPng, pFile.get(), ReadDataFromInputStream);
	png_set_sig_bytes(pPng, HEADER_SIZE);
	png_read_info(pPng, pInfo);

	png_uint_32 Width=0, Height=0;
	int BitDepth=0, ColourType=-1;
	LOG_INFO( "Width = " << pInfo->width << ", Height = " << pInfo->height );
	png_get_IHDR( pPng, pInfo, &Width, &Height, &BitDepth, &ColourType, NULL, NULL, NULL);
	if( ColourType != PNG_COLOR_TYPE_RGB_ALPHA ){
		png_destroy_read_struct(&pPng, &pInfo, NULL);
		throw std::runtime_error( "Png was of the wrong colour type.  It needs to be RGBA" );
	}
	Dimensions = Math::Size( Width, Height );

	Data.resize( Width*Height );
	for( png_uint_32 y=0; y<Height; ++y ) {
		png_read_row( pPng, reinterpret_cast<png_bytep>(&Data[y*Width]), NULL );
	}

	png_destroy_read_struct(&pPng, &pInfo, NULL);
}
}

//////////////////////////////////////////////////////////////
PngImage::PngImage( Utility::NativeAssetFilePtr pFile )
{
	try {
		ReadIntoBuffer( pFile, m_Data, m_Dimensions );
	}
	catch (const std::exception& e) {
		LOG_WARN( e.what() );
		throw;
	}
}

//////////////////////////////////////////////////////////////
PngImage::~PngImage()
{
}

}
}
