//----------------------------------------------------------------------------
//Description: The BoundingBox class is a helper class for the Model class.
//The BoundingBox is used to find the two extreme verticies contained within
//a Model.  The midpoint between these two points represents the "center" of
//the Model.  This "center" is also know as the Model's fixedPoint.  This is
//the point about which all scalings and rotations occur.
//
//Author: Scott Nykl
//----------------------------------------------------------------------------

#pragma once

#include "Vector.h"
#include "AftrOpenGLIncludes.h"
#include "Mat4Fwd.h"
#include <span>
#include <memory>

//----------------------------------------------------------------------------
//Description: The BoundingBox class is a helper class for the Model class.
//The BoundingBox is used to find the two extreme verticies contained within
//a Model.  The midpoint between these two points represents the "center" of
//the Model.  This "center" is also know as the Model's fixedPoint.  This is
//the point about which all scalings and rotations occur.  The smallest X, Y,
//and Z components are found and create a min Vector.  The largest X, Y, and 
//Z components are also found and create a max Vector.
//----------------------------------------------------------------------------
namespace Aftr
{

class IndexedGeometryRectangularPrism;

class BoundingBox
{
public:
   /**
      Constructor that instantiates a new BoundingBox.
   */
   BoundingBox();
   BoundingBox( const BoundingBox& toCopy );
   BoundingBox( BoundingBox&& toMove ) noexcept;

   /**
      Destructor that deletes and NULL-ifys all pointers owned by this 
      instance.
   */
   ~BoundingBox();

   BoundingBox& operator=( const BoundingBox& box );
   BoundingBox& operator=( BoundingBox&& box ) noexcept;

   void render( const Camera& cam, const Mat4& modelMatrix );
   void render( const Mat4& MVPMatrix );
   void renderSelection( const Camera& cam, GLubyte red, GLubyte green, GLubyte blue );

   /**
      This method takes in a vertex and checks to see if any of its X, Y, or Z
      components are an extreme min or extreme max. None of these methods reset
      previous BBox mins and maxes. If you are trying to completely overwrite
      all previously inspects verts, call setBBoxExtrema
   */
   void updateBBox( const Vector* p );
   void updateBBox( const Vector& p );
   void updateBBox( float const *const xyzVertArray, const size_t numVerts );
   void updateBBox( const std::vector<Vector>& pts );
   void updateBBox( std::forward_iterator auto const& beginIter, std::forward_iterator auto const& endIter );
   void updateBBox( std::span<const Vector> verts );
   void updateBBox();

   //Instead of updating one or several points and comparing each for a min/max value
   //of the current bbox, this method overwrites all previous min/max values and uses
   //only the inpassed points to find a new min/max. This is similar to setlxlylz, but
   //using only a set of verts.
   void setBBoxExtrema( const std::vector<Vector>& pts );

   std::string toString() const;

   /**
      This method returns the current minimum of all components of each Vector
      that has been processed; that is, the smallest X, Y, and Z found are 
      returned.
      \return Vector* : A Vector pointer to the minimum.
   */
   Vector getMin() const { return this->min; }

   /**
      This method returns the current maximum of all components of each Vector
      that has been processed; that is, the largest X, Y, and Z found are 
      returned.
      RETURN: Vector* : A Vector pointer to the maximum.
   */
   Vector getMax() const { return this->max; }

   Vector getCenterPoint() const { return( (this->max + this->min) / 2.0f ); }

   /**
      This method returns a Vector containing the dimensions of the model.
      \return Vector: x component: lx( max.x - min.x )
      Vector: y component: ly( max.y - min.y )
      Vector: z component: lz( max.z - min.z )
   */
   Vector getlxlylz() const;

   /**
      This method allows the user to specify his own dimension of the bounding
      box for this model. This is useful when a user wants only part of a model
      or wants a larger part of a model to be taken into account when ODE
      initializes the collision volumes for each WO.
      Note, this does NOT modify the actual size of the model object, it
      only changes the dimensions of the stored bounding box. Thus when the 
      WO in created in ODE, the collision volume will be the size 
      specified within the bounding box.
      PARAM: Vector& v The new length of this model's bounding box.
      v.x = new length of x component
      v.y = new length of y component
      v.z = new length of z component
   */
   void setlxlylz( const Vector& v, Vector centerPoint = Vector(0,0,0) );

   /** 
      Takes as input an array of 8 Aftr::Vectors. It populates each Vector
      with the 8 original points of the bounding box. The points are assigned
      as follows. The bounding box is always centered about the origin and 
      assume the viewer is located at the origin looking along the +x axis.
      verts[0] is "front top left" +++
      verts[1] is "front bottom left" ++-
      verts[2] is "front top right" +-+
      verts[3] is "front bottom right"  +--
      verts[4] is "back top left" -++
      verts[5] is "back bottom left" -+-
      verts[6] is "back top right" --+
      verts[7] is "back bottom right" ---
   */
   void getBoundingBoxVerts( Vector* verts );
   static const int XYZ = 0;
   static const int XY_Z = 1;
   static const int X_YZ = 2;
   static const int X_Y_Z = 3;
   static const int _XYZ = 4;
   static const int _XY_Z = 5;
   static const int _X_YZ = 6;
   static const int _X_Y_Z = 7;



protected:

   void initMemberData();

   struct VLE_3VF_3CUB
   {
      GLfloat x, y, z;
      GLubyte r, g, b;
   };

   void generateVerts();
   bool OnFirstRender = true;

   void generateVertexArrayData();

   GLuint vao = 0; // Vertex Array Object handle (core)
   GLuint vertBO = 0; // Vertex Buffer Object (core)
   GLuint idxBO = 0; // Index Buffer Object (core)
   static const GLubyte vertexListSize = 24;
   static const GLubyte indexListSize = 24;
   std::vector< VLE_3VF_3CUB > vertexList;
   //VLE_3VF_3CUB* vertexList = nullptr;
   std::vector< GLubyte > indexList;
   //GLubyte* indexList = nullptr;

   Vector min;   //< The smallest X, Y, and Z components found thus far.
   Vector max;   //< The largest X, Y, and Z components found thus far.

   std::unique_ptr<IndexedGeometryRectangularPrism> prism;
};

std::ostream& operator <<( std::ostream& out, const BoundingBox& b );

} //namespace Aftr
