// Copyright 2013 Autodesk, Inc. All rights reserved. 
//
// Use of this software is subject to the terms of the Autodesk 
// license agreement provided at the time of installation or download, 
// or which otherwise accompanies this software in either electronic 
// or hard copy form.

#ifndef __XGENMENTALRAYPROCEDURAL_H__
#define __XGENMENTALRAYPROCEDURAL_H__

#include <vector>
#include <shader.h>
#include <memory>

#include <xgen/XgRenderAPI.h>
using namespace XGenRenderAPI;

typedef miTag miString;

namespace XGenMR
{
	class ProceduralWrapper;

	struct miMatrixClass
	{
		miMatrix m;
	};

	struct miMatrixStruct
	{
		miMatrixStruct( const miMatrix m )
		{
			mi_matrix_copy( m_matrix, m );
		}
		miMatrixStruct( const miMatrixStruct& m )
		{
			*this = m;
		}
		miMatrixStruct& operator=( const miMatrixStruct& m )
		{
			mi_matrix_copy( m_matrix, m.m_matrix );
			return *this;
		}

		/*miMatrix& operator()
		{
			return m_matrix;
		}

		const miMatrix& operator() const
		{
			return m_matrix;
		}
*/
		miMatrix m_matrix;
	};

	// Registration user data class used for pushing the data on the hair scalars
	// This stores if the data needs to be pushed, and a buffer pointer to it.
	template< class T/*user data type*/, class E/*enum for the primitive cache*/, int N/*Number of components.*/ >
	class TUserData
	{
	public:
		TUserData( PrimitiveCache* pc, PrimitiveCache::EBoolAttribute bAdd, E eArray, int context, const char* name );

		TUserData( const T* p, int context, const char* name );

		inline T get(size_t j) const;

		inline void registerUserData( size_t& io_perPrim, size_t& io_perPoint, XGenMR::UserDataFormat& fmt, size_t& k, int* interpolateComponent );
		inline void registerUserData( size_t& io_perPrim, size_t& io_perPoint, XGenMR::UserDataFormat& fmt );

		inline void copy( size_t j, float* cur, size_t& k, int context );

		inline bool isValid( int context );

	private:
		int			m_context;
		bool		m_add;
		const T*	m_p;
		std::string m_name;
	};

	typedef TUserData<int,PrimitiveCache::EIntArrayAttribute,1> TIntUserData;
	typedef TUserData<float,PrimitiveCache::EFloatArrayAttribute,1> TFloatUserData;
	typedef TUserData<vec3,PrimitiveCache::EVec3ArrayAttribute,3> TVec3UserData;

	typedef std::vector< TIntUserData >		TIntUserDataList;
	typedef std::vector< TFloatUserData >	TFloatUserDataList;
	typedef std::vector< TVec3UserData >	TVec3UserDataList;

	// Holds the 3 user data type lists + some helper functions.
	class UserDataList
	{
	public:
		// Iterate over all the user data vectors
		inline void pushUserData( size_t j, float* cur, size_t& k, int context );

		// List all the optional built in user data
		inline void listUserData( PrimitiveCache* pc, bool isSpline );

		// Register Hair Optional User Data
		inline void registerUserData( size_t& io_perPrim, size_t& io_perPoint, XGenMR::UserDataFormat& fmt, size_t& k, int* interpolateComponent );

		// Register Hair Optional User Data
		inline void registerUserData( size_t& io_perPrim, size_t& io_perPoint, XGenMR::UserDataFormat& fmt );

	private:
		TIntUserDataList vecInt;
		TFloatUserDataList vecFloat;
		TVec3UserDataList vecVec3;
	};


    
    class UserDataList; // forward declaration
    class Procedural; // forward declaration

    class BaseProcedural : public ProceduralCallbacks
    {
    public:
        BaseProcedural( const BaseProcedural* parentProc );
        virtual ~BaseProcedural();

        virtual bool get( EBoolAttribute ) const;
        virtual const char* get( EStringAttribute ) const;
        virtual float get( EFloatAttribute ) const;
        virtual const float* get( EFloatArrayAttribute ) const;
        virtual unsigned int getSize( EFloatArrayAttribute ) const;

        virtual const char* getOverride( const char* in_name ) const;
        virtual bool getArchiveBoundingBox( const char* in_filename, bbox& out_bbox ) const;
        virtual void getTransform( float in_time, mat44& out_mat ) const;

    private:
        BaseProcedural( );
        static void convertMatrix( const miMatrix in_mat, mat44& out_mat );

    protected:

        // We'll put all the attributes from user/options/camera in a param map.
        // The Parameters maps are initialized a single time in init().
        class Param
        {
        public:
            ~Param()
            {
                if( isString() )
                {
                    std::string* p = (std::string*)m_p;
                    delete p;
                }
                else if( isFloat() )
                {
                    float* p = (float*)m_p;
                    delete p;
                }
                else if( isFloatArray() )
                {
                    std::vector<float>* p = (std::vector<float>*)m_p;
                    delete p;
                }
                else if( isMatrixArray() )
                {
                    std::vector<miMatrixStruct>* p = (std::vector<miMatrixStruct>*)m_p;
                    delete p;
                }
                m_p = NULL;
            }
            Param( const std::string& in_str )
            {
                m_t = eString;
                std::string* s = new std::string;
                *s = in_str;
                m_p = (void*)s;
            }
            bool isString() { return m_t==eString; }
            std::string* asString()
            {
                return m_t==eString ? (std::string*)m_p : NULL;
            }

            Param( float in_f )
            {
                m_t = eFloat;
                float* f = new float;
                *f = in_f;
                m_p = (void*)f;
            }
            bool isFloat() { return m_t==eFloat; }
            float* asFloat()
            {
                return m_t==eFloat ? (float*)m_p : NULL;
            }

            Param( const std::vector<float>& in_f )
            {
                m_t = eFloatArray;
                std::vector<float>* f = new std::vector<float>;
                *f = in_f;
                m_p = (void*)f;
            }
            bool isFloatArray() { return m_t==eFloatArray; }
            std::vector<float>* asFloatArray()
            {
                return m_t==eFloatArray ? (std::vector<float>*)m_p : NULL;
            }

            Param( const std::vector<miMatrixStruct>& in_m )
            {
                m_t = eMatrixArray;
                std::vector<miMatrixStruct>* m = new std::vector<miMatrixStruct>;
                *m = in_m;
                m_p = (void*)m;
            }
            bool isMatrixArray() { return m_t==eMatrixArray; }
            std::vector<miMatrixStruct>* asMatrixArray()
            {
                return m_t==eMatrixArray ? (std::vector<miMatrixStruct>*)m_p : NULL;
            }
        private:
            enum ParamType
            {
                eString, eFloat, eFloatArray, eMatrixArray
            };
            ParamType m_t;
            void* m_p;
        };
        typedef std::map<std::string,Param*> ParamMap;
        ParamMap m_user, m_overrides;

        bool getString( const ParamMap& in_params, const char* in_name, const char*& out_value, bool in_user=false ) const;
        bool getFloat( const ParamMap& in_params, const char* in_name, float& out_value, bool in_user=false  ) const;

        unsigned int getArraySize( const ParamMap& in_params, const char* in_name, int in_eType, bool in_user=false  ) const;
        bool getFloatArray( const ParamMap& in_params, const char* in_name, const float*& out_value, bool in_user=false  ) const;
        bool getMatrixArray( const ParamMap& in_params, const char* in_name, const miMatrix*& out_value, bool in_user=false  ) const;
    };

    // A face renderer is created by enumerating the Faces on the PatchRenderer.
    // It will take a snapshot of the state of the PatchRenderer.
    // It will also inherit the xgen args and ProceduralCallbacks from the patch.
    /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    class CountHairDataProcedural : public BaseProcedural
    {
    public:

        CountHairDataProcedural( const Procedural& parentProc );
        virtual ~CountHairDataProcedural() {}

        // static function for initialization
        static bool initFaceRenderer(CountHairDataProcedural* dProc, 
            PatchRenderer* patch, unsigned int f);

		// XGenRenderAPI::ProceduralCallbacks
		virtual void flush(  const char* in_geom, PrimitiveCache* in_cache );
		virtual void log( const char* in_str ){}

        void render() { m_face->render();}

        inline unsigned int getNumPrims() const { return m_numPrims;}
        inline unsigned int getNumPoints() const { return m_numPoints;}

    private:
        CountHairDataProcedural();
        CountHairDataProcedural( const CountHairDataProcedural& );

        std::auto_ptr<FaceRenderer> m_face;

        // approximation settings
		miInteger m_approx_degree;
		miInteger m_approx_mode;
		miInteger m_approx_parametric_subdivisions;
		miScalar  m_approx_fine_sub_pixel_size;

        unsigned int m_numPrims;
        unsigned int m_numPoints;
    };

       // A face renderer is created by enumerating the Faces on the PatchRenderer.
    // It will take a snapshot of the state of the PatchRenderer.
    // It will also inherit the xgen args and ProceduralCallbacks from the patch.
    /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    class InitHairDataProcedural : public BaseProcedural
    {
    public:

        InitHairDataProcedural( const Procedural& parentProc, UserDataList* pUserData, miHair_list* pHair);
        virtual ~InitHairDataProcedural() {}

        // static function for initialization
        static bool initFaceRenderer(InitHairDataProcedural* dProc, 
            PatchRenderer* patch, unsigned int f);

		// XGenRenderAPI::ProceduralCallbacks
		virtual void flush(  const char* in_geom, PrimitiveCache* in_cache );
		virtual void log( const char* in_str ){}

        void render() { m_face->render();}

        inline bool isValid() { return m_numInterpolateComponents != -1;}

        inline miGeoIndex getNumScalarsPerPoint() const { return m_numScalarsPerPoint;}
        inline miGeoIndex getNumScalarsPerPrim() const { return m_numScalarsPerPrim;}
        inline miGeoIndex getNumInterpolateComponents() const { return m_numInterpolateComponents;} 
        inline const std::string& getUserDataStrFormat() const { return m_userDataStrFormat;}

    private:
        InitHairDataProcedural();
        InitHairDataProcedural( const InitHairDataProcedural& );

        std::auto_ptr<FaceRenderer> m_face;

        // approximation settings
		miInteger m_approx_degree;
		miInteger m_approx_mode;
		miInteger m_approx_parametric_subdivisions;
		miScalar  m_approx_fine_sub_pixel_size;

        // this user data list will be initialized
        UserDataList* m_pUserData;
        std::string m_userDataStrFormat;
        miHair_list* m_pHair;

        // data layout numbers
        miGeoIndex m_numScalarsPerPoint;
        miGeoIndex m_numScalarsPerPrim;
        miGeoIndex m_numInterpolateComponents;
    };

    class FlushHairDataProcedural : public BaseProcedural
    {
    public:

        FlushHairDataProcedural(
            const Procedural& parentProc,
            miGeoIndex* pHairIndices,
            miScalar* pHairScalars,
            UserDataList* pUserData,
            miGeoIndex numScalarsPerPoint,
            miGeoIndex numScalarsPerPrim,
            miGeoIndex numInterpolateComponents,
            miGeoIndex numScalarsTotal,
            miGeoIndex numIndicesTotal);

        virtual ~FlushHairDataProcedural() {}

        // static function for initialization
        static bool initFaceRenderer(FlushHairDataProcedural* dProc, 
            PatchRenderer* patch, unsigned int f);

		// XGenRenderAPI::ProceduralCallbacks
		virtual void flush(  const char* in_geom, PrimitiveCache* in_cache );
		virtual void log( const char* in_str ){}

        void render() { m_face->render();}

    private:
        FlushHairDataProcedural();
        FlushHairDataProcedural( const FlushHairDataProcedural& );

        std::auto_ptr<FaceRenderer> m_face;
        
        // approximation settings
		miInteger m_approx_degree;
		miInteger m_approx_mode;
		miInteger m_approx_parametric_subdivisions;
		miScalar  m_approx_fine_sub_pixel_size;
        miScalar  m_motion_blur_multiplier;
        
        // buffers to write the data to
        miGeoIndex* m_pHairIndices;
        miScalar* m_pHairScalars;
        UserDataList* m_pUserData;

        // data layout sizes
        miGeoIndex m_numScalarsPerPoint;
        miGeoIndex m_numScalarsPerPrim;
        miGeoIndex m_numInterpolateComponents;
        // Debug: compute offsets into the buffers to assert total size
        miGeoIndex m_hairScalarsOffset;
        miGeoIndex m_hairIndicesOffset;
        miGeoIndex m_numScalarsTotal;
        miGeoIndex m_numIndicesTotal;
    };


	class Procedural : public BaseProcedural
	{
	public:
		friend class ProceduralWrapper;
		friend class FlushHairDataProcedural;
        friend class InitHairDataProcedural;
        friend class CountHairDataProcedural;

		struct Params
		{
			miString data;		// Procedural Arguments

			// User Attributes Arguments. We define everything on the geoshader node for now.
			// All their content is converted into ParamMap, in init();
			// In other renderers, most are defined as user data on options, camera and the procedural.
			miString user; 		// User RiAttribute on the geoshader
			miString overrides; // User overrides: length "0.0" width "0.0"
			miScalar frame;
			miString patches;	// List of patches

			// Echo params
			miBoolean	echo;					// Echo the whole geoshader content to an mi file. The echo_* parameters fills the miEchoOptions struct.
			miString	echo_filename;			// Filename where to echo.
			miBoolean	echo_ascii;             // non-binary output
			miInteger	echo_explode_objects;   // write objects to subfiles
			miBoolean	echo_verbatim_textures; // dump textures verbatim ?
			miInteger	echo_dont;              // EO_* bitmap: omit these
			miInteger	echo_dont_recurse;      // EO_* bitmap: no prereqs

			// Approx params
			miInteger approx_degree;
			miInteger approx_mode;
			miInteger approx_parametric_subdivisions;
			miScalar  approx_fine_sub_pixel_size;

			// Motion Blur params
			miBoolean motion_blur;
			miInteger motion_blur_mode;
			miInteger motion_blur_steps;
			miScalar  motion_blur_factor;
			miScalar  motion_blur_multiplier;
		};

		Procedural();
		virtual ~Procedural();

		// MR Entry points.
		void init(miState* state, Params* paras, miBoolean *inst_init_req );
		void exit( miState* state, Params* paras );
		miBoolean execute( miTag* result, miState* state, Params* paras );

		// Export geoshader content to disk.
		void echo( miTag* result, miState* state, Params* paras );

		// XGenRenderAPI::ProceduralCallbacks
		virtual void flush(  const char* in_geom, PrimitiveCache* in_cache );
		virtual void log( const char* in_str ){}

		// Render is called from the assembly callback function.
		bool render( miTag* result, const miState* state );

        // Helper function for transfering data from placeholder callbacks
        // to flushSplines and vice versa
        void setPlaceholderObjectTag(const miTag tag) { m_tagPlaceholderObject = tag;}
        miTag getUserDataTag() { return m_tagUserData;}

	private:

		// These 4 methods are protected by the global mutex.
		bool nextFace( bbox& b, unsigned int& f );
		bool initPatchRenderer( const char* in_params );
		bool initFaceRenderer( Procedural* pProc, unsigned int f );

		// init a new procedural per patch.
		bool initPatchProcedural( Procedural* pParent, const std::string& strPatch );
        
        // Helper function for patch-render callback for splines/hair.
		bool renderHairObject( miTag* result, const miState* state );
        // write out the hair object header
		miHair_list* beginHairObject( );
		void endHairObject( unsigned int numScalarsTotal, const std::string& strFormat );

		void flushSplines( const char *geomName, PrimitiveCache* pc );
		void flushCards( const char *geomName, PrimitiveCache* pc );
		void flushSpheres( const char *geomName, PrimitiveCache* pc );
		void flushArchives( const char *geomName, PrimitiveCache* pc );
		void syncArchives( const char *geomName, PrimitiveCache* pc );

		//static void pushCustomParams( AtNode* in_node, PrimitiveCache* pc );

		const char* getUniqueName( char* buf, const char* basename );

		miTag m_node;

		typedef std::vector<Procedural*> TProcList;
		TProcList m_patches;
		PatchRenderer* m_patch;
		std::string m_patchName;
		FaceRenderer* m_face;

		std::string m_data;

		bool getString( const ParamMap& in_params, const char* in_name, const char*& out_value, bool in_user=false ) const;
		bool getFloat( const ParamMap& in_params, const char* in_name, float& out_value, bool in_user=false  ) const;

		unsigned int getArraySize( const ParamMap& in_params, const char* in_name, int in_eType, bool in_user=false  ) const;
		bool getFloatArray( const ParamMap& in_params, const char* in_name, const float*& out_value, bool in_user=false  ) const;
		bool getMatrixArray( const ParamMap& in_params, const char* in_name, const miMatrix*& out_value, bool in_user=false  ) const;


		void initInstanceFlags( miInstance* inst, bool in_visible );
		void initInstanceTransforms( miInstance* inst, const std::vector<miMatrixClass>& matrices );
		void listInstanceUserData( PrimitiveCache* pc, UserDataList& out_ud, std::string& out_strFormat, std::vector<float>& out_scalars );
		miTag createUserData(bool in_bIsSpline, const std::string& in_dataName, const std::string& in_strFormat, const std::vector<float>* in_pScalars=NULL );
		void initInstanceUserData(miInstance* io_pInst, bool in_bIsSpline, const std::string& in_dataName, const std::string& in_strFormat, const std::vector<float>* in_pScalars=NULL );
		miTag makeArchiveInstanceGroup( PrimitiveCache* pc, const std::string& instanceName, const std::string& instanceGroupName, const std::string& filename, const std::string& select, const std::string& material, miScalar frame, miInteger assembly );
		void evalMatrix( miMatrixClass& matrix, mat44* out_xP, mat44* out_xN, const vec3* points_i, unsigned int j, double& prevAngle2, double bbox_scale, bool normalParam );

		const miState* m_state;
		miTag m_result;
		miTag m_sphere;
		miTag m_dummy;
        miTag m_tagUserData;
		std::string m_parentName;  
		std::string m_parentNameNoFace;
		std::string m_primType;
		std::vector<miTag> m_tags;
		std::vector<miTag> m_tagsHiddenGroup;
        
        miTag m_tagPlaceholderObject;
        miGeoIndex m_numHairPoints;
        miGeoIndex m_numHairPrims;

		std::map<std::string,std::string>* m_archives;

		bool m_bSyncArchives;
		bool m_bPerFaceAssemblies;
		bool m_bPerPatchHairObjects;
		bool m_bEcho;
		
		miInteger m_approx_degree;
		miInteger m_approx_mode;
		miInteger m_approx_parametric_subdivisions;
		miScalar  m_approx_fine_sub_pixel_size;

		bool m_motion_blur;
		miInteger m_motion_blur_mode;
		miInteger m_motion_blur_steps;
		miScalar  m_motion_blur_factor;
		miScalar  m_motion_blur_multiplier;
	};
};

#endif

