//	VirtualDub - Video processing and capture application
//	Modification for PNG support
//  Added by Cyrius, using libpng 1.2.4 and zlib 1.1.4
//
//	This program 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 2 of the License, or
//	(at your option) any later version.
//
//	This program is distributed in the hope that it will be useful,
//	but WITHOUT ANY WARRANTY; without even the implied warranty of
//	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//	GNU General Public License for more details.
//
//	You should have received a copy of the GNU General Public License
//	along with this program; if not, write to the Free Software
//	Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

#include <stdio.h>
#include <ctype.h>

#include <windows.h>
#include <vfw.h>

#include <crtdbg.h>
#include <limits.h>

#include "../oshelper.h"
#include "../Error.h"
#include "../ProgressDialog.h"
#include "VideoSourceImagesPNG.h"
#include "../OGM/OGMCommonDefs.h"

#ifndef INVALID_SET_FILE_POINTER
#define INVALID_SET_FILE_POINTER ((DWORD)-1)
#endif

extern HWND g_hWnd;

VideoSourceImagesPNG::VideoSourceImagesPNG(const char *pszBaseFormat)
: VideoSourceImages()
{
	png_ptr = NULL;
	info_ptr = NULL;
	end_info = NULL;
	//Prepare the fake file header
	memset(&bfh, 0, sizeof(bfh));
	bfh.bfType = 'MB';
	bfh.bfReserved1 = 0;
	bfh.bfReserved2 = 0;

	//Prepare the fake bitmap header
	memset(&bih, 0, sizeof(bih));
	bih.biBitCount = 24;
	bih.biCompression = BI_RGB;
	bih.biPlanes = 1;
	bih.biSize = sizeof(bih);
	bih.biSizeImage = 0;

	// Fake BMP in memory : file header + bitmap header + image
	bfh.bfOffBits = sizeof(bfh) + sizeof(bih);

	try {
		_construct(pszBaseFormat);
	} catch(...) {
		_destruct();
		throw;
	}
}

VideoSourceImagesPNG::~VideoSourceImagesPNG(void) {
	_reset();
}

void VideoSourceImagesPNG::_reset(void) {
	if(png_ptr || info_ptr || end_info)
		png_destroy_read_struct(
		(png_ptr ? &png_ptr : (png_structpp)NULL)
		, (info_ptr ? &info_ptr : (png_infopp)NULL)
		, (end_info ? &end_info : (png_infopp)NULL));
	png_ptr = NULL;
	info_ptr = NULL;
	end_info = NULL;
}

void png_error_fn(png_structp png_ptr, png_const_charp error_msg) {
	throw MyError(error_msg);
}

void png_warning_fn(png_structp png_ptr, png_const_charp warning_msg) {
	_RPT0(_CRT_WARN, warning_msg);
}

void png_read_data_fn(png_structp png_ptr, png_bytep data, png_size_t length) {
	HANDLE h = (HANDLE)png_get_io_ptr(png_ptr);
	if(!h)
		png_error(png_ptr, "No file Handle provided");

	DWORD read = 0;
	BOOL success = ReadFile(h, data, length, &read, NULL);
	if(!success)
		throw MyWin32Error("Cannot read image file \"%s\":\n%%s", GetLastError(), data);

	if(read != length)
		png_error(png_ptr, "Unexpected EOF encountered");
}

int VideoSourceImagesPNG::_read(LONG lStart, LONG lCount, LPVOID lpBuffer, LONG cbBuffer, LONG *plBytesRead, LONG *plSamplesRead) {
	if (plBytesRead)
		*plBytesRead = 0;

	if (plSamplesRead)
		*plSamplesRead = 0;

	char buf[512];

	if (_snprintf(buf, sizeof buf, mszPathFormat, lStart + mImageBaseNumber) < 0)
		return 0;

	// Check if we already have the file handle cached.  If not, open the file.

	HANDLE h;
	//	Pulco 29/10/2002 : moved some declarations earlier in the code to avoid "skipped by goto" errors
	static png_byte   **ppbRowPointers = NULL;
	png_byte           *pbImageData = (png_byte *)lpBuffer;
	
	if (lStart == mCachedHandleFrame) {
		h = mCachedHandle;
		if (INVALID_SET_FILE_POINTER == SetFilePointer(h, 0, NULL, FILE_BEGIN))
			goto read_fail;
	} else{
		h = CreateFile(buf, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);

		if (h == INVALID_HANDLE_VALUE)
			goto read_fail;

		// Close the old cached handle and replace it with the new one.

		if (mCachedHandle != INVALID_HANDLE_VALUE)
			CloseHandle(mCachedHandle);

		mCachedHandle = h;
		mCachedHandleFrame = lStart;
	}

	DWORD dwActual;
	BOOL bSucceeded;

	png_byte png_sig[8];
	int                 iBitDepth;
	int                 iColorType;
	double              dGamma;
	png_color_16       *pBackground;
	png_uint_32         ulChannels;
	png_uint_32         ulRowBytes;
	png_uint_32         i;
	png_uint_32         width;
	png_uint_32         height;
	int                 channels;

	_reset();

	png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING
		, (png_voidp)NULL/*(png_voidp)user_error_ptr*/, (png_error_ptr)png_error_fn, png_warning_fn);
	if(!png_ptr)
		throw MyMemoryError();

	info_ptr = png_create_info_struct(png_ptr);
	if(!info_ptr) {
		png_destroy_read_struct(&png_ptr, (png_infopp)NULL, (png_infopp)NULL);
		throw MyMemoryError();
	}
	end_info = png_create_info_struct(png_ptr);
	if(!end_info) {
		png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL);
		throw MyMemoryError();
	}

	// Tell the read function what is our pointer
	png_set_read_fn(png_ptr, (voidp)h, (png_rw_ptr)png_read_data_fn);

	bSucceeded = ReadFile(h, png_sig, 8, &dwActual, NULL);
	if(!bSucceeded)
		goto read_fail;
	if((dwActual!=8) || (!png_check_sig(png_sig, 8)))
		throw MyError("File doesn't seem to be a valid PNG file");


	png_set_sig_bytes(png_ptr, 8);
	png_read_info(png_ptr, info_ptr);

	png_get_IHDR(png_ptr, info_ptr, &width, &height, &iBitDepth,
			&iColorType, NULL, NULL, NULL);
        
	//iBitDepth = bits / channel

	// 16 bits => 8 bits
	if(iBitDepth == 16)
		png_set_strip_16(png_ptr);

	// < 8 bits => 8 bits
	if(iBitDepth < 8)
		png_set_packing(png_ptr);

	// Paletted color => RGB
	if(iColorType == PNG_COLOR_TYPE_PALETTE)
		png_set_palette_to_rgb(png_ptr);

	// Grayscale (<8 bits) => 8 bits
	/*if(iColorType == PNG_COLOR_TYPE_GRAY && iBitDepth < 8)
		png_set_gray_1_2_4_to_8(png_ptr);*/

	// Grayscale => RGB
	if(iColorType == PNG_COLOR_TYPE_GRAY || iColorType == PNG_COLOR_TYPE_GRAY_ALPHA)
		png_set_gray_to_rgb(png_ptr);

	// Remove alpha transparency
	if(iColorType & PNG_COLOR_MASK_ALPHA)
		png_set_strip_alpha(png_ptr);

	// RGB => BGR
	png_set_bgr(png_ptr);
	
	// set the background color to draw transparent and alpha images over.
	/*if(png_get_bKGD(png_ptr, info_ptr, &pBackground)) {
			png_set_background(png_ptr, pBackground, PNG_BACKGROUND_GAMMA_FILE, 1, 1.0);
			pBkgColor->red   = (byte) pBackground->red;
			pBkgColor->green = (byte) pBackground->green;
			pBkgColor->blue  = (byte) pBackground->blue;
	} else {
			pBkgColor = NULL;
	}*/
        
  // if required set gamma conversion
	if(png_get_gAMA(png_ptr, info_ptr, &dGamma))
			png_set_gamma(png_ptr, (double) 2.2, dGamma);
        
  // after the transformations have been 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, &iBitDepth,
			&iColorType, NULL, NULL, NULL);
        
        
  // row_bytes is the width x number of channels
        
	//ulRowBytes = png_get_rowbytes(png_ptr, info_ptr);
	// size of one line must be a multiple of 4 (>>5 <=> /32)
	ulRowBytes = ((width*24+31)>>5)*4;
	ulChannels = png_get_channels(png_ptr, info_ptr);
        

	s64 dwSize;
	dwSize = ulRowBytes * height * sizeof(png_byte);
        
	//Prepare the fake file header
	bfh.bfSize = dwSize;

	//Prepare the fake bitmap header
	bih.biWidth = width;
	bih.biHeight = height;

	dwSize += bfh.bfOffBits;

	if(!pbImageData) {
		if(plBytesRead)
			*plBytesRead = (LONG)dwSize;
		if(plSamplesRead)
			*plSamplesRead = 1;

		return AVIERR_OK;
	}

	if(dwSize > (s64)cbBuffer) {
		if(plBytesRead) {
			if(dwSize > _I32_MAX)
				*plBytesRead = 0x7FFFFFFFUL;		// uh oh....
			else
				*plBytesRead = dwSize;
		}

		return AVIERR_BUFFERTOOSMALL;
	}

	memcpy(pbImageData, &bfh, sizeof(bfh));
	pbImageData += sizeof(bfh);
	memcpy(pbImageData, &bih, sizeof(bih));
	pbImageData += sizeof(bih);
       
  // allocate memory for an array of row-pointers
        
	if((ppbRowPointers=(png_bytepp) malloc((height) * sizeof(png_bytep))) == NULL)
		throw MyMemoryError();
        
  // set the individual row-pointers to point at the correct offsets
        
	// set upside-down
	for (i = 0; i < height; i++)
			//ppbRowPointers[i] = pbImageData + i * ulRowBytes;
			ppbRowPointers[i] = pbImageData + (height-i-1) * ulRowBytes;
        
  // now we can go ahead and just read the whole image
        
	png_read_image(png_ptr, ppbRowPointers);
        
  // read the additional chunks in the PNG file (not really needed)
        
	png_read_end(png_ptr, NULL);
        
  // and we're done
        
	free(ppbRowPointers);
	ppbRowPointers = NULL;
        
  // yepp, done

	if (plBytesRead)
		*plBytesRead = (LONG)dwSize;

	if (plSamplesRead)
		*plSamplesRead = 1;

	return 0;

read_fail:
	throw MyWin32Error("Cannot read image file \"%s\":\n%%s", GetLastError(), buf);
}