/*
 * $Id: imagedds.cpp 3683 2015-06-18 14:46:16Z cejka-mail.muni.cz $
 *
 * Description  : One line description of file.
 * Author       : Vaclav Kyba <mail/Jabber: vaseo1@gmail.com>  <ICQ: 98576293>
 *
 * Purpose      :
 *     Long description of what the file is for.
 */


//#include <wx/wfstream.h>
#include "kernel.h"
#include "flexilog.h"
#include "imagedds.h"
#include "glgetproc.h"


using namespace VRUT;


ImageDDS::ImageDDS() : Image(DDSIMAGE)
{
}


ImageDDS::ImageDDS(const ImageDDS &_image) : Image(_image.GetType(), _image.IsCube())
{
	memcpy(&image.header, &_image.GetImage().header, sizeof(DDS_header));
	image.dataSize = GetImage().dataSize;
	image.data = new unsigned char[image.dataSize];
	memcpy(image.data, _image.GetImage().data, image.dataSize);
	ParseHeader(wxT(""));
	transparency=_image.IsTransparent();
	alphaUsageType=_image.GetAlphaUsageType();
}

ImageDDS::ImageDDS(DDS_header *header, unsigned long dataSize, void *data, bool cube) : Image(DDSIMAGE, cube)
{
	memcpy(&image.header, header, sizeof(DDS_header));
	image.dataSize = dataSize;
	image.data = new unsigned char[image.dataSize];
	memcpy(image.data, data, dataSize);
	ParseHeader(wxT(""));
}


ImageDDS::~ImageDDS()
{
}


bool ImageDDS::IsOk() const
{
	return (image.data != (unsigned char *)NULL);
}


bool ImageDDS::ParseHeader(wxString fname)
{
	if ( image.header.dwMagic != DDS_MAGIC || image.header.dwSize != 124 ||
		!(image.header.dwFlags & DDSD_PIXELFORMAT) || !(image.header.dwFlags & DDSD_CAPS) )
	{
		LOGERROR(wxT("<ImageDDS>File '")+fname+wxT("' doesn't appear to be a valid .dds file"));
		return false;
	}
#ifndef VRUT_HPC
	if ( PF_IS_DXT1( image.header.sPixelFormat ) )
	{
		image.compressed = true;
		image.swap = false;
		image.palette = false;
		image.divSize = 4;
		image.blockBytes = 8;
		image.internalFormat = GL_COMPRESSED_RGBA_S3TC_DXT1_EXT;
	}
	else if ( PF_IS_DXT3( image.header.sPixelFormat ) )
	{
		image.compressed = true;
		image.swap = false;
		image.palette = false;
		image.divSize = 4;
		image.blockBytes = 16;
		image.internalFormat = GL_COMPRESSED_RGBA_S3TC_DXT3_EXT;
	}
	else if ( PF_IS_DXT5( image.header.sPixelFormat ) )
	{
		image.compressed = true;
		image.swap = false;
		image.palette = false;
		image.divSize = 4;
		image.blockBytes = 16;
		image.internalFormat = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT;
	}
	else if ( PF_IS_BGRA8( image.header.sPixelFormat ) )
	{
		image.compressed = false;
		image.swap = false;
		image.palette = false;
		image.divSize = 1;
		image.blockBytes = 4;
		image.internalFormat = GL_RGBA8;
		image.externalFormat = GL_BGRA;
		image.type = GL_UNSIGNED_BYTE;
	}
	else if ( PF_IS_BGR8( image.header.sPixelFormat ) )
	{
		image.compressed = false;
		image.swap = false;
		image.palette = false;
		image.divSize = 1;
		image.blockBytes = 3;
		image.internalFormat = GL_RGB8;
		image.externalFormat = GL_BGR;
		image.type = GL_UNSIGNED_BYTE;
	}
	else if ( PF_IS_BGR5A1( image.header.sPixelFormat ) )
	{
		image.compressed = false;
		image.swap = true;
		image.palette = false;
		image.divSize = 1;
		image.blockBytes = 2;
		image.internalFormat = GL_RGB5_A1;
		image.externalFormat = GL_BGRA;
		image.type = GL_UNSIGNED_SHORT_1_5_5_5_REV;
	}
	else if ( PF_IS_BGR565( image.header.sPixelFormat ) )
	{
		image.compressed = false;
		image.swap = true;
		image.palette = false;
		image.divSize = 1;
		image.blockBytes = 2;
		image.internalFormat = GL_RGB5;
		image.externalFormat = GL_RGB;
		image.type = GL_UNSIGNED_SHORT_5_6_5;
	}
	else if ( PF_IS_INDEX8( image.header.sPixelFormat ) )
	{
		image.compressed = false;
		image.swap = false;
		image.palette = true;
		image.divSize = 1;
		image.blockBytes = 1;
		image.internalFormat = GL_RGB8;
		image.externalFormat = GL_BGRA;
		image.type = GL_UNSIGNED_BYTE;
	}
	else
#endif
	{
		LOGERROR(wxT("<ImageDDS>Unknown pixel format in DDS file '")+fname+wxT("'"));
		return false;
	}
	return true;
}


bool ImageDDS::Load(const wxString & fname, int cubeSide, int flip)
{
	if (cubeSide>0)
		LOGERROR(wxT("ImageDDS::Load load into cubeSide not implemented"));
	if (flip!=0)
		LOGERROR(wxT("ImageDDS::Load flipping image not implemented"));
	if (Image::Load(fname))
	{
		SAFE_DELETE_ARR(image.data);
		/* Open the file */
		wxFileInputStream fin(fname);
		/* Get the surface descriptor */
		fin.Read(&image.header, sizeof(image.header));
		if (!ParseHeader(fname))
			return false;

		if (image.header.sCaps.dwCaps2 & DDSCAPS2_CUBEMAP)
			cube = true;

		/* Calculate pixel data size */
		unsigned curr = fin.TellI();
		unsigned end = fin.SeekI(0, wxFromEnd);
		fin.SeekI(curr);
		image.dataSize = end - curr;
		/* Read pixel data with mipmaps */
		image.data = new unsigned char[image.dataSize];
		fin.Read(image.data, sizeof(unsigned char) * image.dataSize);

		return true;
	}
	return false;
}

#ifndef VRUT_HPC
bool ImageDDS::BuildOglTextureFace(int imageTarget, unsigned size, unsigned &offset) const
{
	unsigned x = image.header.dwWidth;
	unsigned y = image.header.dwHeight;
	unsigned mipMapCount = (image.header.dwFlags & DDSD_MIPMAPCOUNT) ? image.header.dwMipMapCount : 1;

	if (image.compressed)
	{
		for ( unsigned int ix = 0; ix < mipMapCount; ++ix )
		{
			glCompressedTexImage2D( imageTarget, ix, image.internalFormat, x, y, 0, size, image.data + offset );
			x = (x+1)>>1;
			y = (y+1)>>1;
			offset += size;
			size = __max( image.divSize, x )/image.divSize * __max( image.divSize, y )/image.divSize * image.blockBytes;
		}
	}
	else if (image.palette) //TODO: not tested
	{
		unsigned char * data = image.data + 256 * 4;
		unsigned int * palette = (unsigned int *)image.data;
		unsigned int * unpacked = new unsigned[size * sizeof(unsigned)];
		for ( unsigned ix = 0; ix < mipMapCount; ++ix )
		{
			for ( unsigned zz = 0; zz < size; ++zz )
			{
				unpacked[ zz ] = palette[ data[ zz + offset] ];
			}
			glPixelStorei( GL_UNPACK_ROW_LENGTH, y );
			glTexImage2D( imageTarget, ix, image.internalFormat, x, y, 0, image.externalFormat, image.type, unpacked );
			x = (x+1)>>1;
			y = (y+1)>>1;
			offset += size;
			size = x * y * image.blockBytes;
			glPixelStorei( GL_UNPACK_ROW_LENGTH, 0 );
		}
		delete [] unpacked;
	}
	else
	{
		//fixme: how are MIP maps stored for 24-bit if pitch != ySize*3 ?
		for ( unsigned int ix = 0; ix < mipMapCount; ++ix )
		{
			glPixelStorei( GL_UNPACK_ROW_LENGTH, y );
			glTexImage2D( imageTarget, ix, image.internalFormat, x, y, 0, image.externalFormat, image.type, image.data + offset );
			x = (x+1)>>1;
			y = (y+1)>>1;
			offset += size;
			size = x * y * image.blockBytes;
			glPixelStorei( GL_UNPACK_ROW_LENGTH, 0 );
		}
		glPixelStorei( GL_UNPACK_SWAP_BYTES, GL_FALSE );
	}
	GLCHECKERROR(wxT("<ImageDDS::BuildOglTextureFace>"));
	return true;
}

void ImageDDS::UpdateOglTexture(GLuint tex_object, const TEXTURE_SAMPLER_TYPE buildAsType, bool create_mipmaps) const
{
	// TODO Does anyone have a better idea where to place this (OGL context has to be created and active by the time we call this) ?
	// TODO Since this function is in VRUT's core and GLEW is a static library, initializing GLEW in module does not mean GLEW is
	//		initialized here. It means GLEW must be initialized from VRUT's core.
	if (!glCompressedTexImage2D)
		KERNEL->InitGlew();
	if (IsOk())
	{
		unsigned x = image.header.dwWidth;
		unsigned y = image.header.dwHeight;
		unsigned mipMapCount = (image.header.dwFlags & DDSD_MIPMAPCOUNT) ? image.header.dwMipMapCount : 1;

		unsigned size = 0;
		//tests of correctness
		if (image.compressed)
		{
			size = __max( image.divSize, x )/image.divSize * __max( image.divSize, y )/image.divSize * image.blockBytes;
			//TODO: CubeMapGen cube maps do not pass this test, yet they look fine. Revision of this test is needed.
			/*if ( (image.header.dwPitchOrLinearSize != 0 && size != image.header.dwPitchOrLinearSize) || !(image.header.dwFlags & DDSD_LINEARSIZE) )
			{
				LOGERROR(wxT("<ImageDDS>Texture size mismatch"));
				return texID;
			}*/
		}
		else if (image.palette) //TODO: not tested
		{
			//  currently, we unpack palette into BGRA
			//  I'm not sure we always get pitch...
			size = image.header.dwPitchOrLinearSize * image.header.dwHeight;
			//  And I'm even less sure we don't get padding on the smaller MIP levels...
			if ( (image.header.dwFlags & DDSD_PITCH) == 0 || image.header.sPixelFormat.dwRGBBitCount != 8 || (size != x * y * image.blockBytes) )
			{
				LOGERROR(wxT("<ImageDDS>Size mismatch"));
				return;
			}
			else
			{
				if (image.swap )  //TODO: not tested
					glPixelStorei( GL_UNPACK_SWAP_BYTES, GL_TRUE );
				size = x * y * image.blockBytes;
			}
		}
		else //For uncompressed BGR[A]
		{
			size = x * y * image.blockBytes;
		}

		int bindTarget = (buildAsType==TEXCUBE)? GL_TEXTURE_CUBE_MAP : GL_TEXTURE_2D;

		glBindTexture( bindTarget, tex_object);

		unsigned offset = 0;
		if ((buildAsType==TEXCUBE) && (image.header.sCaps.dwCaps1 & DDSCAPS_COMPLEX) && (image.header.sCaps.dwCaps2 & DDSCAPS2_CUBEMAP))
		{
			if (image.header.sCaps.dwCaps2 & DDSCAPS2_CUBEMAP_POSITIVEX)
				BuildOglTextureFace(GL_TEXTURE_CUBE_MAP_POSITIVE_X, size, offset);
			if (image.header.sCaps.dwCaps2 & DDSCAPS2_CUBEMAP_NEGATIVEX)
				BuildOglTextureFace(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, size, offset);
			if (image.header.sCaps.dwCaps2 & DDSCAPS2_CUBEMAP_POSITIVEY)
				BuildOglTextureFace(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, size, offset);
			if (image.header.sCaps.dwCaps2 & DDSCAPS2_CUBEMAP_NEGATIVEY)
				BuildOglTextureFace(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, size, offset);
			if (image.header.sCaps.dwCaps2 & DDSCAPS2_CUBEMAP_POSITIVEZ)
				BuildOglTextureFace(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, size, offset);
			if (image.header.sCaps.dwCaps2 & DDSCAPS2_CUBEMAP_NEGATIVEZ)
				BuildOglTextureFace(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, size, offset);
		}
		else
		{
			if (buildAsType==TEXCUBE)
				LOGWARNING(wxT("<ImageDDS::BuildOglTexture>Cannot build texture as CUBEMAP"));
			BuildOglTextureFace(GL_TEXTURE_2D, size, offset);
		}

		glTexParameteri( bindTarget, GL_TEXTURE_MAX_LEVEL, mipMapCount-1 );
		GLCHECKERROR(wxT("<ImageDDS::BuildOglTexture>"));
	}
}
#endif

bool ImageDDS::SetTransparencyAndSetAlphaUsage(bool )
{
	//TODO: test pixels to be sure that alpha is really used
	//This is going to be a bit complicated. We ll either have to
	//decompress the texture on the cpu (which would require a decompression routine for every compression there is [dxt1-5, s3tc, ...])
	//or upload it to gpu here and read it decompressed back into some buffer
	return IsOk() && ( image.header.dwFlags & DDPF_ALPHAPIXELS );
}

int ImageDDS::GetWidth() const
{
	return (IsOk()?image.header.dwWidth:0);
}

int ImageDDS::GetHeight() const
{
	return (IsOk()?image.header.dwHeight:0);
}

bool ImageDDS::Save(wxString filename) const
{
	LOGERROR(wxT("ImageDDS::Save not implemented."));
	return false;
}
