#pragma once

#include "AftrOpenGLIncludes.h"
#include <string>
#include <functional>

namespace Aftr
{

/**
   \brief A class that represents a unique identifier for the Shared portion of a Texture's data;
   ie, TexDataShared. This class defines equivalence and a hash operator so any texture that is loaded
   can be uniquely identified *and* subsequent attempts to reload the identical texture result in
   sharing the already loaded TexDataShared.

   The shared texture data, TexDataShared, is the GPU OpenGL handle as well as immutable properties
   of the loaded texture. These properties include the parameters needed to uniquely construct a
   TexDataShared, namely:
   1) const std::string& filename (converted to absolute path if this is a normally loaded tex (ie, not a Dynamic Tex)
   2) bool createMipmap //the same tex can be loaded twice -- once w/ a mipmap, once w/o. This is typically not done, however.
   3) GLint internalFormat  //the internal format OpenGL will use to layout the texture on the GPU, commonly GL_RGBA8
   
   Some obvious properties are not used, including the Texture's pixelWidth and pixelHeight. This is because when
   a filename is given, one must open and parse the file *before* knowing the pixel dimensions. As a result, a
   worker thread parsing that file doesn't find out the size until after the unique ID is already created.

*/
class TexDataSharedID
{
public:
  
   std::string fileName; ///< name of texture read from disk   
   bool IsMipmapped = false; ///< If true, this image is mipmapped on server-side; false if not mipmapped on server-side
   GLenum texDimensionality = -1; ///< Set to GL_TEXTURE_1D, GL_TEXTURE_2D, GL_TEXTURE_3D, GL_TEXTURE_4D, TEXTURE_CUBE_MAP, or TEXTURE_BUFFER_EXT; default is 2D.
   GLint internalFormat = -1;
   unsigned int width  = 0; ///< user-requested width of Image in pixels when initially loaded (0 means use size from file)
   unsigned int height = 0; ///< user-requested height of Image in pixels when initially loaded (0 means use size from file)

   //Auto generates 3-way comparision for each data member used. Only include members that do not change
   //after construction
   auto operator<=>( TexDataSharedID const& t ) const noexcept = default;

   std::string toString() const;

};

} //namespace Aftr

namespace std
{
   //Create a custom hash function for ModelDataSharedID and place it in the std namespace
   // so the std::unordered_multi_map can find this template specialization automatically.
   //https://stackoverflow.com/questions/17016175/c-unordered-map-using-a-custom-class-type-as-the-key

   //Sine we are using a hash function, we are combining a string and vector (3 floats), so
   // we want a good hash function.
   //https://stackoverflow.com/questions/1646807/quick-and-simple-hash-code-combinations/1646913#1646913

   template<>
   struct hash< Aftr::TexDataSharedID >
   {
      size_t operator()( const Aftr::TexDataSharedID& k ) const
      {
         //std::hash<std::string> str_hasher;
         //uses underlying type of Vector (either float or double or long double, etc)
         //using fileName_type       = decltype(Aftr::TexDataSharedID::fileName);
         using mipmapped_type      = decltype(Aftr::TexDataSharedID::IsMipmapped);
         using texDim_type         = decltype(Aftr::TexDataSharedID::texDimensionality);
         using internalFormat_type = decltype(Aftr::TexDataSharedID::internalFormat);
         using sz_type             = decltype(Aftr::TexDataSharedID::width);
         
         std::hash< std::string >         fileName_hasher;
         std::hash< mipmapped_type >      mipmapped_hasher;
         std::hash< texDim_type >         texDim_hasher;
         std::hash< internalFormat_type > internalFormat_hasher;
         std::hash< sz_type >             sz_hasher;

         size_t hash =   fileName_hasher( k.fileName );
         hash ^= (      mipmapped_hasher( k.IsMipmapped ) << 1 );
         hash ^= (         texDim_hasher( k.texDimensionality ) << 1 );
         hash ^= ( internalFormat_hasher( k.internalFormat ) << 1 );
         if( k.width > 0 )
            hash ^= ( sz_hasher( k.width ) << 1 );
         if( k.height > 0 )
            hash ^= ( sz_hasher( k.height ) << 1 );
         return hash;
      }
   };
}
