#pragma once

#include "AftrOpenGLIncludes.h"
#include "AftrGlobals.h"
#include <vector>
#include <string>
#include <memory>
#include "ModelMeshRenderDataGenerator.h"
#include "ModelMeshRenderData.h"

namespace Aftr
{

class ModelMeshSkin;



/**
   This class encapsulates all the information necessary to render the underlying
   geometry of a mesh. The associated skins (materials, textures, shaders,etc) are
   expected to already be bound before this mesh's render is invoked. This geometry
   is rendered using either flat shading (one normal per face) or smooth shading
   (one normal per vertex).

   HOW TO USE THIS CLASS / OVERVIEW:
   BLUF: This class stores the OpenGL handles to draw exactly one mesh that has
   three possible variants of GL_PRIM_TYPE, MESH_SHADING_TYPE (affects 
   normal/topology generation), and MESH_RENDER_TYPE (VBO or VA).
   Very commonly, a mesh will have only one triplet of these options (such 
   as GL_TRIANGLES, mstAUTO, and VBO). In this case a call to renderGL*(...) will
   enqueue that data to be drawn by the GL. If desired, multiple combinations
   of the aforementioned triplets can be generated and drawn via the appropriate 
   ModelMeshSkin which stores the GL_PRIM_TYPE, MESH_SHADING_TYPE, and MESH_RENDER_TYPE.

   This class is generated via a ModelParser (like ASSIMP, or 3ds parser, or obj parser,
   or the ancient wrl parser). However, *there are TWO ways* parsers may use this class
   to create a valid MMDS that is drawn by GL.
   
   1) The first way is to use the default
   constructor OR the constructor consuming a ModelMeshRenderDataGenerator. Using this
   approach will, at the point where sent to GL, invoke this->generateRenderData() which
   will use the vertex and index information (but not normals) to generate normals based on the 
   shading type. This is great for files that do not provide normals.
   
   
   2) Some parsers and file formats are more sophisticated and will provide their own
   normals (as well as things like tex coords, verts, indicies, tangents, generic
   attributes, etc). In this case, the parser (such as ASSIMP) will have to directly
   create the ModelMeshRenderData as it parses the file. Since the parser has beautifully
   crafted the MMRD, there is no need to make a generator that will build one that may not
   be exactly what the parser intended. In this case, use the third constructor that
   consumes a ModelMeshRenderData directly as well as a shading type, and prim type.
   Subsequently, at the point where sent to GL, this will inovke this->



   Upon invocation of generateRenderData(...), the generated shading type is determined
   by the corresponding skin's shading type.
   If the skin's shadingType is changed later on, this method's
   generateRenderData(...) must be reinvoked passing in the new shadingType of either
   MESH_SHADING_TYPE::mstFLAT or MESH_SHADING_TYPE::mstSMOOTH.
   
   Internally, both the flat shaded data and smooth shaded data may exist concurrently
   if both shading types have been generated via generateRenderData(...). However, the
   default behavior only loads the skin's default shading type; this saves memory on
   both the graphics card and CPU-side.

   All meshes are rendered via OpenGL using indexed geometry objects; namely,
   Vertex Buffer Objects (VBOs) or Vertex Arrays (VAs). If the current
   machine supports VBOs, all non-deformable geometry is placed in a VBO and
   rendered via a Vertex Array encapsulating the constituient VBO buffers;
   this sends all data to the graphics card exactly once at load time. Any 
   rendering of these objects is then performed on graphics
   hardware limited only by the graphics hardware. If the current machine does not
   support VAs/VBOs, or if the geometry is deformable, then VAs are used to perform
   the rendering. VAs send the data to the graphics card each render; however
   optimized drivers cache the VA data on the graphics card and only need to
   update the deformed geometry. Again, all rendering is done with indexed
   geometry; that is, no display lists, no immediate mode rendering.
*/
class ModelMeshDataShared
{
public:
   //When using this constructor or the constructor consuming a ModelMeshRenderDataGenerator,
   //this instance's generateRenderData(...) will be invoked to produce an internally owned
   //ModelMeshRenderData which will be placed into this instance's this->renderData. this->renderData is
   //an std::map which maps GL_PRIM_TYPE, MESH_SHADING_TYPE (affects normal/topology generation), and MESH_RENDER_TYPE (VBO or VA)
   //to a specific ModelMeshRenderData that can be drawn by OpenGL.
   ModelMeshDataShared();
   ///In passed renderDataGenerator's ownership is given to this instance.
   ModelMeshDataShared( std::unique_ptr<ModelMeshRenderDataGenerator > renderDataGenerator );

   ///This constructor consumes/takes ownership of the in passed ModelMeshRenderData* object. This is used when
   ///a generator isn't necessarily needed, but the in passed render data ought to be used as is. For example,
   ///The Asset Importer library (assimp) performs the primitive type conversion, shading operators, and
   ///normal generation that the ModelMeshRenderDataGenerator usually does.
   ///If assimp already does this, we don't want to redo that work.
   ///It's also likely that assimp does this conversion more efficiently and timely than the generator class.
   ///The user must also pass in the corresponding shading type of the contained render data (smooth or flat).
   ///The user must also pass in the OpenGL Primitive type of the rendered primitives, ie GL_TRIANGLES, 
   ///GL_TRIANGLE_STRIP, GL_POINTS, etc.
   /// 
   ///*When this constructor is called, we have to invoke ModelMeshDataShared::addModelMeshRenderData() 
   /// from within ManagerModelMultiplicity::sendModelDataSharedServerSide() call. This is in contrast to
   /// other parsers which invoke this class' default constructor and then invoke this->generateRenderData().
   /// When the default constructor is called, we have to invoke ModelMeshDataShared::generateRenderData() 
   /// from within ManagerModelMultiplicity::sendModelDataSharedServerSide().
   /// 
   /// The GenerateRenderData() variant of the method will also generate normals, shading information, etc.
   /// Using this constructor will not invoke generate and will let the user (parser, like ASSIMP) populate
   /// the normal array and shading information directly from the parser. So if the 3D model file has normal
   /// information, this constructor lets you directly use that instead of recomputing smooth or flat shading.
   ///
   ModelMeshDataShared( std::unique_ptr<ModelMeshRenderData> renderData, const MESH_SHADING_TYPE& mstType, GLenum glPrimType,
                        bool destroyCPUSideDataAfterSendingToGPU = true );

   virtual ~ModelMeshDataShared();

   /// Returns true iff a valid ModelMeshRenderData matching the in passed triplet of render keys was found
   bool has_sent_ModelMeshRenderData_to_ServerSide( MESH_SHADING_TYPE shadingType, GLenum glPrimType = GL_TRIANGLES ) const noexcept;
   
   ///Returns true only if a new ModelMeshRenderData was inserted into this instance internal renderDataMap AND successfully registered/sent
   ///to the Graphics API.
   /// This method will ensure the data passed into this MMDS is sent to OpenGL and properly instantianted. This includes the 3 constructors
   /// worth of input data -- either a ModelMeshRenderDataGenerator (default constructor or 2nd constructor) OR a ModelMeshRenderData
   /// directly built from a parser (such as ASSIMP) (3rd constructor).
   /// You may use this method instead of the lower level methods: generateRenderData(...) and addModelMeshRenderData(...)
   bool send_ModelMeshRenderData_to_ServerSide( MESH_SHADING_TYPE shadingType, GLenum glPrimType = GL_TRIANGLES, MESH_RENDER_TYPE renderType = MESH_RENDER_TYPE::mrtVBO );

   /**
      This method is invoked by the ManagerModelMultiplicity and uses the data
      from the in passed ModelMeshRenderDataGenerator* renderDataGenerator to construct the Indexed Geometry
      (VBO or VA), generate all necessary OpenGL handles, send the data to graphics
      memory, and everything else necessary to make this instance's render() method
      correctly draw the model.
   */
   virtual void generateRenderData( MESH_SHADING_TYPE shadingType, MESH_RENDER_TYPE renderType = MESH_RENDER_TYPE::mrtVBO, GLenum glPrimType = GL_TRIANGLES );
   /**
      This method is called manually when the underlying ModelMeshRenderData for a specific shading/render type is already generated, but the underlying generator
      has changed size, so mapping isn't sufficient.
   */
   virtual void regenerateRenderData(MESH_SHADING_TYPE shadingType, MESH_RENDER_TYPE renderType, GLenum glPrimType);
   virtual bool hasGeneratedRenderData( MESH_SHADING_TYPE shadingType, GLenum primType );
   std::string toString() const;
   virtual void render( ModelMeshSkin& skin );
   virtual void renderSelection( const ModelMeshSkin& skin, GLubyte red, GLubyte green, GLubyte blue );

   bool isUsingVBO( MESH_SHADING_TYPE shadingType, GLenum glPrimType );
   bool isUsingVA( MESH_SHADING_TYPE shadingType, GLenum glPrimType );

   ModelMeshRenderDataGenerator* getRenderDataGenerator();
   void setRenderDataGenerator( std::unique_ptr<ModelMeshRenderDataGenerator > g );

   ModelMeshRenderData* getModelMeshRenderData( MESH_SHADING_TYPE shadingType, GLenum primType );

   /// This method simply inserts the corresponding render data and shading info into this ModelMeshDataShared
   /// AND also SENDS the data data to OpenGL -- this MUST be called from the main thread which owns the
   /// OpenGL context. If you are calling from a worker thread, use the 3rd constructor to defer making any
   /// OpenGL calls (thread safe).
   /// 
   /// For example, the Asset Importer library (assimp) performs the primitive type conversion and shading 
   /// operators that the ModelMeshRenderDataGenerator usually does. If assimp already does this, we don't 
   /// want to redo that work.
   /// It's also likely that assimp does this conversion more efficiently and timely than the generator class.
   /// The user must also pass in the corresponding shading type of the contained render data (smooth or flat).
   /// The user must also pass in the OpenGL Primitive type of the rendered primitives, ie GL_TRIANGLES, 
   /// GL_TRIANGLE_STRIP, GL_POINTS, etc.
   /// 
   /// The in passed data becomes *owned* by this instanced.
   /// 
   /// Nothing will be usable by the Graphics API *until* after calling this method, the user/AftrBurner
   /// also invokes this->send_ModelDataShared_to_ServerSide(...). This invocation *must* be performed 
   /// on the same thread which created the OpenGL context or undefined behavior of OpenGL will occur.
   /// Typically, the ManagerModelMultiplicity::sendModelDataSharedServerSide() automatically takes care
   /// of this.
   /// 
   /// If an existing renderData is already associated with the corresponding shadingType & glPrimType, the
   /// bool overwriteExistingMMRD is used to determine if the existing renderData will be destroyed and replaced
   /// with the in pass renderData. If overwriteExistingMMRD is true, the existing renderData will be replaced,
   /// otherwise it will not be replaced.
   /// If destroyCPUSideDataAfterSendingToGPU is true, the CPU's copy of the render data (vertex info, index info, etc) will
   /// be deleted once a GPU-side copy has been made. Deleting it is free CPU-side memory, but the CPU may lost quick
   /// access to the total set of vertices which may want to be used by the physics engine at a later time.
   bool addModelMeshRenderData( ModelMeshRenderData* renderData,
                                const MESH_SHADING_TYPE& shadingType, GLenum glPrimType,
                                bool overwriteExistingMMRD = true, bool destroyCPUSideDataAfterSendingToGPU = true );

   //modifies the underlying model mesh's modelMeshRenderData to re-sample the vertices
   //currently only works for points, re-sampling techniques should be possible for other primitive shapes
   //but may not make sense for all models (under sampling triangles will leave gaps in the model).
   //Currently uses the currently selected skin's prim-type, render-type pair, could be extended to modify
   //a specified pairing.
   //Factor is the under sampling factor relative to the ORIGINAL data (easy enough for sequential data, may
   //take more thought for other types, i.e. passing in 2 will give you half as many original points,
   //followed by a subsequent call of 3 will give you one third as many points, not one sixth.
   void underSampleMeshWithFactor( int factor, const ModelMeshSkin& skin );

protected:

   void initMemberData( std::unique_ptr<ModelMeshRenderDataGenerator > renderDataGenerator );
   void sendDataServerSide( ModelMeshRenderData& r );
   void sendNormalDataServerSide(ModelMeshRenderData& r);///< For rendering normals in a 3.2+ core compliant manner, but currently not called in dev branch(SLN)

   /// Saves the in-passed ModelMeshRenderData and corresponding parameters and will send them to OpenGL on the 
   /// next invocation of send_ModelMeshDataShared_to_ServerSide(...). This let's a worker thread create the MMRD but
   /// the main thread owning the GL context to actually send to GL via send_ModelMeshDataShared_to_ServerSide(...).
   void prepareToSend_MMRD( ModelMeshRenderData* mmrd, MESH_SHADING_TYPE shadingType, MESH_RENDER_TYPE renderType, GLenum glPrimType );

   virtual void render( ModelMeshSkin& skin, ModelMeshRenderData& r );
   virtual void renderMeshCompatibility( ModelMeshSkin& skin, const ModelMeshRenderData& r );
   virtual void renderMeshGL32( ModelMeshSkin& skin, const ModelMeshRenderData& r );
   virtual void renderNormals(  ModelMeshSkin& skin, ModelMeshRenderData& r );
   virtual void renderNormals32(ModelMeshSkin& skin, ModelMeshRenderData& r);
   virtual void renderSelection( const ModelMeshSkin& skin, const ModelMeshRenderData& r, GLubyte red, GLubyte green, GLubyte blue );
   virtual void renderMeshSelectionCompatibility( const ModelMeshSkin& skin, const ModelMeshRenderData& r, GLubyte red, GLubyte green, GLubyte blue );
   virtual void renderMeshSelectionGL32( const ModelMeshSkin& skin, const ModelMeshRenderData& r );

   ///Map of shading type and GL Primitive Type: (GL_TRIANGLES, GL_POINTS, GL_TRIANGLE_STRIP, etc
   std::map< std::pair< MESH_SHADING_TYPE , GLenum >, ModelMeshRenderData* > renderDataMap;

   std::unique_ptr<ModelMeshRenderDataGenerator > renderDataGenerator = nullptr; ///< Mesh data populated from parser

   //Used by the 3rd constructor or the addModelMeshRenderData(...) method
   struct MMRD_to_send
   {
      std::unique_ptr<ModelMeshRenderData> mmrd     = nullptr;
      bool destroyCPUSideDataAfterSendingToGPU = false;
      MESH_SHADING_TYPE shadingType = MESH_SHADING_TYPE::mstAUTO;
      GLenum glPrimType             = GL_TRIANGLES;
      MESH_RENDER_TYPE renderType   = MESH_RENDER_TYPE::mrtVBO;
   };
   std::optional< MMRD_to_send > mmrd_to_send_to_gpu; ///< 3rd constructor will populate this member. Subsequent 
                                 //call to send_ModelDataShared_to_ServerSide(...) will invoke OpenGL functions
                                 //send to GPU memory / register w/ OpenGL.
};

}

