/* -*- c++ -*- 
*
* Copyright (C) 2007 Loic Dachary <loic@dachary.org>
* Copyright (C) 2004, 2006 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@tsarevitch.org>
*
*/

#ifndef __OSGCAL__TEXLAYERS_FLATTEN_H__
#define __OSGCAL__TEXLAYERS_FLATTEN_H__

#include <osgCal/Export>

#include <osg/Version>
#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>

namespace osgCal {

OSGCAL_EXPORT void InvertPremultipliedAlpha(osg::Image &_img);

class TextureLayersFlatten : public osg::Referenced {
public:

	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_;
		PIXELOP pixelOp_;
	};

	class MyGeometry : public osg::Geometry {
	public:

		MyGeometry(osg::Texture2D *_target, bool _bClearFrameBufferAfterRecopy)
		{
			target_ = _target;
			bClearFrameBufferAfterRecopy_ = _bClearFrameBufferAfterRecopy;
			nodeToMask_ = NULL;
		}

		void setNodeToMask(osg::Node *_nodeToMask)
		{
			nodeToMask_ = _nodeToMask;
		}

		osg::ref_ptr<osg::Texture2D> target_;
		osg::Geode *geode_;
		bool bClearFrameBufferAfterRecopy_;
		
		osg::Node *nodeToMask_;
		
#if OSG_VERSION_MAJOR != 2
		virtual void drawImplementation(osg::State &) const;
#else // OSG_VERSION_MAJOR != 2
		virtual void drawImplementation(osg::RenderInfo &) const;
#endif // OSG_VERSION_MAJOR != 2
	};

	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;
		}

		osg::Geometry *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)
		  {
		  };

		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();

	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_;

		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,
				osg::Texture2D *colorMask,
				osg::Texture2D *maskOn,
				osg::Texture2D *alphaPart = 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);
	void setupAlphaPartPass(osg::Group *on, osg::Texture2D *textureToPutAlphaOn);

	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::Texture2D> alphaPart_;

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

	int width_, height_;

	osg::ref_ptr<Quad> quads_[4];
	

};

}

#endif
