#include <windows.h>
#include <stdio.h>
#include <tchar.h>
#include <ddraw.h>
#include "../imagelib/cImage.h"
#include "cImgFile.h"

/*
#ifdef _DEBUG
  #pragma comment(lib, "FilePackage\\imagelib\\imagelib_d.lib")
#else
  #pragma comment(lib, "FilePackage\\imagelib\\imagelib.lib")
#endif *//*_DEBUG*/

cImgFile::cImgFile() : cFileObj()
{
	m_fileType		= FILE_TYPE_IMAGE;		// 圖形格式
	m_imageFormat	= IMAGE_UNKNOWN;		// 圖形格式
	m_width			= 0;					// 圖形寬度
	m_height		= 0;					// 圖形高度
	m_mipmapLevels	= 0;					// mipmap數量 (0或1都代表沒有mipmap)
}

cImgFile::~cImgFile()
{
	Destroy();
}

void cImgFile::Destroy()
{
	cFileObj::Destroy();
	m_imageFormat	= IMAGE_UNKNOWN;		// 圖形格式
	m_width			= 0;					// 圖形寬度
	m_height		= 0;					// 圖形高度
	m_mipmapLevels	= 0;					// mipmap數量 (0或1都代表沒有mipmap)
}

bool cImgFile::LoadFromFile( const TCHAR *workDirStr, const TCHAR *fileNameStr, CompressFormat format, void *pSpecialData )
{
	Destroy();

	TCHAR fullNameStr[MAX_PATH];
	_stprintf( fullNameStr, "%s%s", workDirStr, fileNameStr );
	if ( !Read( fullNameStr, (ReadImageOptions*)pSpecialData ) ) return false;

	m_fileType			= FILE_TYPE_IMAGE;
	m_compressFormat	= COMPRESS_FMT_NORMAL;
	SetFileName( fileNameStr );
	ConvertFormat( format );			// 轉換為指定壓縮格式

	return true;
}

bool cImgFile::Read( const TCHAR *fileNameStr, ReadImageOptions *pSpecialData )
{
	assert( fileNameStr );

	cImage img;
	if ( !img.Load( fileNameStr ) )
	{
		if ( ReadDDS( fileNameStr, pSpecialData ) )
			return true;
		return false;
	}	
	
	ReadImageOptions info;
	if ( pSpecialData ) info = *pSpecialData;
	if ( info.fmt == IMAGE_UNKNOWN )
	{
		switch (img.GetFormat())
		{
		case PF_R8G8B8:
			info.fmt = IMAGE_R5G6B5;
			break;

		case PF_I8:
		case PF_A8R8G8B8:
		default:
			info.fmt = IMAGE_A8R8G8B8;
			break;
		}
	}

	if ( img.GetDepth() != 32 )
		img.ConvertFormat(PF_A8R8G8B8);

	m_width	= img.GetXSize();
	m_height = img.GetYSize();

	switch ( info.fmt )
	{
		case IMAGE_DXT1:
		case IMAGE_DXT1a:
		case IMAGE_DXT3:
		case IMAGE_DXT5:
		{
			nvCompressionOptions options;
			if ( !info.mipmapBool ) options.mipMapGeneration = kNoMipMaps;
			if ( info.fmt == IMAGE_DXT1 ) options.textureFormat = kDXT1;
			else if ( info.fmt == IMAGE_DXT1a ) options.textureFormat = kDXT1a;
			else if ( info.fmt == IMAGE_DXT3 ) options.textureFormat = kDXT3;
			else if ( info.fmt == IMAGE_DXT5 ) options.textureFormat = kDXT5;
			m_pData = CompressDXT( img.GetPixels(), m_width, m_height, m_dataSize, m_mipmapLevels, &options );
			m_imageFormat = info.fmt;
			break;
		}
		case IMAGE_R5G6B5:
		case IMAGE_A1R5G5B5:
		{
			m_dataSize = m_width * m_height * 2;
			m_pData = new BYTE[m_dataSize];
			m_imageFormat = info.fmt;
			Transfrom( m_pData, m_imageFormat, 0, 0, m_width, m_height, m_width, img.GetPixels(), m_width, m_height );
			break;
		}
		case IMAGE_R8G8B8:
		{
			m_dataSize = m_width * m_height * 3;
			m_pData = new BYTE[m_dataSize];
			m_imageFormat = info.fmt;
			Transfrom( m_pData, m_imageFormat, 0, 0, m_width, m_height,m_width, img.GetPixels(), m_width, m_height );
			break;
		}
		default:
		{
			m_dataSize = m_width * m_height * 4;
			m_pData = new BYTE[m_dataSize];
			m_imageFormat = IMAGE_A8R8G8B8;
			memcpy(m_pData, img.GetPixels(), m_dataSize);
			//Transfrom( m_pData, m_imageFormat, 0, 0, m_width, m_height, img.GetPixels(), m_width, m_height );
			break;
		}
	}

	img.Destroy();

	return true;
}

bool cImgFile::ReadDDS( const TCHAR *fileNameStr, ReadImageOptions *pSpecialData )
{
	FILE *fptr;	
	if ( (fptr = fopen( fileNameStr, "rb" )) == NULL )
		return false;

	int magicNumber;
	fread( &magicNumber, sizeof(int), 1, fptr );
	if ( memcmp(&magicNumber, "DDS ", 4) != 0 )
	{
		fclose( fptr );
		return false;
	}

	DDSURFACEDESC2 ddsd;
	fread( &ddsd, sizeof(DDSURFACEDESC2), 1, fptr );

	int size		= 0;
	m_width			= (int)ddsd.dwWidth;
	m_height		= (int)ddsd.dwHeight;
	m_mipmapLevels	= (int)ddsd.dwMipMapCount;

	if ( m_mipmapLevels <= 0 )
		 m_mipmapLevels = 1;
	else
	{
		int max = GetMipmapCount( m_width, m_height );
		if ( m_mipmapLevels > max )
			m_mipmapLevels = max;
	}
	
	// Determine number of texels in the texture surfaces (including mipmaps)
	int w = max( MIN_UNIT, ddsd.dwWidth );
	int h = max( MIN_UNIT, ddsd.dwHeight );
	for (int i = 0; i < m_mipmapLevels; i++ )
	{
		size += w * h;
		if ( w > MIN_UNIT ) w >>= 1;
		if ( h > MIN_UNIT ) h >>= 1;
	}

	if ( size <= 0 )
	{
		fclose( fptr );
		return false;
	}

	if(ddsd.ddpfPixelFormat.dwFlags & DDPF_RGB)
	{
		// For 32-bit textures, assume A8R8G8B8
		if(ddsd.ddpfPixelFormat.dwRGBBitCount == 32)
		{
			size *= 4;
			m_imageFormat = IMAGE_A8R8G8B8;
		}
		else
		{
			fclose( fptr );
			return false;
		}
	}
	else if(ddsd.ddpfPixelFormat.dwFlags & DDPF_FOURCC)
	{
		switch ( ddsd.ddpfPixelFormat.dwFourCC )
		{
		case DXT_1:
			size /= 2;
			m_imageFormat = IMAGE_DXT1a;
			break;

		case DXT_3:
			m_imageFormat = IMAGE_DXT3;
			break;

		case DXT_5:
			m_imageFormat = IMAGE_DXT5;
			break;

		default:
			fclose( fptr );
			return false;
		}
	}

	// Read data
	m_dataSize = size;
	m_pData = new BYTE[m_dataSize];
	fread( m_pData, m_dataSize, 1, fptr );

	fclose( fptr );
	return true;
}

bool cImgFile::BltToRGB( void *pDest, ImageFormat format, int x, int y, int width, int height, int pitch, int mipmapLevel )
{
	if ( pDest == NULL || m_pData == NULL ) return false;
	if ( format != IMAGE_R5G6B5 && format != IMAGE_A1R5G5B5 && format != IMAGE_R8G8B8 && format != IMAGE_A8R8G8B8 ) return false;

	RGBQUAD *pPixels = (RGBQUAD*)m_pData;
	RGBQUAD *pNew = NULL;
	if ( m_imageFormat != IMAGE_A8R8G8B8 ) {
		pNew = new RGBQUAD[m_width * m_height];
		switch ( m_imageFormat )
		{
			case IMAGE_R5G6B5:
			{
				WORD *pSrc = (WORD*)m_pData;
				for ( int i = 0; i < m_width * m_height; i++ ) {
					pNew[i].rgbReserved	= 0xFF;
					pNew[i].rgbBlue		= g_5BitsTo8BitsArray[pSrc[i] & 0x1F];
					pNew[i].rgbGreen	= g_6BitsTo8BitsArray[(pSrc[i] >> 5) & 0x3F];
					pNew[i].rgbRed		= g_5BitsTo8BitsArray[(pSrc[i] >> 11) & 0x1F];
				}
				break;
			}
			case IMAGE_A1R5G5B5:
			{
				WORD *pSrc = (WORD*)m_pData;
				for ( int i = 0; i < m_width * m_height; i++ ) {
					pNew[i].rgbReserved	= ( pSrc[i] & 0x8000 ) ? 0xFF : 0;
					pNew[i].rgbBlue		= g_5BitsTo8BitsArray[pSrc[i] & 0x1F];
					pNew[i].rgbGreen	= g_5BitsTo8BitsArray[(pSrc[i] >> 5) & 0x1F];
					pNew[i].rgbRed		= g_5BitsTo8BitsArray[(pSrc[i] >> 10) & 0x1F];
				}
				break;
			}
			case IMAGE_R8G8B8:
			{
				BGRCOLOR *pSrc = (BGRCOLOR*)m_pData;
				for ( int i = 0; i < m_width * m_height; i++ ) {
					pNew[i].rgbBlue		= pSrc[i].r;
					pNew[i].rgbGreen	= pSrc[i].g;
					pNew[i].rgbRed		= pSrc[i].r;
					pNew[i].rgbReserved	= 0xFF;
				}
				break;
			}
			case IMAGE_DXT1a:
			case IMAGE_DXT1:
				PlotDXT1( (BYTE*)pNew, m_pData, m_width, m_height );
				break;
			case IMAGE_DXT3:
				PlotDXT3( (BYTE*)pNew, m_pData, m_width, m_height );
				break;
			case IMAGE_DXT5:
				PlotDXT5( (BYTE*)pNew, m_pData, m_width, m_height );
				break;
		}
		pPixels = pNew;
	}

	if(mipmapLevel == -1)
	{
		Transfrom( pDest, format, x, y, width, height, pitch, pPixels, m_width, m_height );
	}
	else
	{
		// Find texel offset to the required mip level
		int pow_w = GetPower2( m_width );
		int pow_h = GetPower2( m_height );
		BYTE *pSrc = m_pData;
		int size = 0;
		if ( mipmapLevel > 0 )
		{
			int w = max( MIN_UNIT, pow_w );
			int h = max( MIN_UNIT, pow_h );
			for ( int levels = min( mipmapLevel, m_mipmapLevels ); levels > 0; levels-- )
			{
				size += w * h;
				if ( w > MIN_UNIT ) w >>= 1;
				if ( h > MIN_UNIT ) h >>= 1;
				if ( pow_w > MIN_UNIT ) pow_w >>= 1;
				if ( pow_h > MIN_UNIT ) pow_h >>= 1;
			}
		}

		// Multiply by texel pitch
		switch ( m_imageFormat )
		{
			case IMAGE_R5G6B5:
			case IMAGE_A1R5G5B5:
				size *= 2;
				break;

			case IMAGE_R8G8B8:
				size *= 3;
				break;

			case IMAGE_A8R8G8B8:
				size *= 4;
				break;
		}

		// Offset the source pointer
		pPixels = (RGBQUAD *) ((BYTE *) pPixels + size);

		// Copy
		Transfrom( pDest, format, x, y, width, height, pitch, pPixels, pow_w, pow_h );
	}

	if ( pNew ) delete [] pNew;

	return true;
}

inline DWORD TransferFormatType( ImageFormat format )
{
	if ( format == IMAGE_DXT1 ) return kDXT1;		
	else if ( format == IMAGE_DXT1a ) return kDXT1a;
	else if ( format == IMAGE_DXT3 ) return kDXT3;
	else if ( format == IMAGE_DXT5 ) return kDXT5;
	return format;
}

bool cImgFile::BltToDXT( void *pDest, ImageFormat format, int x, int y, int width, int height, int mipmapLevels, DWORD color, DWORD rop )
{
	if ( pDest == NULL || m_pData == NULL || !IsDXTFormat(format) || !IsDXTFormat(m_imageFormat) ) return false;
	
	int pow_w = GetPower2( m_width );
	int pow_h = GetPower2( m_height );
	BYTE *pSrc = m_pData;
	int size = 0;
	if ( mipmapLevels > 0 ) {
		int w = max( MIN_UNIT, pow_w );
		int h = max( MIN_UNIT, pow_h );
		for ( int levels = min( mipmapLevels, m_mipmapLevels ); levels > 0; levels-- ) {
			size += w * h;
			if ( w > MIN_UNIT ) w >>= 1;
			if ( h > MIN_UNIT ) h >>= 1;
			if ( pow_w > MIN_UNIT ) pow_w >>= 1;
			if ( pow_h > MIN_UNIT ) pow_h >>= 1;
		}
	}
	if ( m_imageFormat == IMAGE_DXT1 || m_imageFormat == IMAGE_DXT1a ) size /= 2;
	pSrc += size;

	return BltDXT( pDest, TransferFormatType(format), x, y, width, height, pSrc, TransferFormatType(m_imageFormat), pow_w, pow_h, color, rop );
}

void cImgFile::SaveHeader( cListData &header )
{
	cFileObj::SaveHeader( header );	
	header.Push( &m_imageFormat, sizeof(ImageFormat) );
	header.Push( &m_width, sizeof(int) );
	header.Push( &m_height, sizeof(int) );
	header.Push( &m_mipmapLevels, sizeof(int) );
}

bool cImgFile::LoadHeader( HANDLE hFile )
{
	DWORD readBytesOut;
	if ( cFileObj::LoadHeader( hFile ) == false )
		return false;

	if ( m_fileType == FILE_TYPE_IMAGE ) {
		ReadFile( hFile, &m_imageFormat, sizeof(ImageFormat), &readBytesOut, NULL );
		switch (m_imageFormat)
		{
		case IMAGE_UNKNOWN:
		case IMAGE_R5G6B5:
		case IMAGE_A1R5G5B5:
		case IMAGE_R8G8B8:
		case IMAGE_A8R8G8B8:
		case IMAGE_DXT1:
		case IMAGE_DXT1a:
		case IMAGE_DXT3:
		case IMAGE_DXT5:
			break;

		default:
			return false;
		}

		ReadFile( hFile, &m_width, sizeof(int), &readBytesOut, NULL );
		if ( m_width < 0 || m_width > 0x2000 )
			return false;

		ReadFile( hFile, &m_height, sizeof(int), &readBytesOut, NULL );
		if ( m_height < 0 || m_height > 0x2000 )
			return false;

		ReadFile( hFile, &m_mipmapLevels, sizeof(int), &readBytesOut, NULL );
		if ( m_mipmapLevels < 0 || m_mipmapLevels > 0x10 )
			return false;
	}
	return true;
}

void cImgFile::WriteData( HANDLE hFile )
{
	DWORD writeBytesOut;	

	// if ( m_mipmapLevels > 1 )
	{
		DWORD dwMagic = 0x20534444;			// "DDS "

		DDSURFACEDESC2 ddsd;
		memset(&ddsd, 0, sizeof(ddsd));

		ddsd.dwSize			= sizeof(ddsd);
		ddsd.dwWidth		= m_width;
		ddsd.dwHeight		= m_height;
		ddsd.dwMipMapCount	= m_mipmapLevels;		

		if ( m_mipmapLevels > 1 )
			ddsd.dwFlags	= DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_PIXELFORMAT | DDSD_MIPMAPCOUNT | DDSD_LINEARSIZE;
		else
			ddsd.dwFlags	= DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_PIXELFORMAT | DDSD_LINEARSIZE;

		//ddsd.dwRefreshRate	= 2;
		//ddsd.dwSrcVBHandle	= 2;

		// DDPIXELFORMAT
		ddsd.ddpfPixelFormat.dwSize		= sizeof(DDPIXELFORMAT);		

		// DDS
		switch (m_imageFormat)
		{
		case IMAGE_R5G6B5:
			ddsd.lPitch		= m_width * m_height * 2;
			ddsd.ddpfPixelFormat.dwFlags		= DDPF_RGB;
			ddsd.ddpfPixelFormat.dwRGBBitCount	= 16;
			break;
		case IMAGE_A1R5G5B5:
			ddsd.lPitch		= m_width * m_height * 2;
			ddsd.ddpfPixelFormat.dwFlags		= DDPF_RGB;
			ddsd.ddpfPixelFormat.dwRGBBitCount	= 16;
			break;
		case IMAGE_R8G8B8:
			ddsd.lPitch		= m_width * m_height * 3;
			ddsd.ddpfPixelFormat.dwFlags		= DDPF_RGB;
			ddsd.ddpfPixelFormat.dwRGBBitCount	= 24;
			ddsd.ddpfPixelFormat.dwRBitMask		= 0x00ff0000;
			ddsd.ddpfPixelFormat.dwGBitMask		= 0x0000ff00;
			ddsd.ddpfPixelFormat.dwBBitMask		= 0x000000ff;
			break;
		case IMAGE_A8R8G8B8:
			ddsd.lPitch		= m_width * m_height * 4;
			ddsd.ddpfPixelFormat.dwFlags		= DDPF_RGB | DDPF_ALPHAPIXELS;
			ddsd.ddpfPixelFormat.dwRGBBitCount	= 32;
			ddsd.ddpfPixelFormat.dwRBitMask		= 0x00ff0000;
			ddsd.ddpfPixelFormat.dwGBitMask		= 0x0000ff00;
			ddsd.ddpfPixelFormat.dwBBitMask		= 0x000000ff;
			ddsd.ddpfPixelFormat.dwRGBAlphaBitMask = 0xff000000;
			break;
		case IMAGE_DXT1:
			ddsd.lPitch		= m_width * m_height / 2;
			ddsd.ddpfPixelFormat.dwFlags	= DDPF_FOURCC;
			ddsd.ddpfPixelFormat.dwFourCC	= 0x31545844;				// DXT1
			break;
		case IMAGE_DXT1a:
			ddsd.lPitch		= m_width * m_height / 2;
			ddsd.ddpfPixelFormat.dwFlags	= DDPF_FOURCC;
			ddsd.ddpfPixelFormat.dwFourCC	= 0x31545844;				// DXT1
			break;
		case IMAGE_DXT3:
			ddsd.lPitch		= m_width * m_height;
			ddsd.ddpfPixelFormat.dwFlags	= DDPF_FOURCC;
			ddsd.ddpfPixelFormat.dwFourCC	= 0x33545844;				// DXT3
			break;
		case IMAGE_DXT5:
			ddsd.lPitch		= m_width * m_height;
			ddsd.ddpfPixelFormat.dwFlags	= DDPF_FOURCC;
			ddsd.ddpfPixelFormat.dwFourCC	= 0x35545844;				// DXT5
			break;
		}
		ddsd.dwLinearSize	= ddsd.lPitch;		

		// 判斷DDS檔案
		WriteFile( hFile, &dwMagic, sizeof(DWORD), &writeBytesOut, NULL );

		// DDS 檔頭
		WriteFile( hFile, &ddsd, sizeof(DDSURFACEDESC2), &writeBytesOut, NULL );
	}
	/*
	else
	{
		//TGA
		//cImage image;

		//image.Create(m_width, m_height, 32, IMAGELIB_FORMAT_TGA);

		//BltToRGB(image.GetPixels(), IMAGE_A8R8G8B8, 0, 0, m_width, m_height, m_width * 4);
	}
	*/

	cFileObj::WriteData(hFile);
}

bool cImgFile::Transfrom( void *pDest, ImageFormat format, int x, int y, int width, int height, int pitch, void *pSrc, int srcWidth, int srcHeight)
{
	RGBQUAD *pPixels = (RGBQUAD *)pSrc;
	switch ( format )
	{
		case IMAGE_R5G6B5:
		case IMAGE_A1R5G5B5:
		{
			WORD *pLine = (WORD*)pDest + y * (pitch / 2);
			for ( int yy = 0; yy < srcHeight; yy++ ) {
				if ( yy + y >= height ) break;
				for ( int xx = 0; xx < srcWidth; xx++ ) {
					if ( xx + x >= width ) break;
					if ( format == IMAGE_R5G6B5 ) {
						pLine[xx + x] = RGBToR5G6B5( pPixels[xx].rgbRed, pPixels[xx].rgbGreen, pPixels[xx].rgbBlue );
					} else if ( format == IMAGE_A1R5G5B5 ) {
						pLine[xx + x] = RGBToR5G6B5( pPixels[xx].rgbRed, pPixels[xx].rgbGreen, pPixels[xx].rgbBlue );
						if ( pPixels[xx].rgbReserved & 0x80 ) pLine[xx + x] |= 0x8000;
					}
				}
				pPixels += srcWidth;
				pLine += (pitch / 2);
			}
			break;
		}
		case IMAGE_R8G8B8:
		{
			// We do not support 24 bit
			assert(FALSE);

			BGRCOLOR *pLine = (BGRCOLOR*)pDest + y * width;
			for ( int yy = 0; yy < srcHeight; yy++ ) {
				if ( yy + y >= height ) break;
				for ( int xx = 0; xx < srcWidth; xx++ ) {
					if ( xx + x >= width ) break;
					pLine[xx].r = pPixels[xx].rgbBlue;
					pLine[xx].g = pPixels[xx].rgbGreen;
					pLine[xx].b = pPixels[xx].rgbRed;
				}
				pPixels += srcWidth;
				pLine += width;				
			}
			break;
		}
		case IMAGE_A8R8G8B8:
		{
			RGBQUAD* pLine = (RGBQUAD*)pDest + y * (pitch / 4);
			for ( int yy = 0; yy < srcHeight; yy++ ) {
				if ( yy + y >= height ) break;
				memcpy( pLine + x, pPixels, sizeof(RGBQUAD) * min(width - x, srcWidth) );
				pLine += pitch / 4;
				pPixels += srcWidth;
			}
			break;
		}
		default:
			return false;
	}
	return true;
}
