/*
 * $Id: imagecommon.cpp 4560 2017-06-09 12:46:23Z antonin.misek-skoda-auto.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 "kernel.h"
#include "imagecommon.h"
#include "glgetproc.h"
#include "common.h" //

using namespace VRUT;


ImageCommon::ImageCommon() : Image(WXIMAGE)
{
}

ImageCommon::ImageCommon(const ImageCommon &_image) : Image(_image.GetType(), _image.IsCube())
{
	if (_image.GetWxImage())
		image.reset(new wxImage(*_image.GetWxImage()));
	for (size_t i=0; i<5; i++)
	{
		if (_image.GetWxImage(i))
			images[i].reset(new wxImage(*_image.GetWxImage(i)));
	}
	filename=*_image.GetFilename();
	for (size_t i=0; i<6; i++)
		filenames[i].Assign(_image.GetCubeSideName(i));
	transparency=_image.IsTransparent();
	alphaUsageType=_image.GetAlphaUsageType();
}

ImageCommon::ImageCommon(wxImage *_image, bool cube) : Image(WXIMAGE, cube), image(_image)
{
}

ImageCommon::~ImageCommon()
{
}

bool ImageCommon::IsOk() const
{
	if (cube)
	{
		for (size_t i=0; i<5; i++)
			if (!images[i] || !images[i]->IsOk())
				return false;
	}
	return image && image->IsOk();
}


bool ImageCommon::Load(const wxString & fname, int cubeSide, int flip)
{
	if (cubeSide<=0)
	{
		image.reset(loadImage(fname));
		if (image)
		{
			if (flip & FLIP_HORIZONTALLY)	*image = image->Mirror(true);
			if (flip & FLIP_VERTICALLY)		*image = image->Mirror(false);
		}
		return (image.get() != nullptr);
	}
	else if (cubeSide<6)
	{
		images[cubeSide-1].reset(loadImage(fname, cubeSide));
		if (images[cubeSide-1])
		{
			cube = true;
		
			if (flip & FLIP_HORIZONTALLY)	*images[cubeSide-1] = images[cubeSide-1]->Mirror(true);
			if (flip & FLIP_VERTICALLY)		*images[cubeSide-1] = images[cubeSide-1]->Mirror(false);
		}
		return (images[cubeSide-1].get() != nullptr);
	}
	return false;
}

bool ImageCommon::SetImage(const wxImage *_image, int cubeSide)
{
	if (cubeSide<=0)
	{
		image.reset(new wxImage(*_image));
		if (image && image->IsOk())
			convertMaskToAlpha(image.get());
		return (image.get() != nullptr);
	}
	else if (cubeSide<6)
	{
		images[cubeSide-1].reset(new wxImage(*_image));
		if (images[cubeSide-1])
		{
			cube = true;
			convertMaskToAlpha(images[cubeSide-1].get());
		}
		return (images[cubeSide-1].get() != nullptr);
	}
	return false;
}

#ifndef VRUT_HPC
void ImageCommon::BuildOneCubeSide(wxImage* image, const TEXTURE_SAMPLER_TYPE buildAsType, GLenum cubeSide, bool use_glu_for_mipmaps)
{
	if (!image || !image->IsOk())
		return;
	if (image->HasAlpha())
	{
		///Join RGB and Alpha data
		unsigned pixels = image->GetWidth() * image->GetHeight();
		std::unique_ptr<GLubyte []> rgbaData(new GLubyte[pixels * 4]);
		unsigned char * tmpRGBA = rgbaData.get();
		unsigned char * rgbData = image->GetData();
		unsigned char * alphaData = image->GetAlpha();
		for (unsigned pix = 0; pix < pixels; pix++)
		{
			*tmpRGBA++ = *rgbData++;
			*tmpRGBA++ = *rgbData++;
			*tmpRGBA++ = *rgbData++;
			*tmpRGBA++ = *alphaData++;
		}

		if (use_glu_for_mipmaps)
			gluBuild2DMipmaps(cubeSide, 4, image->GetWidth(), image->GetHeight(), GL_RGBA, GL_UNSIGNED_BYTE, rgbaData.get());
		else
			glTexImage2D(cubeSide, 0, GL_RGBA, image->GetWidth(), image->GetHeight(), 0, GL_RGBA, GL_UNSIGNED_BYTE, rgbaData.get());
	}
	else
	{
		if (use_glu_for_mipmaps)
			gluBuild2DMipmaps(cubeSide, 3, image->GetWidth(), image->GetHeight(), GL_RGB, GL_UNSIGNED_BYTE, image->GetData());
		else
			glTexImage2D(cubeSide, 0, GL_RGB, image->GetWidth(), image->GetHeight(), 0, GL_RGB, GL_UNSIGNED_BYTE, image->GetData());
	}
}

void ImageCommon::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.
	//		NOTE: KERNEL->InitGlew calls glewInit from VRUT's core. It means that calling it from render modules initializes glew
	//			in VRUT's core, not in the module itself, the module needs to call glewInit by itself.
	//		NOTE: This all may depend on whether GLEW is compiled using /MT or /MD
	if (!glGenerateMipmapEXT && create_mipmaps)
		KERNEL->InitGlew();
	if (IsOk())
	{
		int texTarget = (buildAsType==TEXCUBE)? GL_TEXTURE_CUBE_MAP : GL_TEXTURE_2D;
		glBindTexture( texTarget, tex_object );
		glPixelStorei( GL_UNPACK_ALIGNMENT, 1 );

		bool use_glu_for_mipmaps = create_mipmaps && !(GLEW_EXT_framebuffer_object);

		if (buildAsType==TEXCUBE)
		{
			bool resized = Image::CheckAndFixImageResolution(image.get(), buildAsType);
			if (resized)
			{
				for (int i = 0; i < 5; i++)
				{
					*images[i] = images[i]->Scale(image->GetWidth(), image->GetHeight());
				}
			}

			BuildOneCubeSide(image.get(), buildAsType, GL_TEXTURE_CUBE_MAP_POSITIVE_X, use_glu_for_mipmaps);
			BuildOneCubeSide(images[0].get(), buildAsType, GL_TEXTURE_CUBE_MAP_NEGATIVE_X, use_glu_for_mipmaps);
			BuildOneCubeSide(images[1].get(), buildAsType, GL_TEXTURE_CUBE_MAP_POSITIVE_Y, use_glu_for_mipmaps);
			BuildOneCubeSide(images[2].get(), buildAsType, GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, use_glu_for_mipmaps);
			BuildOneCubeSide(images[3].get(), buildAsType, GL_TEXTURE_CUBE_MAP_POSITIVE_Z, use_glu_for_mipmaps);
			BuildOneCubeSide(images[4].get(), buildAsType, GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, use_glu_for_mipmaps);
		}
		else
		{
			Image::CheckAndFixImageResolution(image.get(), buildAsType);
			BuildOneCubeSide(image.get(), buildAsType, GL_TEXTURE_2D, use_glu_for_mipmaps);
		}

		if (create_mipmaps && !use_glu_for_mipmaps)
			glGenerateMipmapEXT(texTarget);
	}
	GLCHECKERROR(wxT("<ImageCommon::BuildOglTexture>"));
}
#endif

bool ImageCommon::SetTransparencyAndSetAlphaUsage(bool testPixels)
{
	if (image && IsOk() && (image->HasAlpha()/* || image->HasMask()*/)) //TODO: Mask is converted to alpha and disabled in the loadImage method, so this condition is probably obsolete.
	{
		if (testPixels && image->HasAlpha())
		{
			int width = image->GetWidth();
			int height = image->GetHeight();
			unsigned char *alpha = image->GetAlpha();
			long pixelsCount=(long)width*(long)height;
			unsigned int translucentAlphaPixelCount(0);
			for (int i=0; i < pixelsCount; i++)
			{
				if (*alpha != 255)
					transparency = true;
				
				if (*alpha < 245 && *alpha > 10)
					translucentAlphaPixelCount++;

				alpha++;
			}
			if (translucentAlphaPixelCount != pixelsCount)
				alphaUsageType = Image::MASK;
			return transparency;
		}
		else
			return (transparency = true);
	}
	return (transparency = false);
}

int ImageCommon::GetWidth() const
{
	//return ((image && IsOk())?image->GetWidth():0);
	return image->GetWidth();
}

int ImageCommon::GetHeight() const
{
	//return ((image && IsOk())?image->GetHeight():0);
	return image->GetHeight();
}

bool ImageCommon::Save(wxString filename) const
{
	wxImage im(image->Mirror(false));
	return im.SaveFile(filename);
}

void ImageCommon::convertMaskToAlpha(wxImage* image)
{
	if (image->HasMask())//convert mask to alpha to correctly render image
	{
		unsigned char * rgbData = image->GetData();
		unsigned char * alphaData = NULL;
		unsigned pixels = image->GetWidth() * image->GetHeight();
		unsigned char * alphaDataPos;
		if (image->HasAlpha())
			alphaData = image->GetAlpha();
		else
		{
			alphaData = (unsigned char *)malloc(pixels*sizeof(unsigned char));
			alphaDataPos = alphaData;
			for (unsigned pix = 0; pix < pixels; pix++)
				*alphaDataPos++=255;
		}
		bool hasAlphaPixel=false;
		unsigned char r, g, b;
		image->GetOrFindMaskColour(&r, &g, &b);
		alphaDataPos = alphaData;
		for (unsigned pix = 0; pix < pixels; pix++)
		{
			if ((rgbData[0]==r)&&(rgbData[1]==g)&&(rgbData[2]==b))
			{
				*alphaDataPos=0;
				hasAlphaPixel=true;
			}
			rgbData+=3;
			alphaDataPos++;
		}
		if (!image->HasAlpha())
		{
			if (hasAlphaPixel)
				image->SetAlpha(alphaData);
			else
				free(alphaData);
		}
		image->SetMask(false);
	}
	SetTransparencyAndSetAlphaUsage(true);
}

wxImage* ImageCommon::loadImage(const wxString & fname, int cubeSide)
{
	if (Image::Load(fname, cubeSide))
	{
		wxFileName fn(fname);
		wxImage im;
		if (!fn.GetExt().CmpNoCase(wxT("tga"))) //This condition is here because of the bug in wxWidgets. See its ticket here http://trac.wxwidgets.org/ticket/12702
			im.LoadFile(fname, wxBITMAP_TYPE_TGA, 0);
		else
			im.LoadFile(fname, wxBITMAP_TYPE_ANY, 0);
		if (im.IsOk())
		{
			wxImage * image = new wxImage(im.Mirror(false));
			convertMaskToAlpha(image);
			return image;
		}
	}
	return NULL;
}
