//-----------------------------------------------------------------------------
// Includes
//-----------------------------------------------------------------------------		
#include <Render/DDSImage.hpp>
#include <cstdio>
#include <limits>
#include <algorithm>
#include <cstring>

namespace Render
{
	//-------------------------------------------------------------------------
	// DDS Header constants
	//-------------------------------------------------------------------------
	//  little-endian, of course
	#define DDS_MAGIC 					0x20534444
	//-------------------------------------------------------------------------
	//  DDS_header.dwFlags
	#define DDSD_CAPS                   0x00000001 
	#define DDSD_HEIGHT                 0x00000002 
	#define DDSD_WIDTH                  0x00000004 
	#define DDSD_PITCH                  0x00000008 
	#define DDSD_PIXELFORMAT            0x00001000 
	#define DDSD_MIPMAPCOUNT            0x00020000 
	#define DDSD_LINEARSIZE             0x00080000 
	#define DDSD_DEPTH                  0x00800000 
	//-------------------------------------------------------------------------
	//  DDS_header.sPixelFormat.dwFlags
	#define DDPF_ALPHAPIXELS            0x00000001 
	#define DDPF_FOURCC                 0x00000004 
	#define DDPF_INDEXED                0x00000020 
	#define DDPF_RGB                    0x00000040 
	//-------------------------------------------------------------------------
	//  DDS_header.sCaps.dwCaps1
	#define DDSCAPS_COMPLEX             0x00000008 
	#define DDSCAPS_TEXTURE             0x00001000 
	#define DDSCAPS_MIPMAP              0x00400000 
	//-------------------------------------------------------------------------
	//  DDS_header.sCaps.dwCaps2
	#define DDSCAPS2_CUBEMAP            0x00000200 
	#define DDSCAPS2_CUBEMAP_POSITIVEX  0x00000400 
	#define DDSCAPS2_CUBEMAP_NEGATIVEX  0x00000800 
	#define DDSCAPS2_CUBEMAP_POSITIVEY  0x00001000 
	#define DDSCAPS2_CUBEMAP_NEGATIVEY  0x00002000 
	#define DDSCAPS2_CUBEMAP_POSITIVEZ  0x00004000 
	#define DDSCAPS2_CUBEMAP_NEGATIVEZ  0x00008000 
	#define DDSCAPS2_VOLUME             0x00200000 
	//-------------------------------------------------------------------------
	#define D3DFMT_DXT1     			0x31545844    //  DXT1 compression texture format (MAKEFOURCC('D','X','T','1'))
	#define D3DFMT_DXT3     			0x33545844    //  DXT3 compression texture format (MAKEFOURCC('D','X','T','3'))
	#define D3DFMT_DXT5     			0x35545844    //  DXT5 compression texture format (MAKEFOURCC('D','X','T','5'))
	//-------------------------------------------------------------------------
	// DXT Format
	//-------------------------------------------------------------------------
	#define PF_IS_DXT1(pf) \
		((pf.dwFlags & DDPF_FOURCC) && \
 		 (pf.dwFourCC == D3DFMT_DXT1))
	//-------------------------------------------------------------------------
	#define PF_IS_DXT3(pf) \
		((pf.dwFlags & DDPF_FOURCC) && \
		 (pf.dwFourCC == D3DFMT_DXT3))
	//-------------------------------------------------------------------------
	#define PF_IS_DXT5(pf) \
		((pf.dwFlags & DDPF_FOURCC) && \
		 (pf.dwFourCC == D3DFMT_DXT5))
	//-------------------------------------------------------------------------
	// 8bits per channel Format
	//-------------------------------------------------------------------------
	#define PF_IS_ABGR8(pf) \
		((pf.dwFlags & DDPF_RGB) && \
		 (pf.dwFlags & DDPF_ALPHAPIXELS) && \
		 (pf.dwRGBBitCount == 32) && \
		 (pf.dwRBitMask == 0x000000ff) && \
		 (pf.dwGBitMask == 0x0000ff00) && \
		 (pf.dwBBitMask == 0x00ff0000) && \
		 (pf.dwAlphaBitMask == 0xff000000))
	//-------------------------------------------------------------------------
	#define PF_IS_ARGB8(pf) \
		((pf.dwFlags & DDPF_RGB) && \
		 (pf.dwFlags & DDPF_ALPHAPIXELS) && \
		 (pf.dwRGBBitCount == 32) && \
		 (pf.dwRBitMask == 0x00ff0000) && \
		 (pf.dwGBitMask == 0x0000ff00) && \
		 (pf.dwBBitMask == 0x000000ff) && \
		 (pf.dwAlphaBitMask == 0xff000000))
	//-------------------------------------------------------------------------
	// 16bits per channel Format
	//-------------------------------------------------------------------------
	#define PF_IS_ABGR16(pf) \
		((pf.dwFlags & DDPF_RGB) && \
		 (pf.dwFlags & DDPF_ALPHAPIXELS) && \
		 (pf.dwRGBBitCount == 64) && \
		 (pf.dwRBitMask == 0x000000ff) && \
		 (pf.dwGBitMask == 0x0000ff00) && \
		 (pf.dwBBitMask == 0x00ff0000) && \
		 (pf.dwAlphaBitMask == 0xff000000))
	//-------------------------------------------------------------------------
	#define PF_IS_ARGB16(pf) \
		((pf.dwFlags & DDPF_RGB) && \
		 (pf.dwFlags & DDPF_ALPHAPIXELS) && \
		 (pf.dwRGBBitCount == 64) && \
		 (pf.dwRBitMask == 0x00ff0000) && \
		 (pf.dwGBitMask == 0x0000ff00) && \
		 (pf.dwBBitMask == 0x000000ff) && \
		 (pf.dwAlphaBitMask == 0xff000000))
	//-------------------------------------------------------------------------
	// 32bits per channel Format
	//-------------------------------------------------------------------------
	#define PF_IS_ABGR32(pf) \
		((pf.dwFlags & DDPF_RGB) && \
		 (pf.dwFlags & DDPF_ALPHAPIXELS) && \
		 (pf.dwRGBBitCount == 128) && \
		 (pf.dwRBitMask == 0x000000ff) && \
		 (pf.dwGBitMask == 0x0000ff00) && \
		 (pf.dwBBitMask == 0x00ff0000) && \
		 (pf.dwAlphaBitMask == 0xff000000))
	//-------------------------------------------------------------------------
	#define PF_IS_ARGB32(pf) \
		((pf.dwFlags & DDPF_RGB) && \
		 (pf.dwFlags & DDPF_ALPHAPIXELS) && \
		 (pf.dwRGBBitCount == 128) && \
		 (pf.dwRBitMask == 0x00ff0000) && \
		 (pf.dwGBitMask == 0x0000ff00) && \
		 (pf.dwBBitMask == 0x000000ff) && \
		 (pf.dwAlphaBitMask == 0xff000000))
	//-------------------------------------------------------------------------
	// Mix Format
	//-------------------------------------------------------------------------
	#define PF_IS_BGR5A1(pf) \
		((pf.dwFlags & DDPF_RGB) && \
		 (pf.dwFlags & DDPF_ALPHAPIXELS) && \
		 (pf.dwRGBBitCount == 16) && \
		 (pf.dwRBitMask == 0x00007c00) && \
		 (pf.dwGBitMask == 0x000003e0) && \
		 (pf.dwBBitMask == 0x0000001f) && \
		 (pf.dwAlphaBitMask == 0x00008000))
	//-------------------------------------------------------------------------
	#define PF_IS_BGR565(pf) \
		((pf.dwFlags & DDPF_RGB) && \
		!(pf.dwFlags & DDPF_ALPHAPIXELS) && \
		 (pf.dwRGBBitCount == 16) && \
		 (pf.dwRBitMask == 0x0000f800) && \
		 (pf.dwGBitMask == 0x000007e0) && \
		 (pf.dwBBitMask == 0x0000001f))
	//-------------------------------------------------------------------------
	// Indexed Format
	//-------------------------------------------------------------------------
	#define PF_IS_INDEX8(pf) \
		((pf.dwFlags & DDPF_INDEXED) && \
		 (pf.dwRGBBitCount == 8))
	//-------------------------------------------------------------------------
	union DDSHeader 
	{
		struct 
		{
			unsigned int    dwMagic;
			unsigned int    dwSize;
			unsigned int    dwFlags;
			unsigned int    dwHeight;
			unsigned int    dwWidth;
			unsigned int    dwPitchOrLinearSize;
			unsigned int    dwDepth;
			unsigned int    dwMipMapCount;
			unsigned int    dwReserved1[ 11 ];

			// DDPIXELFORMAT
			struct 
			{
				unsigned int    dwSize;
				unsigned int    dwFlags;
				unsigned int    dwFourCC;
				unsigned int    dwRGBBitCount;
				unsigned int    dwRBitMask;
				unsigned int    dwGBitMask;
				unsigned int    dwBBitMask;
				unsigned int    dwAlphaBitMask;
			} sPixelFormat;

			// DDCAPS2
			struct 
			{
				unsigned int    dwCaps1;
				unsigned int    dwCaps2;
				unsigned int    dwDDSX;
				unsigned int    dwReserved;
			} sCaps;

			unsigned int    dwReserved2;
		};

		char data[ 128 ];
	};


	//-----------------------------------------------------------------
	// C/D-Tor
	//-----------------------------------------------------------------
	DDSImage::DDSImage(int _width, int _height):
	Film(_width, _height)
	{
		pixels = new PixelComponent[Width*Height*4];
		std::memset(pixels, 0x0, Width*Height*4*sizeof(PixelComponent));

		values = new ValueComponent[Width*Height*4];
		std::memset(values, 0x0, Width*Height*4*sizeof(ValueComponent));		
	}
	//-----------------------------------------------------------------
	DDSImage::~DDSImage()
	{
		delete[] values;
		delete[] pixels;
	}
	//-----------------------------------------------------------------
	void DDSImage::AccumulateRadiance(const Math::ivec2& _pixel, const Math::vec4& _color)
	{
		int index = (_pixel.x + ((Width-1)-_pixel.y)*Width) * 4;
		values[index+0] += _color.r;
		values[index+1] += _color.g;
		values[index+2] += _color.b;
		values[index+3] += _color.a;
	}
	//-----------------------------------------------------------------
	void DDSImage::ToneMapScale(float* _src, unsigned char* _dst, int _nPixels)
	{
		float maxValue = std::numeric_limits<float>::min();
		for(int p=0;p<_nPixels;++p)
		{
			int index = p*4;
			maxValue = std::max(maxValue,_src[index+0]);
			maxValue = std::max(maxValue,_src[index+1]);
			maxValue = std::max(maxValue,_src[index+2]);
		}

		float invMaxValue = 1.f / maxValue;
		for(int p=0;p<_nPixels;++p)
		{
			int index = p*4;
			_dst[index+0] = 255 * _src[index+0] * invMaxValue;
			_dst[index+1] = 255 * _src[index+1] * invMaxValue;
			_dst[index+2] = 255 * _src[index+2] * invMaxValue;
			_dst[index+3] = 255 * _src[index+3];
		}
	}
	//-----------------------------------------------------------------
	void DDSImage::SaveToFile(const std::string& _filename)
	{
		// Open file
		FILE* file = fopen(_filename.c_str(), "wb");
		assert(file!=NULL); // "Loading image failed - Can't open file (file:'%s')",_filename.c_str());

		// Get image data
		unsigned int blockBytes = 4*sizeof(PixelComponent); // Size in bytes

		// Create header
		DDSHeader header;
		header.dwMagic 						= DDS_MAGIC ;
		header.dwSize  						= 124;
		header.dwFlags						= DDSD_HEIGHT | DDSD_WIDTH | DDSD_CAPS | DDSD_PIXELFORMAT;
		header.dwHeight						= Height;
		header.dwWidth						= Width;
		header.dwPitchOrLinearSize			= 0x0;
		header.dwDepth						= 0x0; //TODO for depth texture
		header.dwMipMapCount				= 0x0;

		header.sPixelFormat.dwSize			= 32;
		header.sPixelFormat.dwFlags			= DDPF_RGB | DDPF_ALPHAPIXELS;
		header.sPixelFormat.dwFourCC		= 0x0;
		header.sPixelFormat.dwRGBBitCount	= blockBytes*8; // Convert size in bits
		header.sPixelFormat.dwRBitMask		= 0x000000ff;	// TODO : change for depth texture
		header.sPixelFormat.dwGBitMask		= 0x0000ff00;	//
		header.sPixelFormat.dwBBitMask		= 0x00ff0000;	//
		header.sPixelFormat.dwAlphaBitMask	= 0xff000000;	//

		header.sCaps.dwCaps1				= 0x0;
		header.sCaps.dwCaps2				= 0x0;
		header.sCaps.dwDDSX					= 0x0;
		header.sCaps.dwReserved				= 0x0;
		header.dwReserved2					= 0x0;		

		// Write DDS header
		fwrite(&header, sizeof(header), 1, file);

		// Apply tone mapping
		ToneMapScale(values, pixels, Width * Height);

		// Copy pixel
		long int dataSize = Width * Height * blockBytes;
		fwrite(reinterpret_cast<const unsigned char*>(pixels), 1, dataSize, file);

		fclose(file);
	}

}
