/* -*- c++ -*- 
*
* Copyright (C) 2004 Mekensleep
*
*	Mekensleep
*	24 rue vieille du temple
*	75004 Paris
*       licensing@mekensleep.com
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301, USA.
*
* Authors:
*  Igor Kravtchenko <igor@ozos.net>
*
*/

#ifndef __OSGCAL__TEXLAYERS_FLATTEN_H__
#define __OSGCAL__TEXLAYERS_FLATTEN_H__

#include <osg/Geometry>
#include <osg/Geode>
#include <osg/Group>
#include <osg/Material>
#include <osg/ref_ptr>
#include <osg/TexEnvCombine>
#include <osg/Texture2D>

#include <osgUtil/CullVisitor>
#include <osgUtil/RenderBin>

#include <vector>

#include <osgCal/Export>

#ifdef SUPPORT_GIF_HLS
# if defined(_MSC_VER) || defined(__CYGWIN__) || defined(__MINGW32__) || defined( __BCPLUSPLUS__) || defined( __MWERKS__)
#   include <osgPlugins/gif/src/gifloader.h>
# else
#   include <osgCal/gifloader.h>
# endif
#endif

namespace osgCal {

OSGCAL_EXPORT void RGBtoHSL(float _r, float _g, float _b, float &_h, float &_s, float &_l);
OSGCAL_EXPORT void HSLtoRGB(float _h, float _s, float _l, float &_r, float &_g, float &_b);
OSGCAL_EXPORT void InvertPremultipliedAlpha(osg::Image &_img);

class TextureLayersFlatten : public osg::Referenced {
public:

#ifdef SUPPORT_GIF_HLS
	static std::map<osg::ref_ptr<osg::Texture2D>, osg::ref_ptr<GIFImage> > texture2GIFimage_;
#endif

	enum PIXELOP {
		// terminology are taken from Photoshop
		PIXELOP_DECAL, // for single layer
		PIXELOP_NORMAL,
		PIXELOP_OVERLAY,
		PIXELOP_LINEARDODGE,
		PIXELOP_MULTIPLY,
	};

	class Layer {
	public:
		Layer() { };
		osg::ref_ptr<osg::Texture2D> texture_;

#ifdef SUPPORT_GIF_HLS
		osg::ref_ptr<GIFImage> orgGIFforHLS_;
#endif
		PIXELOP pixelOp_;
	};

	class MyGeometry : public osg::Geometry {
	public:

		MyGeometry(osg::Texture2D *_target, bool _bClearFrameBufferAfterRecopy, int _iOrder)
		{
			target_ = _target;
			bClearFrameBufferAfterRecopy_ = _bClearFrameBufferAfterRecopy;
			bRendered_ = false;
			iOrder_ = _iOrder;
		}

		osg::ref_ptr<osg::Texture2D> target_;
		bool bClearFrameBufferAfterRecopy_;
		int iOrder_;
		mutable bool bRendered_;

		virtual void drawImplementation(osg::State &) const;
	};

/*
	class TextureFilterGeometry : public osg::Geometry {
	public:

		TextureFilterGeometry(bool bSetFilter)
		{
			bSetFilter_ = bSetFilter;
		}

		bool bSetFilter_;

		virtual void drawImplementation(osg::State &) const;

		bool computeBound() const;
	};
*/
	class QuadParams {
	public:

		QuadParams()
		{
			geomToUse = NULL;
			minPt = osg::Vec2f(0, 0);
			maxPt = osg::Vec2f(1, 1);
			minUV = osg::Vec2f(0, 0);
			maxUV = osg::Vec2f(1, 1);
			srcBlendFactor = GL_SRC_ALPHA;
			dstBlendFactor = GL_ONE_MINUS_SRC_ALPHA;
			bInvertUV = true;
			bDisableWriteMask = true;
		}

		MyGeometry *geomToUse;
		osg::Vec2f minPt;
		osg::Vec2f maxPt;
		osg::Vec2f minUV;
		osg::Vec2f maxUV;
		GLenum srcBlendFactor;
		GLenum dstBlendFactor;
		bool bInvertUV;
		bool bDisableWriteMask;
	};

	class Quad : public osg::Referenced {

		Quad() { };

	public:
		explicit Quad(	QuadParams &);

		virtual ~Quad();

		inline osg::Geode* getGeode() const { return (osg::Geode*) geode_.get(); }
		inline osg::Geometry* getGeometry() const { return (osg::Geometry*) geom_.get(); }
		inline osg::Vec3Array* getVerticesArray() const { return (osg::Vec3Array*) vertices_.get(); }
		inline osg::Vec2Array* getUVArray() const { return (osg::Vec2Array*) uv_.get(); }
		inline osg::Material* getMaterial() const { return (osg::Material*) material_.get(); }
		inline osg::Texture2D* getTexture() const { return (osg::Texture2D*) texture_.get(); }
		inline void setTexture(osg::Texture2D *_tex) { texture_ = _tex; }

	protected:
		osg::ref_ptr<osg::Geode> geode_;
		osg::ref_ptr<osg::Geometry> geom_;
		osg::ref_ptr<osg::Vec3Array> vertices_;
		osg::ref_ptr<osg::Vec2Array> uv_;
		osg::ref_ptr<osg::Material> material_;
		osg::ref_ptr<osg::Texture2D> texture_;
	};

	class BaseRenderTechnic {
	public:

		BaseRenderTechnic(TextureLayersFlatten *_parent, osg::Texture2D *_textureA, osg::Texture2D *_textureB, osg::Texture2D *_textureOutput) :
			parent_(_parent),
			createdFromlayer_(NULL),
			textureA_(_textureA),
			textureB_(_textureB),
			textureOutput_(_textureOutput),
			opacity_(1),
			colorFactor_(1, 1, 1),
			hsl_(0, 0, 0)
		  {
		  };

		virtual ~BaseRenderTechnic();
		
		OSGCAL_EXPORT inline int getTechnicID() const { return technicID_; }

		OSGCAL_EXPORT inline float getOpacity() const { return opacity_; }
		OSGCAL_EXPORT void setOpacity(float);

		OSGCAL_EXPORT inline const osg::Vec3f& getColorFactor() const { return colorFactor_; }
		OSGCAL_EXPORT void setColorFactor(const osg::Vec3f &_col);

		virtual void flushTextureCache();

		#ifdef SUPPORT_GIF_HLS
		OSGCAL_EXPORT inline const osg::Vec3f& getHSL() const { return hsl_; }
		OSGCAL_EXPORT inline void setHSL(const osg::Vec3f &_hsl) { hsl_ = _hsl; }

		OSGCAL_EXPORT void updateGIFTextureForHSL(osg::State &);
		#endif

	protected:

		virtual void setup(osg::Group *on) { };
		virtual void updateCombiners();

		int technicID_;
		TextureLayersFlatten *parent_;
		Layer *createdFromlayer_;
		osg::ref_ptr<osg::Texture2D> textureA_;
		osg::ref_ptr<osg::Texture2D> textureB_;
		osg::ref_ptr<osg::Texture2D> textureOutput_;
		osg::ref_ptr<osg::Group> parentGroup_;

		float opacity_;
		osg::Vec3f colorFactor_;
		osg::Vec3f hsl_;

		std::vector< osg::ref_ptr<Quad> > quads_;

		friend class TextureLayersFlatten;
	};

	class DecalRenderTechnic : public BaseRenderTechnic {
	public:
	
		typedef BaseRenderTechnic Parent;
		
		DecalRenderTechnic(TextureLayersFlatten *_parent, osg::Texture2D *_textureA, osg::Texture2D *_textureOutput) :
    BaseRenderTechnic(_parent, _textureA, NULL, _textureOutput)
		{
			technicID_ = PIXELOP_DECAL;
		}

		virtual ~DecalRenderTechnic() { }

		virtual void flushTextureCache();

	protected:

		virtual void setup(osg::Group *parent);
		virtual void updateCombiners();
		
		osg::ref_ptr<osg::TexEnvCombine> combiner_;

		friend class TextureLayersFlatten;
	};

	class NormalRenderTechnic : public BaseRenderTechnic {
	public:
	
		typedef BaseRenderTechnic Parent;
	
		NormalRenderTechnic(TextureLayersFlatten *_parent, osg::Texture2D *_textureA, osg::Texture2D *_textureB, osg::Texture2D *_textureOutput) :
    BaseRenderTechnic(_parent, _textureA, _textureB, _textureOutput)
		{
			technicID_ = PIXELOP_NORMAL;
		}

		virtual ~NormalRenderTechnic() { }

		virtual void flushTextureCache();

	protected:

		virtual void setup(osg::Group *parent);
		virtual void updateCombiners();

		osg::ref_ptr<osg::TexEnvCombine> combiners_[2];

		friend class TextureLayersFlatten;
	};

	class OverlayRenderTechnic : public BaseRenderTechnic {
	public:
	
		typedef BaseRenderTechnic Parent;
	
		OverlayRenderTechnic(TextureLayersFlatten *_parent, osg::Texture2D *_textureA, osg::Texture2D *_textureB, osg::Texture2D *_textureOutput) :
		BaseRenderTechnic(_parent, _textureA, _textureB, _textureOutput)
		{
			technicID_ = PIXELOP_OVERLAY;
		}

		virtual ~OverlayRenderTechnic() { }

		void flushTextureCache();

	protected:

		virtual void setup(osg::Group *on);
		virtual void updateCombiners();

		osg::ref_ptr<osg::TexEnvCombine> combiners_[2];

		friend class TextureLayersFlatten;
	};

	class LinearDodgeRenderTechnic : public BaseRenderTechnic {
	public:
	
		typedef BaseRenderTechnic Parent;
	
		LinearDodgeRenderTechnic(TextureLayersFlatten *_parent, osg::Texture2D *_textureA, osg::Texture2D *_textureB, osg::Texture2D *_textureOutput) :
		BaseRenderTechnic(_parent, _textureA, _textureB, _textureOutput)
		{
			technicID_ = PIXELOP_LINEARDODGE;
		}

		virtual ~LinearDodgeRenderTechnic() { }

		virtual void flushTextureCache();

	protected:

		virtual void setup(osg::Group *on);
		virtual void updateCombiners();

		osg::ref_ptr<osg::TexEnvCombine> combiners_[2];

		friend class TextureLayersFlatten;
	};

	class MultiplyRenderTechnic : public BaseRenderTechnic {
	public:
	
		typedef BaseRenderTechnic Parent;
	
		MultiplyRenderTechnic(TextureLayersFlatten *_parent, osg::Texture2D *_textureA, osg::Texture2D *_textureB, osg::Texture2D *_textureOutput) :
		BaseRenderTechnic(_parent, _textureA, _textureB, _textureOutput)
		{
			technicID_ = PIXELOP_MULTIPLY;
		}

		virtual ~MultiplyRenderTechnic() { }

		void flushTextureCache();

	protected:

		virtual void setup(osg::Group *on);
		virtual void updateCombiners();

		osg::ref_ptr<osg::TexEnvCombine> combiners_[2];

		friend class TextureLayersFlatten;
	};

	TextureLayersFlatten();
	virtual ~TextureLayersFlatten();

	static void resetBinCounter();
	static void destroy();

	void init(int width,
						int height,
						std::vector<Layer> &layers,
						osg::Group *on,
						osg::Texture2D *myOutputTexture = NULL,
						osg::Texture2D *colorMask = NULL,
						osg::Texture2D *maskOn = NULL,
						bool bUseOldLayersParams = true);

	inline const std::vector<Layer>& getOrgLayer() const { return orgLayers_; }

	inline BaseRenderTechnic* getBaseRenderTechnic(unsigned int _index) { return _index < technics_.size() ? technics_[_index] : NULL; }
	inline int getNbBaseRenderTechnics() const { return technics_.size(); }

	inline osg::Texture2D* getOutputTexture() { return outputTexture_.get(); }
	inline void setOutputTexture(osg::Texture2D *_tex) { outputTexture_ = _tex; }
	
	OSGCAL_EXPORT void flushTextureCacheForAllBRT();

protected:

	void setupLastColorMaskPass(osg::Group *on);

	std::vector<BaseRenderTechnic*> technics_;
	std::vector<Layer> orgLayers_;

	osg::ref_ptr<osg::Texture2D> outputTexture_;
	
	osg::ref_ptr<osg::Texture2D> colorMask_;
	osg::ref_ptr<osg::Texture2D> maskOn_;

	osg::ref_ptr<osg::MatrixTransform> modelview_abs_;
	osg::ref_ptr<osg::Projection> projection_;

	int width_, height_;
	
	osg::ref_ptr<Quad> quads_[2];
};

} // namespace osgCal

#endif
