#pragma once

#include <vector>
#include "Vector.h"
#include "ModelDataSharedID.h"
#include "BoundingBox.h"
#include "AftrBimap.h"
#include "Mat4Fwd.h"

namespace Aftr
{

class ModelMesh;

/**
   Data that defines a Model is divided into two parts - 1) per instance data and 2)
   shared data. Per instance data represents data that is specific to each instance
   of a model; this data does not make sense to share between two models. For example,
   the position and orientation of two models should be independent of each other -
   manipulating one should not manipulate the other. Furthermore, material 
   properties, textures, and shaders should not be forced to be shared amongst 
   several instances of the same model.

   However, a large part of the data makes sense to share with all instances of
   a given model. For example, the geometry/topology that defines the model should
   be placed in memory only once and shared. The same goes for Textures and shaders.

   Instances of this class are maintained by the ManagerModelMultiplicity. This
   class encapsulates data that is shared and/or duplicated across multiple instances
   of the same model. This class contains a set of meshes that define this model. Each
   mesh is composed of a skin and rendering data. This class also contains pointers to
   a composite list of all the indicies and verticies comprising the geometry of this
   instance.
*/
class ModelDataShared
{
public:
   ModelDataShared();
   /**
      This construction creates a copy of each ModelMesh within this vector. As a result,
      this object does not take ownership of any objects contained within the in passed
      vector. The user is still responsible for destroying the data inside of the in
      passed vector.  The order of the meshes passed into this constructor is preserved,
      so any object wishing to manipulate these meshes outisde this class may maintain
      their own list of indices based on those passed into this constructor; however,
      pointers to the original meshes will not work since this object contains copies.

      This constructor is called by a parser exactly once for each type of model. This
      is where the composite vertex and index lists get new'd and populated. All
      subsequent Models utilizing this ModelDataShared object share the same composite
      vertex and index lists.
   */
   ModelDataShared( const std::vector< ModelMesh* >& meshes );
   ModelDataShared( const ModelDataShared& toCopy );
   ModelDataShared( std::vector< ModelMesh* >&& meshes );
   ModelDataShared( ModelDataShared&& meshes ) noexcept;
   /**
      This method is invoked by the ManagerModelMultiplicity::sendModelDataSharedServerSide( ModelDataShared* m ).
      This method is invoked after all contained Meshes of this instance have had their ModelMeshRenderData 
      generated and sent server side either as a VA or VBO or VA/VBO.
   */
   virtual void onAllContainedMeshesHaveBeenSentServerSide();
   virtual ~ModelDataShared();
   ModelDataShared& operator =( const ModelDataShared& m );
   void setModelDataSharedID( const ModelDataSharedID& id );
   virtual void render( const Mat4& modelMatrix, const Mat4& normalMatrix, const Camera& cam );
   virtual void renderSelection( const Mat4& modelMatrix, const Mat4& normalMatrix, const Camera& cam, GLubyte red, GLubyte green, GLubyte blue );
   std::vector< ModelMesh* >& getModelMeshes() { return this->meshes; }
   const std::vector< ModelMesh* >& getModelMeshes() const noexcept { return this->meshes; }
   BoundingBox& getBoundingBox() { return this->bBox; }
   const BoundingBox& getBoundingBox() const noexcept { return this->bBox; }
   //const std::vector< Vector >& getCompositeVertexList() { return this->compositeVertexList; }
   const std::vector< Vector >& getCompositeVertexList() const noexcept { return this->compositeVertexList; }
   //const std::vector< unsigned int >& getCompositeIndexList() { return this->compositeIndexList; }
   const std::vector< unsigned int >& getCompositeIndexList() const noexcept { return this->compositeIndexList; }
   const std::vector< Vector >& getCompositeTangentList() const noexcept { return this->compositeTangentList; }
   const std::string& getFileName() const noexcept { return this->id.getFileName(); }
   const Vector& getInitialScaleFactor() const noexcept { return this->id.getScaleFactor(); }
   AftrBimap< std::pair< size_t, size_t >, std::pair< size_t, size_t > >& getIndexAlias() { return indexAlias; }

   /**
      Called by ManagerModelMultiplicity::destroyModelSharedData(...) when all instances
      of this ModelDataShared are destroyed. Since this composite list is shared amongst all
      instances, the destructor for this class does not destroy the composite index, vertex,
      tangent, etc lists - this is the responsibility of the ManagerModelMultiplicity.
   */
   void destroyCompositeLists();

   virtual void generateCompositeGenericAttributeLists();

   /**
      Maps the compositeIndex i to the mesh index and vertex entry index within the mesh's VBO or VA.
      For example, mapCompositeIndexToSubMeshIDandIndex[ this->compositeIndexList[1] ] = (0,238).
      
      Above, the user selected the 1st index in the composite index list (hence [1]). The 
      mapCompositeIndexToSubMeshIDandIndex then returns a tuple denoting which meshIdx and vertex Entry
      index corresponds to that index. In this example, ModelDataShared::mesh[0] at VBO / VA index 238
      corresponds to the 1st index in the compositeIndexList.
   */
   std::map< unsigned int, std::pair< unsigned int, unsigned int > >& getMapCompositeIndexToSubMeshIndex() { return this->mapCompositeIndexToSubMeshIDandIndex; }

protected:

   virtual void generateCompositeTangentList();
   virtual void generateCompositeQuadricList();

   void initMemberData();
   void generate_composite_vertex_and_index_lists_and_bbox_dims();

   /**
      Unique identifier used by ManagerModelMultiplicity. This is managed (created and
      destroyed) by the ManagerModelMultiplicity, the user should not modify this object.
   */
   ModelDataSharedID id;

   BoundingBox bBox; ///< The cumulative bounding box for this ModelDataShared

   /**
      A collection of all the submeshes that define this model's appearance. Each ModelMesh
      contains a ModelMeshSkin (controls textures/mutli-textures/shaders/material properties/
      flat or smooth shading) and a ModelMeshDataShared (contains all relevant OpenGL data
      used to render the submesh to the display (efficient handling of VBOs/VAs based on
      machine-specific supported hardware)).

      This vector is owned by this specific instance. The destructors for the ModelMesh,
      however, do not destroy the shared data (such as the OpenGL VBO/VA geometry) only
      instance-specific data is destroyed (such as the Skin properties).
   */
   std::vector< ModelMesh* > meshes;

   /**
      A collection of all the unique vertices contained across all submeshes defining this
      model. This data is stored once in memory and managed via the ManagerModelMultiplicity.
      This data is used by the physics engine as well as Aftr utilities such as the 
      ray-casting functions / octree optimizations, etc. Normally, the user does not need to
      modify this list. This list is populated once on construction, changing this list does
      not affect the OpenGL geometry that is rendered nor will it affect the Physics Engine's
      collision detection if the Physics Engine has already been instantiated. For a model
      loaded from file (.wrl, .3ds, .lwo, etc...) This list is a copy of the vertex data
      originally parsed and stored in the ModelMeshRenderDataGenerator class.
      populated by the parser.
   */
   std::vector< Vector > compositeVertexList; // perhaps should become std::shared_ptr<>

   /**
      The set of indicies into this this object's compositeVertexList.
   */
   std::vector< unsigned int > compositeIndexList; // perhaps should become std::shared_ptr<>

   /**
      Maps the compositeIndex i to the mesh index and vertex entry index within the mesh's VBO or VA.
      For example, mapCompositeIndexToSubMeshIDandIndex[ this->compositeIndexList[1] ] = (0,238).
      
      Above, the user selected the 1st index in the composite index list (hence [1]). The 
      mapCompositeIndexToSubMeshIDandIndex then returns a tuple denoting which meshIdx and vertex Entry
      index corresponds to that index. In this example, ModelDataShared::mesh[0] at VBO / VA index 238
      corresponds to the 1st index in the compositeIndexList.
   */
   std::map< unsigned int, std::pair< unsigned int, unsigned int > > mapCompositeIndexToSubMeshIDandIndex;
   
   /**
      A collection of each vertices's corresponding tangent vector (tangent attribute) if
      tangents were generated during construction.
   */
   std::vector< Vector > compositeTangentList;

   /**
      Stores a bidirectional mapping between the original pair of 
      mesh index / vertex index onto the  current 
      mesh index / vertex index corresponding to the current
      location of that vertex. The original pair are those which are
      passed into the constructor for this ModelDataShared instance.

      For example, if Vertex v was originally in this->meshes.at( 0 ) at 
      index 13, but was then reloacted for some reason, 
      such as a mesh splitting process (for frustum acceleration), 
      to this->meshes.at( 28 ) at index 456
      bidirectional map would contain:
      ( 0, 13 ) <--> ( 28, 456 )

      If this map's size is greater than zero, it implies the original
      verticies have been reorganized. The left hand side (lhs) of the bimap
      contains one entry for each original position of a given vertex; 
      the right hand side (rhs) of the bimap contains one entry for 
      the current position of a given vertex.  When manipulating VA/VBO 
      data, ensure the current position is being used, not the original.
   */
   AftrBimap< std::pair< size_t, size_t >, std::pair< size_t, size_t > > indexAlias;
};

} //namespace Aftr

