// pnginfo.cpp : Defines the entry point for the console application.
//
#include <exception>
#include <iostream>
#include <boost/format.hpp>

#include <png.h>
#include <pngstruct.h>

static void error_handler(png_structp png_ptr, png_const_charp msg)
{
	//		if (png_ptr)
	//			;
	throw std::logic_error( boost::str(boost::format("libpng: %1") % msg).c_str() );
}

static void warning_handler(png_structp png_ptr, png_const_charp msg)
{
	//		if (png_ptr)
	//			;
	std::cerr << "Warning: libpng: " << msg << std::endl;
}

void print_info( const std::string& name )
{
	png_structp png_ptr = 0;
	png_infop info_ptr = 0;
try
{
	//
	//	Open the file.
	//
	FILE * file = fopen( name.c_str(), "rb");
	if ( !file )
		throw std::logic_error( "fopen failed." );
	//
	//	First check the eight byte PNG signature.
	//
	png_byte signature[8];
	fread(signature, 1, 8, file);
	if (png_sig_cmp(signature, 0, 8))
		throw std::logic_error( "Bad signature." );
	//
	//	Create the two png(-info) structures.
	//
	png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, 
		0/*this*/,
		(png_error_ptr)error_handler,
		(png_error_ptr)warning_handler);
	if (!png_ptr)
		throw std::logic_error( "png_create_read_struct failed." );
	info_ptr = png_create_info_struct(png_ptr);
	if (!info_ptr)
		throw std::logic_error( "png_create_info_struct failed." );
	//
	//	Initialize the png structure.
	//
	png_init_io(png_ptr, file);
	png_set_sig_bytes(png_ptr, 8);
	//
	//	Read all PNG info up to image data.
	//
	png_read_info(png_ptr, info_ptr);
	//
	//	Get width, height, bit-depth and color-type.
	//
	png_uint_32 width;
	png_uint_32 height;
	int bitDepth;
	int colorType;
	png_get_IHDR(png_ptr, info_ptr, &width, &height, &bitDepth,
		&colorType, NULL, NULL, NULL);
	png_size_t rowBytes = png_get_rowbytes(png_ptr, info_ptr);
	int channels = png_get_channels(png_ptr, info_ptr);

	std::string colorTypeStr;
	switch ( colorType )
	{
	case PNG_COLOR_TYPE_GRAY:
			colorTypeStr = "PNG_COLOR_TYPE_GRAY";
			break;
	case PNG_COLOR_TYPE_GRAY_ALPHA:
			colorTypeStr = "PNG_COLOR_TYPE_GRAY_ALPHA";
			break;
	case PNG_COLOR_TYPE_PALETTE:
			colorTypeStr = "PNG_COLOR_TYPE_PALETTE";
			break;
	case PNG_COLOR_TYPE_RGB:
			colorTypeStr = "PNG_COLOR_TYPE_RGB";
			break;
	case PNG_COLOR_TYPE_RGB_ALPHA:
			colorTypeStr = "PNG_COLOR_TYPE_RGB_ALPHA";
			break;
	default:
		colorTypeStr = boost::str( boost::format( "%02x" ) % colorType );
	}

	std::cout << "Name: " << name << std::endl;
	std::cout << "Width: " << width << std::endl;
	std::cout << "Height: " << height << std::endl;
	std::cout << "Bit Depth: " << bitDepth << std::endl;
	std::cout << "Channels: " << channels << std::endl;
	std::cout << "Color Type: " << colorTypeStr << std::endl;
#if 0
	//
	//	Expand images of all color-type and bit-depth to 3x8-bit RGB.
	//	let the library process alpha, transparency, background, etc.
	//
	if (colorType == PNG_COLOR_TYPE_PALETTE)
		png_set_expand(png_ptr);
	if (bitDepth < 8)
		png_set_expand(png_ptr);
	if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS))
		png_set_expand(png_ptr);
	if (colorType == PNG_COLOR_TYPE_GRAY ||
		colorType == PNG_COLOR_TYPE_GRAY_ALPHA)
		png_set_gray_to_rgb(png_ptr);
	if (colorType == PNG_COLOR_TYPE_RGB_ALPHA ||
		colorType == PNG_COLOR_TYPE_GRAY_ALPHA ||
		png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS) )
		png_set_swap_alpha(png_ptr);
	else
		png_set_filler(png_ptr, ~0, PNG_FILLER_BEFORE);
	if (bitDepth == 16)
#  ifdef PNG_READ_SCALE_16_TO_8_SUPPORTED
		png_set_scale_16(png_ptr);
#  else
		png_set_strip_16(png_ptr);
#  endif
	//
	//	Set the background color to draw transparent and alpha images over.
	//
	png_color_16 * bg16;
	if (png_get_bKGD(png_ptr, info_ptr, &bg16))
		png_set_background(png_ptr, bg16, PNG_BACKGROUND_GAMMA_FILE, 1, 1.0);
	//
	//	If required set gamma conversion.
	//
	double dGamma;
	if (png_get_gAMA(png_ptr, info_ptr, &dGamma))
		png_set_gamma(png_ptr, (double) 2.2, dGamma);
	//
	//	After the transformations are registered, update info_ptr data.
	//
	png_read_update_info(png_ptr, info_ptr);
	//
	//	Get again width, height and the new bit-depth and color-type.
	//
	png_get_IHDR(png_ptr, info_ptr, &width, &height, &bitDepth, &colorType, 0, 0, 0);
	//
	//	Row_bytes is the width x number of channels.
	//
	if ( rowBytes != channels * width * bitDepth / 8 )
		throw std::logic_error( "row bytes is not equal to channels*width*bitDepth/8" );
	//
	//	Check if the image can store data.
	//
	if ( I::bit_depth != bitDepth )
		throw std::logic_error( "bit depth don't match." );
	if ( I::channels != channels )
		throw std::logic_error( "channels don't match." );
	//
	//	Now we can allocate memory to store the image.
	//
	image.resize( width, height );
	//
	//	Read data raw by raw.
	//
	for (png_uint_32 i = 0; i < height; i++)
		png_read_row( png_ptr, (png_bytep)image.getData( 0, i ), 0 );
	//
	//	Read the additional chunks in the PNG file (not really needed).
	//
	png_read_end(png_ptr, 0);
#endif
	//
	//	Destroy reader and info.
	//
	png_destroy_read_struct( &png_ptr, &info_ptr, 0 );
	png_ptr = 0;
	info_ptr = 0;
}
catch ( ... )
{
	if ( png_ptr )
		png_destroy_read_struct( &png_ptr, &info_ptr, 0 );
	throw;
}}

int main(int argc, char * argv[])
{try{

	if ( argc != 2 )
	{
		std::cerr << "Usage: " << argv[0] << " <png file name>" << std::endl;
		return 3;
	}
	
	print_info(argv[1]);

	return 0;
}
catch ( const std::exception& e )
{
	std::cerr << "Error: " << e.what() << std::endl;
	return 2;
}
catch (...)
{
	std::cerr << "Error: unknown." << std::endl;
	return 1;
}}

