//-------------------------------------------------------------------------
//
// File:		EffectI.h
// Desc:		Effect interface.
// Author:		memon <memon@inside.org>
//
//-------------------------------------------------------------------------
//	Copyright (c) 2000 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://moppi.inside.org/demopaja/
//-------------------------------------------------------------------------


#ifndef __DEMOPAJA_EFFECTI_H__
#define __DEMOPAJA_EFFECTI_H__


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 <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( 0x1000000 );
};

namespace Composition {


	//! 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.
	};

	//! 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 );

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

		//! 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( 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;

		//! This method is called when a parameter is changed.
		/*!	Do not call the rendering methods from this method.
			The system will automatically update the state effect
			if a parameter is changed.
		*/
		virtual void				update_notify( PajaTypes::uint32 ui32GizmoId, PajaTypes::uint32 ui32ParamId, PajaTypes::int32 i32Time );

		//! 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.
		*/
		virtual void				initialize( PajaTypes::uint32 ui32Reason, PajaSystem::DeviceContextC* pContext, PajaSystem::TimeContextC* pTimeContext ) = 0;

		//! Evaluate the state of this effect at specified time.
		/*!	All the calculation should be done in this method.
			The eval_state() and do_frame() have been separated to allow
			the user interface to update the state of the effect only once
			and draw the effect many times.

			The eval_state() method may also be called to update the data
			(such as the transformation matrix), but not to draw it.

			Example:

			\code
			void
			TestEffectC::eval_state( int32 i32Time, TimeContextC* pTimeContext )
			{
				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;
			}
			\endcode

		*/
		virtual void				eval_state( PajaTypes::int32 i32Time, PajaSystem::TimeContextC* pTimeContext ) = 0;

		//! Execute (draws, plays, etc.) the current state of the effect.
		/*!	All the rendering code goes in this method.

			Example:
			\code
			void
			PlasmaEffectC::do_frame( DeviceContextC* pContext )
			{
				// 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				do_frame( PajaSystem::DeviceContextC* pContext ) = 0;

		//! 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;

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

	private:
		std::string				m_sName;
		PajaTypes::int32		m_i32Flags;
		TimeSegmentC*			m_pTimeSegment;
	};

};

#endif // __DEMOPAJA_EFFECTI_H__
