#pragma once

#include "AftrOpenGLIncludes.h"
#include <vector>
#include "Vector.h"
#include <functional>
#include <array>
#include <span>
#include <variant>
#include <type_traits>
//import std;

namespace Aftr
{

   /// Set STORAGE_TYPE to GLubyte  if idxCount <= 255 indices
   /// Set STORAGE_TYPE to GLushort if idxCount <= 65535 indices
   /// Set STORAGE_TYPE to GLuint   if idxCount >= 65536 indices
   //template< std::integral STORAGE_TYPE >
   //struct IndexedGeometry_TE_Index_Storage_Type
   //{
   //   using value_type = STORAGE_TYPE;
   //   size_t variantIdx = -1;
   //   IndexedGeometry_TE_Index_Storage_Type( size_t idx_size )
   //   {
   //      std::printf( "User should prefer to use IndexedGeometry_TE_Index_Storage_Size of " );

   //      if( idx_size < std::pow( 2, sizeof( GLubyte ) * 8 ) )
   //      {
   //         std::printf( "IndexedGeometry_TE_Index_Storage_Size< ..., GLubyte> ...\n" );
   //         variantIdx = 0;
   //         if( std::is_same_v< GLubyte, STORAGE_TYPE > )
   //            std::printf( "Optimal Index usage for %zu indices...\n", idx_size );
   //      }
   //      else if( idx_size < std::pow( 2, sizeof( GLushort ) * 8 ) )
   //      {
   //         std::printf( "IndexedGeometry_TE_Index_Storage_Size< ..., GLushort>...\n" );
   //         variantIdx = 1;
   //         if( std::is_same_v< GLushort, STORAGE_TYPE > )
   //            std::printf( "Optimal Index usage for %zu indices...\n", idx_size );
   //      }
   //      else
   //      {
   //         std::printf( "IndexedGeometry_TE_Index_Storage_Size< ..., GLuint>...\n" );
   //         variantIdx = 2;
   //         if( std::is_same_v< GLuint, STORAGE_TYPE > )
   //            std::printf( "Optimal Index usage for %zu indices...\n", idx_size );
   //      }

   //      if( std::numeric_limits<STORAGE_TYPE>::max() < idx_size )
   //      {
   //         std::printf( "ERROR: %s: Your IndexedGeometry_TE cannot support %zu indices, set STORAGE_TYPE to\n"
   //            "GLubyte if idx <= 255; GLushort if idx <= 65535; GLuint if idx >= 65536...\n", AFTR_FILE_LINE_STR, idx_size );
   //         std::abort();
   //      }
   //   }

   //   std::vector<STORAGE_TYPE> const& getIndices( std::variant<
   //      std::vector<GLubyte>, 
   //      std::vector<GLushort>, 
   //      std::vector<GLuint> > const& v ) const noexcept
   //   {
   //      return std::get<std::vector<STORAGE_TYPE>>( v );
   //   }

   //   template<std::integral I>
   //   std::vector<STORAGE_TYPE> operator()( std::vector<I>&& idx )
   //   {
   //      std::vector<STORAGE_TYPE> ret( idx.size(), 0 );
   //      std::transform( idx.begin(), idx.end(), ret.begin(), []( I const i ) { return STORAGE_TYPE( i ); } );
   //      return ret;
   //   }
   //};

   class IndexedGeometry_TE
   {
   public:

      //Main methods used by the user
      using INDEX_LIST_VARIANT = std::variant< std::vector<GLubyte>, std::vector<GLushort>, std::vector<GLuint> >;

      //Type Erased methods specified in the IndexedGeometry_TEIFace (type erased Interface)
      void render() const noexcept;
      void renderGL32() const noexcept;
      void renderCompatibility() const noexcept;
      void renderSelection( GLubyte red, GLubyte green, GLubyte blue ) const noexcept;
      void setGLPrimType( GLenum x ) { renderType = x; }

      void updateColorBuffer() noexcept;
      void updateVertexBuffer() noexcept;
      void updateNormalBuffer() noexcept;
      void updateTextureCoordsBuffer() noexcept;
   private:

      //vertex attributes and topology data
      std::vector< Vector > verts;  //xyz verts
      INDEX_LIST_VARIANT ilist;     //topology describing the faces, depends on renderType. Internal size of each index depends on index_memory_type
      std::vector< Vector > normals;
      std::vector< std::array<float, 2> > texCoords;
      std::vector< aftrColor4ub > colors;

      //OpenGL specific data, populated within createVAO
      GLenum renderType = GL_TRIANGLES;
      GLenum index_memory_type = GL_UNSIGNED_SHORT; //type of integer composing the ilist. Set in onCreate()

      GLuint vertexLocation = -1; ///< GLSL Attribute locations within the shader, queried and populated during creation
      GLuint normalLocation = -1; /// These are initialized to -1 even though they are unsigned because "0" is a valid GL attribute location
      GLuint colorLocation = -1;
      GLuint texCoordLocation = -1;

      GLuint vao = 0;          ///< Vertex Array Object which stores all buffer binding states required to render this VAO
      GLuint indexBuffer = 0;  ///< OpenGL Buffer containing indices to render the geometry
      GLuint vertexBuffer = 0; ///< OpenGL Buffer location for the vertex attributes (set of vertices passed to shader)
      GLuint normalBuffer = 0; ///< OpenGL Buffer location for the normal attributes (set of normals passed to shader)
      GLuint colorBuffer = 0;  ///< OpenGL Buffer location for the colors attributes (set of colors passed to shader)
      GLuint textureBuffer = 0;///< OpenGL Buffer location for the textures attributes (set of textures passed to shader)

   public:
      //Here is how one could declare a sample builder function. It assumes each function returns the respective data. 
      // For example:      
      //IndexedGeometry_TE::fill_indexed_geometry_func igBuilder = [=]( 
      //   std::vector< Vector >& verts,
      //   IndexedGeometry_TE::INDEX_LIST_VARIANT& ilist,
      //   std::vector< Vector >& normals,
      //   std::vector< std::array<float, 2> >& texCoords,
      //   std::vector< aftrColor4ub >& colors )
      //   {
      //      //the minimum needed is verts and ilist, other lists can be left empty
      //      verts = vertFunc();
      //      ilist = idxFunc();
      //      normals = normFunc();
      //      texCoords = texFunc();
      //      //colors = colorFunc(); //if no colors are used, leave this empty
      //   };
      //This assumes each Func() returns -- vertFunc and idxFunc *must* return valid values; the other funcs can return empty if not used
      //  vertFunc()  -> std::vector<Vector>
      //  idxFunc()   -> std::vector<GLubyte> //(up to 256 indices or std::vector<GLushort> (65536) or std::vector<GLuint> (4 billion) )
      //  normFunc()  -> std::vector<Vector>
      //  texFunc()   -> std::vector< std::array<float, 2> >
      //  colorFunc() -> std::vector< aftrColor4ub >
      using fill_indexed_geometry_func = std::function< void( decltype(verts)&,       //std::vector< Vector >& verts
         INDEX_LIST_VARIANT&,    //INDEX_LIST_VARIANT& ilist,
         decltype(normals)&,     //std::vector< Vector >& normals
         decltype(texCoords)&,   //std::vector< std::array<float, 2> >& texCoords
         decltype(colors)& ) >;  //std::vector< aftrColor4ub >& colors

      INDEX_LIST_VARIANT const& getIndexListVariant() const noexcept { return this->ilist; }
      size_t getIndexCount()  const noexcept
      {
         switch( this->ilist.index() )
         {
         case 0: return std::get<0>( this->ilist ).size();
         case 1: return std::get<1>( this->ilist ).size();
         case 2: return std::get<2>( this->ilist ).size();
         default:
            std::printf( "ERROR: %s: Invalid index storage unit in index list!\n"
               "For your index list in your IndexedGeometry_TE, have\n"
               "your index list in fill_indexed_geometry_func return\n"
               "one of the following types:\n"
               "std::vector<GLubyte>\n"
               "std::vector<GLushort>\n"
               "std::vector<GLuint>...\n", AFTR_FILE_LINE_STR );
            std::abort();
         }
      }

      //Accessor methods to access the geometry of this data.
      //Visits the INDEX_LIST_VARIANT, converts all indices to std::vector<unsigned int>, and returns that as a copy
      std::vector<unsigned int> getIndicesCopy() const;
      std::span<const decltype(verts)::value_type>     getVertices()      const noexcept { return std::span{ verts }; }
      std::span<const decltype(normals)::value_type>   getNormals()       const noexcept { return std::span{ normals }; }
      std::span<const decltype(texCoords)::value_type> getTextureCoords() const noexcept { return std::span{ texCoords }; }
      std::span<const decltype(colors)::value_type>    getColors()        const noexcept { return std::span{ colors }; }
      GLsizei getVertexCount() const noexcept { return static_cast<GLsizei>(verts.size()); }//number of vertices
      std::vector< decltype(verts)::value_type>     getVerticesVec()      const noexcept { return verts; }

      std::span<decltype(verts)::value_type>     getVertices()      noexcept { return std::span{ verts }; }
      std::span<decltype(normals)::value_type>   getNormals()       noexcept { return std::span{ normals }; }
      std::span<decltype(texCoords)::value_type> getTextureCoords() noexcept { return std::span{ texCoords }; }
      std::span<decltype(colors)::value_type>    getColors()        noexcept { return std::span{ colors }; }


   private:
      void onCreate( fill_indexed_geometry_func&& builder );
      void createVAO();
      void reattach(); //updates glGetAttribLocation indicies given the currently bound shader
      struct Cache
      {
         GLsizei saved_idx_size = 0;
         GLsizei saved_idx_buffer_size_bytes = 0; //populated in onCreate() and used to avoid re-looking up the ilist variant data each render
         GLvoid const* saved_idx_ptr = nullptr;   //populated in onCreate() and used to avoid re-looking up the ilist variant data each render
      };
      Cache cache;

      static std::tuple<GLenum, Cache> check_idx_size( INDEX_LIST_VARIANT const& var );
      //friend std::tuple<GLenum, IndexedGeometry_TE::Cache> check_idx_size( IndexedGeometry_TE::INDEX_LIST_VARIANT const& var );

   public:

      //Primary ctor -- pass in a func that simply places data in at least verts and ilist.
      //If the user wants normals, colors, or tex coords, simply make those vectors non-zero
      //in size as well.
      IndexedGeometry_TE( fill_indexed_geometry_func&& createGeometryFunc );

      //Big 5 Overloads - ctor, copy ctor, copy assign operator, move ctor, move assign operator, + d'tor (should be virtual if any method in this class is virtual)
      IndexedGeometry_TE() = delete;
      ~IndexedGeometry_TE();
      IndexedGeometry_TE( IndexedGeometry_TE const& toCopy );
      IndexedGeometry_TE& operator=( IndexedGeometry_TE const& toCopyAssign );

      IndexedGeometry_TE( IndexedGeometry_TE&& toMove ) noexcept;
      IndexedGeometry_TE& operator=( IndexedGeometry_TE&& toMoveAssign ) noexcept;

   };

}

