/* -LICENSE-START-
 ** Copyright (c) 2018 Blackmagic Design
 **  
 ** Permission is hereby granted, free of charge, to any person or organization 
 ** obtaining a copy of the software and accompanying documentation (the 
 ** "Software") to use, reproduce, display, distribute, sub-license, execute, 
 ** and transmit the Software, and to prepare derivative works of the Software, 
 ** and to permit third-parties to whom the Software is furnished to do so, in 
 ** accordance with:
 ** 
 ** (1) if the Software is obtained from Blackmagic Design, the End User License 
 ** Agreement for the Software Development Kit (“EULA”) available at 
 ** https://www.blackmagicdesign.com/EULA/DeckLinkSDK; or
 ** 
 ** (2) if the Software is obtained from any third party, such licensing terms 
 ** as notified by that third party,
 ** 
 ** and all subject to the following:
 ** 
 ** (3) the copyright notices in the Software and this entire statement, 
 ** including the above license grant, this restriction and the following 
 ** disclaimer, must be included in all copies of the Software, in whole or in 
 ** part, and all derivative works of the Software, unless such copies or 
 ** derivative works are solely in the form of machine-executable object code 
 ** generated by a source language processor.
 ** 
 ** (4) THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 
 ** OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
 ** FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 
 ** SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 
 ** FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 
 ** ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 
 ** DEALINGS IN THE SOFTWARE.
 ** 
 ** A copy of the Software is available free of charge at 
 ** https://www.blackmagicdesign.com/desktopvideo_sdk under the EULA.
 ** 
 ** -LICENSE-END-
 */

#include <png.h>
#include <dirent.h>
#include <algorithm>
#include <cstring>
#include <string>
#include <sstream>
#include <csetjmp>
#include "ImageLoader.h"

static const uint32_t kPNGSignatureLength = 8;

HRESULT ImageLoader::GetPNGFilesFromDir(const std::string& path, std::vector<std::string>& fileList)
{
	HRESULT	result		= E_FAIL;	
	DIR* 	dirStream	= opendir(path.c_str());

	if (dirStream) 
	{
		struct dirent* dirFile;
		while ((dirFile = readdir(dirStream)) != NULL) 
		{
			// Skip directories and hidden files
			if ((dirFile->d_type == DT_DIR) || (dirFile->d_name[0] == '.'))
				continue;
			
			std::stringstream pngFilenameStream;
			pngFilenameStream << path << '/' << dirFile->d_name;
			
			std::string pngFilename = pngFilenameStream.str();
			
			// Find files ending with .png extension
			size_t fileExt = pngFilename.find_last_of(".");
			if ((fileExt != std::string::npos) && (pngFilename.substr(fileExt+1) == "png"))
			{
				fileList.push_back(pngFilename);
			}
		} 
		closedir(dirStream);
		
		// readdir does not guarantee order
		std::sort(fileList.begin(), fileList.end());

		result = S_OK;
	}
	return result;
}

HRESULT ImageLoader::ConvertPNGToDeckLinkVideoFrame(const std::string& pngFilename, IDeckLinkVideoFrame* deckLinkVideoFrame)
{
	HRESULT		result			= E_FAIL;
	png_structp	pngDataPtr		= nullptr;
	png_infop	pngInfoPtr		= nullptr;
	png_uint_32	width			= 0;
	png_uint_32	height			= 0;
	png_uint_32	rowBytes		= 0;
	png_byte	colorType		= 0;
	png_byte	bitDepth		= 0;
	png_byte	interlaceType	= 0;
	uint8_t*	deckLinkBuffer	= nullptr;
	png_bytep	rowPtr			= nullptr;

	uint8_t pngHeader[kPNGSignatureLength];
	std::vector<uint8_t> rowBuffer;
	
	uint32_t	videoFrameWidth		= 0;
	uint32_t	videoFrameHeight	= 0;
	uint32_t	videoFrameRowBytes	= 0;

	uint32_t	imageOffsetX		= 0;
	uint32_t	imageOffsetY		= 0;
	uint32_t	videoFrameOffsetX	= 0;
	uint32_t	videoFrameOffsetY	= 0;
	uint32_t	rowBytesToCopy		= 0;

	FILE* pngFile = fopen(pngFilename.c_str(), "rb");

	if (!pngFile)
	{
		fprintf(stderr, "Could not open PNG file %s\n", pngFilename.c_str());
		return E_FAIL;
	}

	fread(pngHeader, 1, 8, pngFile);
	if (png_sig_cmp(pngHeader, 0, 8))
	{
		fprintf(stderr, "%s is not a PNG file\n", pngFilename.c_str());
		goto bail;
	}

	pngDataPtr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
	if (!pngDataPtr)
	{
		fprintf(stderr, "Could not create PNG read struct\n");
		goto bail;
	}

	pngInfoPtr = png_create_info_struct(pngDataPtr);
	if (!pngInfoPtr)
	{
		fprintf(stderr, "Could not create PNG info struct\n");
		goto bail;
	}

	if (setjmp(png_jmpbuf(pngDataPtr)))
	{
		fprintf(stderr, "Failed PNG read initialization\n");
		goto bail;
	}

	png_init_io(pngDataPtr, pngFile);
	png_set_sig_bytes(pngDataPtr, kPNGSignatureLength);
	png_read_info(pngDataPtr, pngInfoPtr);

	width			= png_get_image_width(pngDataPtr, pngInfoPtr);
	height			= png_get_image_height(pngDataPtr, pngInfoPtr);
	colorType		= png_get_color_type(pngDataPtr, pngInfoPtr);
	bitDepth		= png_get_bit_depth(pngDataPtr, pngInfoPtr);
	rowBytes		= png_get_rowbytes(pngDataPtr, pngInfoPtr);
	interlaceType	= png_get_interlace_type(pngDataPtr, pngInfoPtr);

	if (bitDepth == 16)
	{
		// Strip 16-bit per channel to 8-bit 
		png_set_strip_16(pngDataPtr);
		rowBytes /= 2;
	}

	switch (colorType)
	{
		case PNG_COLOR_TYPE_PALETTE:
			// Convert color palette to RGB
			png_set_palette_to_rgb(pngDataPtr);
			rowBytes = width * 3;
			// Intended flow through

		case PNG_COLOR_TYPE_RGB:
			// Add filler to 24-bit RGB to convert into RGBA
			png_set_filler(pngDataPtr, 0xff, PNG_FILLER_AFTER);
			rowBytes += (rowBytes / 3);
			break;

		case PNG_COLOR_TYPE_RGB_ALPHA:
			// No transformation required
			break;

		default:
			// Unsupported format
			fprintf(stderr, "PNG file is in unsupported format\n");
			goto bail;
	}
	
	if (interlaceType != PNG_INTERLACE_NONE)
	{
		fprintf(stderr, "PNG interlacing is not supported\n");
		goto bail;		
	}

	if (setjmp(png_jmpbuf(pngDataPtr)))
	{
		fprintf(stderr, "Failed read PNG image data\n");
		goto bail;
	}

	videoFrameWidth = deckLinkVideoFrame->GetWidth();
	videoFrameHeight = deckLinkVideoFrame->GetHeight();
	videoFrameRowBytes = deckLinkVideoFrame->GetRowBytes();

	if (deckLinkVideoFrame->GetBytes((void**)&deckLinkBuffer) != S_OK)
	{
		fprintf(stderr, "Could not get DeckLinkVideoFrame buffer pointer\n");
		goto bail;
	}

	// Clear buffer in video frame, so we can display image smaller than the video frame size without artifacts
	memset(deckLinkBuffer, 0, videoFrameRowBytes * videoFrameHeight);

	png_set_bgr(pngDataPtr);

	rowBuffer.resize(rowBytes);
	rowPtr = rowBuffer.data();

	// Determine X and Y offsets for when image is smaller than output video frame
	videoFrameOffsetX = videoFrameWidth > (uint32_t)width ? (videoFrameWidth - (uint32_t)width) / 2 : 0;
	videoFrameOffsetY = videoFrameHeight > (uint32_t)height ? (videoFrameHeight - (uint32_t)height) / 2 : 0;

	imageOffsetX = (uint32_t)width > videoFrameWidth ? ((uint32_t)width - videoFrameWidth) / 2 : 0;
	imageOffsetY = (uint32_t)height > videoFrameHeight ? ((uint32_t)height - videoFrameHeight) / 2 : 0;

	rowBytesToCopy = std::min((uint32_t)rowBytes, videoFrameRowBytes);

	// If image smaller than video frame, skip lines of buffer
	deckLinkBuffer += videoFrameOffsetY * videoFrameRowBytes;

	// Set row pointers to DeckLinkVideoFrame buffer
	for (uint32_t row = 0; row < height; ++row)
	{
		png_read_row(pngDataPtr, rowPtr, NULL);
		
		if (row < imageOffsetY)
			continue;
		else if (row >= (imageOffsetY + videoFrameHeight))
			break;
			
		memcpy(deckLinkBuffer + videoFrameOffsetX * 4, rowPtr + imageOffsetX * 4, rowBytesToCopy);
		deckLinkBuffer += videoFrameRowBytes;
	}

	result = S_OK;

bail:
	png_destroy_read_struct(&pngDataPtr, &pngInfoPtr, nullptr);
	fclose(pngFile);

	return result;
}

