//-------------------------------------------------------------------------
//
// File:		EffectI.h
// Desc:		Effect interface.
// Author:		memon <memon@inside.org>
//
//-------------------------------------------------------------------------
//	Copyright (c) 2000-2004 Moppi Productions. All Rights Reserved.
//  This file is part of Moppi Demopaja SDK. For conditions of 
//  distribution and use, see the accompanying license.txt file.
//  http://www.demopaja.org
//-------------------------------------------------------------------------


#ifndef __DEMOPAJA_EFFECTI_H__
#define __DEMOPAJA_EFFECTI_H__

// forward declaration
namespace Composition {
	class EffectI;
};

#include "PajaTypes.h"
#include "DataBlockI.h"
#include "EditableI.h"
#include "UndoC.h"
#include "GizmoI.h"
#include "TimeSegmentC.h"
#include "ClassIdC.h"
#include "FileIO.h"
#include "ImportableI.h"
#include "DeviceContextC.h"
#include "BBox2C.h"
#include "Matrix2C.h"
#include "TimeContextC.h"
#include "FileHandleC.h"
#include "LayerC.h"
#include <string>
#include <vector>


namespace PluginClass {
	//! The effect super class ID.
	/*! Every effect should return CLASS_EFFECT as super class. */
	const PluginClass::SuperClassIdC		SUPERCLASS_EFFECT = PluginClass::SuperClassIdC( 0, 0x1000000 );
};

namespace Composition {

	//! Base classID.
	const PluginClass::SuperClassIdC		BASECLASS_EFFECT = PluginClass::SuperClassIdC( 1, 2 );

	//! Predefined indices for get_gizmo().
	/*! This set of indices are used to get default parameters from effect. \see EffectI::get_default_param(). */
	enum DefaultParamE {
		DEFAULT_PARAM_POSITION = 1,		//!< Position parameter.
		DEFAULT_PARAM_ROTATION = 2,		//!< Rotation parameter.
		DEFAULT_PARAM_SCALE = 3,		//!< Scale parameter.
		DEFAULT_PARAM_PIVOT = 4,		//!< Pivot parameter.
	};

	enum EffectUIDrawFlagsE {
		EFFECT_UIDRAW_NONE	= 0,				//! Empty.
		EFFECT_UIDRAW_EXTRAS = 0x0001,	//! Draw extras enabled.
	};

	//! The effect interface.
	/*!	The effect interface is one of the most import interfaces in Demopaja for the coder.
		Every effect to be shown on screen have to be derived from this class.
		
		In normal play mode there are two methods which are called every frame: eval_state() and
		do_frame(). The first evaluates the state of the specified frame, calculates all
		data that can be calculated before hand. The latter renders the effect to the screen.

		The reason to separate the rendering in two parts is that sometimes the Demopaja editor
		needs some information about the effect but there is no need to render the object. Also
		a frame can be rendered multiple time while editing and there's no reason to update the
		data, only to draw it.

		To create a new instace of the effect class a static member should be created,
		which takes care of the instantiating. The member could look like this:

		\code
		TestEffectC*
		TestEffectC::create_new()
		{
			return new TestEffectC;
		}
		\endcode
	*/
	class EffectI : public Edit::EditableI
	{
	public:
		//! Deep copy from a data block, see Edit::DataBlockI::copy().
		/*!	When overriding this method the base class member should be called first.

			Example:
			\code
			void
			TestEffectC::copy( EditableI* pEditable )
			{
				EffectI::copy( pEditable );
				TestEffectC*	pEffect = (TestEffectC*)pEditable;
				m_pGizmo->copy( pEffect->m_pGizmo );
			}
			\endcode
		*/
		virtual void				copy( Edit::EditableI* pEditable );
		//! Shallow copy from a editable, see Edit::EditableI::restore().
		/*!	When overriding this method the base class member should be called first.
			
			Example:
			\code
			void
			TestEffectC::restore( EditableI* pEditable )
			{
				EffectI::restore( pEditable );
				TestEffectC*	pEffect = (TestEffectC*)pEditable;
				m_pGizmo = pEffect->m_pGizmo;
			}
			\endcode
		*/
		virtual void				restore( Edit::EditableI* pEditable );
		//! Returns the base class ID.
		virtual PluginClass::SuperClassIdC		get_base_class_id() const;
		//! Update notify from a connected editable.
		/*!	Do not call the rendering methods from this method.
			The system will automatically update the state effect
			if a parameter is changed.

			The return value determines the how the Demopaja system has to respond to the parameter change.
			For example id the return value is PARAM_NOTIFY_UI_CHANGE, the user interface is update after the
			parameter change. Only use the PARAM_NOTIFY_UI_CHANGE if the parameter/gizmos layout
			(such as number of parameter/gizmos) has changed.

			Use the get_base_class() method to figure out if the caller has been a
			parameter, and get_parent() method to get the gizmo if necessary.
			\code
			uint32
			TestEffectC::update_notify( EditableI* pCaller )
			{
				ParamI*	pParam = 0;
				GizmoI*	pGizmo = 0;

				if( pCaller->get_base_class() == BASECLASS_PARAMETER )
					pParam = (ParamI*)pCaller;
				if( pParam )
					pGizmo = pParam->get_parent();
			}
			\endcode

			See also:
				\see Composition::ParamSetValNotifyE
		*/
		virtual PajaTypes::uint32							update_notify( EditableI* pCaller );

		//! Sets the name of the effect.
		/*! Implemented by the EffectI class. */
		virtual void				set_name( const char* szName );
		//! Returns the name of the effect.
		/*! Implemented by the EffectI class. */
		virtual const char*			get_name() const;

		//! Returns number of gizmos in the effect.
		virtual PajaTypes::int32	get_gizmo_count() = 0;
		//! Returns gizmo at specified index.
		virtual GizmoI*				get_gizmo( PajaTypes::int32 i32Index ) = 0;

		//! Sets the effect flags.
		/*! Be careful to use this method. There are some flags, which
			have to be in place to make the effect work correctly.
			Use add, del or toggle flags methods instead.
		*/
		virtual void				set_flags( PajaTypes::int32 i32Flags );
		//! Sets only specified flags.
		/*! Implemented by the EffectI class. */
		virtual void				add_flags( PajaTypes::int32 i32Flags );
		//! Removes only specified flags.
		/*! Implemented by the EffectI class. */
		virtual void				del_flags( PajaTypes::int32 i32Flags );
		//! Toggles only specified flags.
		/*! Implemented by the EffectI class. */
		virtual void				toggle_flags( PajaTypes::int32 i32Flags );
		//! Returns effect flags.
		/*! Implemented by the EffectI class. */
		virtual PajaTypes::int32	get_flags();

		//! Sets the parent Layer.
		void						set_parent( LayerC* pLayer );
		//! Gets the parent Layer.
		LayerC*						get_parent() const;

		//! Returns the timesegment of this effect
		/*! Implemented by the EffectI class. */
		virtual TimeSegmentC*		get_timesegment();

		//! Returns super class ID of the effect (should be SUPERCLASS_EFFECT for effects).
		virtual PluginClass::SuperClassIdC	get_super_class_id();
		//! Returns unique class ID of the effect.
		virtual PluginClass::ClassIdC		get_class_id() = 0;
		//! Returns effect's class name as NULL terminated string.
		virtual const char*					get_class_name() = 0;

		//! Sets the default file if effect has a default file.
		/*!	If the effect uses file parameters one of the file parameters
			has to be set as default file parameter. This is used for example
			is a file is dragged from File Inspector window to the Timeline Window.
			That causes new effect to be created and the dragged file to be set as
			default file. This method does not have to accept the file.
		*/
		virtual void				set_default_file( PajaTypes::int32 i32Time, Import::FileHandleC* pHandle ) = 0;

		//! Returns default parameter.
		/*! Default parameters are commonly used parameters such as position and scale.

			Example:
			\code
			ParamI*
			TestEffectC::get_default_param( PajaTypes::int32 i32Param )
			{
				if( i32Param == DEFAULT_PARAM_POSITION )
					return m_pGizmo->get_parameter( TEST_PARAM_POS );
				else if( i32Param == DEFAULT_PARAM_ROTATION )
					return m_pGizmo->get_parameter( TEST_PARAM_ROT );
				return 0;
			}
			\endcode
			\see DefaultParamE
		*/
		virtual ParamI*				get_default_param( PajaTypes::int32 i32Param ) = 0;

		//! Returns true if the given point is inside the region of the effect.
		/*!
			Example:
			\code
			bool
			TestEffectC::hit_test( const PajaTypes::Vector2C& rPoint )
			{
				// Point in polygon test (from comp.graphics.algorithms FAQ).
				int32	i, j;
				bool	bInside = false;
				for( i = 0, j = 4 - 1; i < 4; j = i++ ) {
					if( ( ((m_rVertices[i][1] <= rPoint[1]) && (rPoint[1] < m_rVertices[j][1])) ||
						((m_rVertices[j][1] <= rPoint[1]) && (rPoint[1] < m_rVertices[i][1])) ) &&
						(rPoint[0] < (m_rVertices[j][0] - m_rVertices[i][0]) * (rPoint[1] - m_rVertices[i][1]) / (m_rVertices[j][1] - m_rVertices[i][1]) + m_rVertices[i][0]) )
						bInside = !bInside;
				}
				return bInside;
			}
			\endcode
		*/
		virtual bool				hit_test( const PajaTypes::Vector2C& rPoint ) = 0;

		//! Initialize effect.
		/*! \param ui32Reason The reason this method was called.
		
			If ui32Reason is \b INIT_INITIAL_UPDATE, the effect has been
			just instantiated (created) and all available data is
			passed to the effect. This happens either if the effect
			has just been created by the user or the effect is just
			loaded.

			If ui32Reason is \b INIT_DEVICE_CHANGED, the settings for a device
			has been changed. Effect should check that all the devices it uses
			and release all resources bind to them and later restore the resources
			when INIT_DEVICE_VALIDATE command is send. If a device is removed or
			replaced with another (in case of graphics device), the device which present
			is present when INIT_DEVICE_CHANGED is called may not be anymore present
			when INIT_DEVICE_VALIDATE is called later.

			If ui32Reason is \b INIT_DEVICE_INVALIDATE, most a device has gone into a state
			where some device resources needs to released so that the device can be reset.
			Such case can be for example when graphics device changes resolution.

			If ui32Reason is \b INIT_DEVICE_VALIDATE, initialize() is called either after
			INIT_DEVICE_INVALIDATE or INIT_DEVICE_CHANGED. In both cases the effect should
			try to restore and recreate the resources it uses from devices.

			The default implementation stores the interface pointer, so please call it.
		*/
		virtual void				initialize( PajaTypes::uint32 ui32Reason, PajaSystem::DemoInterfaceC* pInterface );

		//! Evaluate the state of this effect and executes it at specified time.
		/*!	All the calculation and rendering should be done in this method.
			Since this method is responsible to calculate the hit test data
			and transformation matrix of the effect, it should be done, even
			the effect fails to render.

			\b Note: Prior versio 0.7 the effect interface also had method
			do_frame() which was used to render the effects. It turned out
			that it was unnecessary and only lead to some work done twice.
			Now these two methods are fused into one.
		
			Example:

			\code
			void
			TestEffectC::eval_state( int32 i32Time )
			{
				Matrix2C	rPosMat, rRotMat, rScaleMat, rPivotMat;
				Vector2C	rPivot = m_pGizmo->get_pivot( i32Time );

				// Calc matrix.
				rPivotMat.set_trans( -rPivot );
				rPosMat.set_trans( m_pGizmo->get_pos( i32Time ) + rPivot );
				rRotMat.set_rot( m_pGizmo->get_rot( i32Time ) / 180.0f * M_PI );
				rScaleMat.set_scale( m_pGizmo->get_scale( i32Time ) ) ;

				// Store matrix.
				m_rTM = rPivotMat * rRotMat * rScaleMat * rPosMat;

				// Calc bounding box
				float32		f32Width = 25, f32Height = 25;
				Vector2C	rMin, rMax, rVec;

				m_rVertices[0][0] = -f32Width;		// top-left
				m_rVertices[0][1] = -f32Height;
				m_rVertices[1][0] =  f32Width;		// top-right
				m_rVertices[1][1] = -f32Height;
				m_rVertices[2][0] =  f32Width;		// bottom-right
				m_rVertices[2][1] =  f32Height;
				m_rVertices[3][0] = -f32Width;		// bottom-left
				m_rVertices[3][1] =  f32Height;

				for( uint32 i = 0; i < 4; i++ ) {
					rVec = m_rTM * m_rVertices[i];
					m_rVertices[i] = rVec;
					if( !i )
						rMin = rMax = rVec;
					else {
						if( rVec[0] < rMin[0] ) rMin[0] = rVec[0];
						if( rVec[1] < rMin[1] ) rMin[1] = rVec[1];
						if( rVec[0] > rMax[0] ) rMax[0] = rVec[0];
						if( rVec[1] > rMax[1] ) rMax[1] = rVec[1];
					}
				}
				// Store bounding box.
				m_rBBox[0] = rMin;
				m_rBBox[1] = rMax;

  
				// Query the interface to use.
				OpenGLInterfaceC*	pInterface = (OpenGLInterfaceC*)pContext->query_interface( INTERFACE_OPENGL );
				if( !pInterface )
					return;

				// Use the interface to set the rendering area.
				pInterface->set_ortho( m_rBBox, m_rBBox.width(), m_rBBox.height() );

				// Render the effect
				...
			}
			\endcode

		*/
		virtual void				eval_state( PajaTypes::int32 i32Time ) = 0;

		//! Draws the UI of the effect if necessary, default implementation does nothing.
		virtual void				draw_ui( PajaTypes::uint32 ui32Flags );

		//! Returns axis-aligned bounding box of the effect.
		virtual PajaTypes::BBox2C	get_bbox() = 0;

		//! Returns transformaion matrix of the effect.
		virtual const PajaTypes::Matrix2C&	get_transform_matrix() const = 0;

		//! Increase reference count (used internally).
		virtual void				add_reference( EditableI* pEditable );
		//! Reset reference count (used internally).
		virtual void				reset_references();
		//! Returns reference count (used internally).
		virtual PajaTypes::int32	get_reference_count();

		//! Serialize effect to a Demopaja output stream.
		/*!	The base class implementation of this method has to
			be called in the overridden method.

			Example:
			\code
			uint32
			TestEffectC::save( SaveC* pSave )
			{
				uint32	ui32Error = IO_OK;
				// EffectI stuff
				pSave->begin_chunk( CHUNK_TESTEFFECT_EFFECTI, TESTEFFECT_VERSION );
					ui32Error = EffectI::save( pSave );
				pSave->end_chunk();
				return ui32Error;
			}
			\endcode
		*/
		virtual PajaTypes::uint32	save( FileIO::SaveC* pSave );
		//! Serialize effect from a Demopaja input stream.
		/*!	The base class implementation of this method has to
			be called in the overridden method.

			Example:
			\code
			uint32
			TestEffectC::load( LoadC* pLoad )
			{
				uint32	ui32Error = IO_OK;
				while( (ui32Error = pLoad->open_chunk()) == IO_OK ) {
					switch( pLoad->get_chunk_id() ) {
					case CHUNK_TESTEFFECT_EFFECTI:
						if( pLoad->get_chunk_version() == TESTEFFECT_VERSION )
							ui32Error = EffectI::load( pLoad );
						break;
					default:
						assert( 0 );
					}
					pLoad->close_chunk();
					if( ui32Error != IO_OK && ui32Error != IO_END )
						return ui32Error;
				}
				return ui32Error;
			}
			\endcode
		*/
		virtual PajaTypes::uint32	load( FileIO::LoadC* pLoad );


	protected:
		//! Default constructor.
		EffectI();
		//! Default constructor with reference to the original.
		EffectI( Edit::EditableI* pOriginal );
		//! Default destructor.
		virtual ~EffectI();

		std::string				m_sName;
		PajaTypes::int32		m_i32Flags;
		TimeSegmentC*			m_pTimeSegment;
		LayerC*					m_pParent;
		PajaSystem::DemoInterfaceC*	m_pDemoInterface;
		std::list<EditableI*>	m_lstReferences;
	};

};

#endif // __DEMOPAJA_EFFECTI_H__
