/*
 * $Id: material.h 4937 2018-04-27 12:55:31Z 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.
 */


#ifndef __MATERIAL__H__
#define __MATERIAL__H__

#include <stdlib.h>
#ifndef VRUT_HPC
#include <GL/glew.h>
#else
#include "OGLconstants.h"
#endif
//#include "image.h"
//#include "common.h"
#include "moddefs.h"
#include "shaderprogram.h"
#include "image.h"
#include "textparser.h"

#define FLAG2B(tex_flag, mask, super_mask)((tex_flag & super_mask) == (mask & super_mask)) ? true : false

namespace VRUT
{
	///Material definition
	struct Material : public Countable
	{
		///MID
		MATERIAL_ID mid;
		///Name
		wxString name;
		///Ambient color RGB
		Vector4 ambient;
		///Diffuse color RGB
		Vector4 diffuse;
		///Specular color RGB
		Vector4 specular;
		///Emission color RGB
		Vector4 emission;
		///Shininess intensity
		float shininess;
		///Depth function
		GLuint depthFunc;
		///1: perfect mirror, 0: no mirror effect
		float reflectivity;
		///Texture mix
		float textureMix;
		///Material flags definitions
		enum FLAG
		{
			BLENDED                = 0x00000001,  ///<Blending should be enabled when rendering material
			DEPTH_TEST_DISABLE     = 0x00000002,
			DEPTH_MASK_DISABLE     = 0x00000004,
			DOUBLE_SIDED           = 0x00000008,
			INVERT_ALPHA           = 0x00000010,
			UNLIT                  = 0x00000020,
			FLAT_REFLECTION		   = 0x00000040
		};
		///Material flags
		unsigned flags;

		///Texture flags definitions
		enum TEX_FLAG
		{
			TEX_UWRAP_REPEAT		= 0x00000001,//this value should be default and not used directly, please use !TEX_UWRAP_CLAMP instead,  first 2 bits is for Uwrap 01 repeate, 10 clamp, 11 clamp to border, 11 free // TODO Why?
			TEX_UWRAP_CLAMP			= 0x00000002,
			TEX_UWRAP_BORDER		= 0x00000003,
			TEX_UWRAP_MASK			= 0x00000003,
			TEX_VWRAP_REPEAT		= 0x00000004,//this value should be default and not used directly, please use !TEX_VWRAP_CLAMP instead//second two bits is for Vwrap 0100 repeate,  1000 clamp, 1100 clamp to edge // TODO Why?
			TEX_VWRAP_CLAMP			= 0x00000008,
			TEX_VWRAP_BORDER		= 0x0000000C,
			TEX_VWRAP_MASK			= 0x0000000C,
			TEX_RWRAP_REPEAT		= 0x00000010,//this value should be default and not used directly, please use !TEX_RWRAP_CLAMP instead//third two bits is for Vwrap 010000 repeate,  100000 clamp, 110000 clamp to edge // TODO Why?
			TEX_RWRAP_CLAMP			= 0x00000020,//dec 32	bin 10 00 00
			TEX_RWRAP_BORDER		= 0x00000030,//dec 48	bin 11 00 00
			TEX_RWRAP_MASK			= 0x00000030,//dec 48	bin 11 00 00
			TEX_MIN_FILTER_POINT	= 0x00000040,//dec 64	bin 01 00 00 00
			TEX_MIN_FILTER_LINEAR	= 0x00000080,//dec 128	bin 10 00 00 00
			TEX_MIN_FILTER_MIPMAP	= 0x000000C0,//dec 192	bin 11 00 00 00
			TEX_MIN_FILTER_MASK		= 0x000000C0,//dec 192	bin 11 00 00 00
			TEX_MAG_FILTER_POINT	= 0x00000100,//dec 256	bin 01 00 00 00 00
			TEX_MAG_FILTER_LINEAR	= 0x00000200,//dec 512	bim 10 00 00 00 00
			TEX_MAG_FILTER_MASK		= 0x00000300,//dec 768	bin 11 00 00 00 00
			
			TEX_ENV_INTERPOLATE		= 0x00000400,//0 01 00 00 00 00 00 00	
			TEX_ENV_MODULATE		= 0x00000800,//0 10 00 00 00 00 00 00
			TEX_ENV_REPLACE			= 0x00000C00,//0 11 00 00 00 00 00 00
			TEX_ENV_DECAL			= 0x00001000,//1 00 00 00 00 00 00 00
			TEX_ENV_BLEND			= 0x00001400,//1 01 00 00 00 00 00 00
			TEX_ENV_ADD				= 0x00001800,//1 10 00 00 00 00 00 00
			TEX_ENV_MASK			= 0x00001C00,//1 11 00 00 00 00 00 00
			TEX_IGNORE_COLORS		= 0x00008000,

			TEX_GEN_FUNC_U_EYE		= 0x00010000,
			TEX_GEN_FUNC_U_OBJECT	= 0x00020000,
			TEX_GEN_FUNC_U_SPHERE	= 0x00040000,
			TEX_GEN_U				= 0x00070000,   //auxiliary value to test if any tex U coord gen function enabled. DO NOT FORGET to update this value when a tex gen mode is added or removed.
			TEX_GEN_FUNC_V_EYE		= 0x00080000,
			TEX_GEN_FUNC_V_OBJECT	= 0x00100000,
			TEX_GEN_FUNC_V_SPHERE	= 0x00200000,
			TEX_GEN_V				= 0x00380000,   //auxiliary value to test if any tex V coord gen function enabled. DO NOT FORGET to update this value when a tex gen mode is added or removed.
			TEX_GEN_REFLECTION		= 0x00400000,
			TEX_GEN_NORMAL			= 0x00800000,
			TEX_GEN_ENABLED			= 0x00ff0000,   //auxiliary value to test if any tex coord gen function enabled. DO NOT FORGET to update this value when a tex gen mode is added or removed.

			TEX_CUBE_MAP			= 0x01000000,
			ALPHA_TEST				= 0x02000000,
			TEXTURE3D				= 0x04000000,	//texture 3d
			BUMP_MAP				= 0x08000000	//bump map
		};

		///Texture flags
		unsigned texFlags;
		///U texGen plane
		Vector4 texGenUPlane;
		///V texGen plane
		Vector4 texGenVPlane;
		///Source blend func
		GLuint blendSrc;
		///Destination blend func
		GLuint blendDst;
		///Image name
	    wxString imageName;
		///Shader program
		ShaderProgram * shader;

		///Constructor
		Material(const wxString & _name) : name(CloneWxString(_name))
		{
			mid = MATERIAL_ID_NONE;
			depthFunc = GL_LEQUAL;
			shininess = 0.0f;
			reflectivity = 0.0f;
			textureMix = 1;
			flags = 0;
			texFlags = TEX_ENV_MODULATE | TEX_UWRAP_REPEAT | TEX_VWRAP_REPEAT | TEX_RWRAP_REPEAT | TEX_MIN_FILTER_MIPMAP | TEX_MAG_FILTER_LINEAR;
			texGenUPlane = Vector4(1,0,0,0);
			texGenVPlane = Vector4(0,1,0,0);
			blendSrc = blendDst = GL_ID_NONE;
			shader = (ShaderProgram *)NULL;
		}

		///Copy constructor
		Material(const Material & m) : mid(m.mid), name(CloneWxString(m.name)), ambient(m.ambient), diffuse(m.diffuse), specular(m.specular), emission(m.emission), shininess(m.shininess),
			depthFunc(m.depthFunc), reflectivity(m.reflectivity), textureMix(m.textureMix), flags(m.flags), texFlags(m.texFlags), texGenUPlane(m.texGenUPlane), texGenVPlane(m.texGenVPlane), blendSrc(m.blendSrc),
			blendDst(m.blendDst), imageName(CloneWxString(m.imageName))
		{
			shader = ( m.shader ? m.shader->Clone() : (ShaderProgram *)NULL );
		}

		///Destructor
		~Material()
		{
			SAFE_DELETE(shader);
		}

		///Get copy of instance
		Material * Clone() const
		{
			return new Material(*this);
		}

		///Assignment
		void Assign(const Material & m, int fieldsMask)
		{
			if (fieldsMask & 1)
				name = CloneWxString(m.name);
			if (fieldsMask & 2)//colors
			{
				ambient = m.ambient;
				diffuse = m.diffuse;
				specular = m.specular;
				emission = m.emission;
				shininess = m.shininess;
				reflectivity = m.reflectivity;
			}
			if (fieldsMask & 4)//flags
			{
				depthFunc = m.depthFunc;
				flags = m.flags;
				blendSrc = m.blendSrc;
				blendDst = m.blendDst;
			}
			if (fieldsMask & 8)//Texture
			{
				textureMix = m.textureMix;
				texFlags = m.texFlags;
				imageName = CloneWxString(m.imageName);
				texGenUPlane = m.texGenUPlane;
				texGenVPlane = m.texGenVPlane;
			}
			if (fieldsMask & 16)//Shader
			{
				if (shader)
					SAFE_DELETE(shader);
				if (m.shader)
				{
					shader=m.shader->Clone();
				}
			}
		}

		///Compare materials
		bool IsSameAs(const Material &m, int fieldsMask, bool withoutPaths = false) const
		{
			//Name
			if (fieldsMask & 1)
				if (name != m.name)
					return false;
			//Colors
			if (fieldsMask & 2)
				if ((ambient != m.ambient) || (diffuse != m.diffuse) || (specular != m.specular) || (emission != m.emission) || (shininess != m.shininess) || (reflectivity != m.reflectivity))
					return false;
			//Flags
			if (fieldsMask & 4)
				if ((depthFunc != m.depthFunc) || (flags != m.flags) || (blendSrc != m.blendSrc) || (blendDst != m.blendDst))
					return false;
			//Texture
			if (fieldsMask & 8)
			{
				if ((textureMix != m.textureMix) || (texFlags != m.texFlags) || (texGenUPlane != m.texGenUPlane) || (texGenVPlane != m.texGenVPlane))
					return false;
				if (withoutPaths)
				{
					if (!TextParser::ComparePaths(imageName, m.imageName, 3))
						return false;
				}
				else if (imageName != m.imageName)
					return false;
			}
			//Shader
			if (fieldsMask & 16)
			{
				if (shader)
				{
					if (!m.shader)
						return false;
					else
					{
						if (!shader->IsSameAs(*(m.shader), 3, withoutPaths))
							return false;
					}
				}
				else
				{
					if (m.shader)
						return false;
				}
			}
			return true;
		}

		bool FixBlendingAndCubeMap(const Image *img)
		{
			bool changed=false;
			bool blended=(diffuse.w!=1);
			bool mask=false;
			if (img && img->IsOk())
			{
				//if (const_cast<Image*>(img)->SetTransparencyAndSetAlphaUsage())
				if (img->IsTransparent())
				{
					blended=true;
					if (img->GetAlphaUsageType() == Image::MASK)
						mask = true;
				}
				if (img->IsCube() && !(texFlags & Material::TEX_CUBE_MAP))
				{
					texFlags |= Material::TEX_CUBE_MAP;
					changed=true;
				}
				if (!img->IsCube() && (texFlags & Material::TEX_CUBE_MAP))
				{
					texFlags ^= Material::TEX_CUBE_MAP;
					changed=true;
				}
			}
			if (blended)
			{
				if (!(flags & Material::BLENDED))
				{
					flags |= Material::BLENDED;
					blendSrc = GL_SRC_ALPHA;
					blendDst = GL_ONE_MINUS_SRC_ALPHA;
					changed=true;
				}
			}
			else
			{
				if (flags & Material::BLENDED)
				{
					flags ^= Material::BLENDED;
					changed=true;
				}
			}
			if (mask && !(texFlags & Material::ALPHA_TEST))
			{
				texFlags |= Material::ALPHA_TEST;
				changed=true;
			}
			if (!mask && (texFlags & Material::ALPHA_TEST))
			{
				texFlags ^= Material::ALPHA_TEST;
				changed=true;
			}
			return changed;
		}

		void UpdateBumpMapFlag() {
#if 1
			texFlags &= ~Material::BUMP_MAP;
			if (shader) {
				for (std::vector<ShaderParam>::const_iterator it = shader->paramList.begin();
					it != shader->paramList.end();
					++it)
					if (it->name == wxT("bumpTexture")) {
					//					const_smart_pointer<Image> image = KERNEL->sceneManager->GetImage(it->imageName, false);
					//					if (image.getPtr())
					texFlags |= Material::BUMP_MAP; // we have a bump map!
					break;
					}
			}
#endif
		}
		
		
		///Get string with material's properties
		wxString ToString() const
		{
			return wxString::Format(wxT("MID: %lu, Name: "), (unsigned long)mid)+name+wxT("\nambient: ")+ambient.ToString()+wxT(", diffuse: ")+diffuse.ToString()
				+wxT(", specular: ")+specular.ToString()+wxT(", emission: ")+emission.ToString()+wxT("\nshininess: ")+TextWriter::WriteToWxString(shininess)
				+wxT(", reflectivity: ")+TextWriter::WriteToWxString(reflectivity)
				+wxString::Format(wxT(", depthFunc: %i, flags: %i, blendSrc: %i, blendDst: %i\n"), depthFunc, flags, blendSrc, blendDst)
				+wxT("imageName: ")+imageName
				+wxT("\ntextureMix: ")+TextWriter::WriteToWxString(textureMix)+wxString::Format(wxT(", texFlags: %i"), texFlags)
				+wxT(", texGenUPlane: ")+texGenUPlane.ToString()+wxT(", texGenVPlane: ")+texGenVPlane.ToString()+wxT("\nshader: ")+(shader?shader->ToString():wxT(""));
		}

		inline bool IsTransparent() const	{	return (flags & BLENDED) != 0;			}
		inline bool IsReflective() const	{	return (flags & FLAT_REFLECTION) != 0;	}
		inline bool IsCubeMap() const		{	return (texFlags & TEX_CUBE_MAP) != 0;	}
		inline bool IsAlphaTest() const		{	return (texFlags & ALPHA_TEST) != 0;	}

		private:
			Material & operator =(const Material & m);
	};
}

#endif
