#pragma once
#include "AftrConfig.h"

#include "TexDataShared.h"
#include "Vector.h"
#include "Mat4.h"
#include <string>
#include <memory>


namespace Aftr
{

/**
   \brief A class that represents a Tex that can be bound across
   one or more verticies.

   Each Tex object has two parts... Shared data and per instance data. The shared
   data is stored in memory exactly once and is managed by the ManagerTex; this 
   is the fundamental data that does not ever change regardless of how it is 
   drawn. This includes things like the server side OpenGL handle, the filename 
   corresponding to that data, the actual image data, etc. This is owned 
   by the ManagerTex and should not be directly manipulated by the user without using 
   the ManagerTex's functions. This data is stored inside the TexDataShared Object.
   There is a many-to-one mapping where many Tex Objects map to a single 
   TexDataShared. A Tex Object has an internal pointer to a TexDataShared Object.

   The per instance data is unique to this Tex Object and is owned by this Tex Object.
   This includes such things as this Tex Object's Tex rotation matrix, Tex translate
   amount, various Tex related OpenGL flags (GL_CLAMP_TO_EDGE, GL_REPEAT, GL_CLAMP_TO_BORDER,
   GL_MIRRORED_REPEAT, GL_MIRROR_CLAMP_TO_EDGE), and other attributes
   that change the properties of the Tex when it is rendered.
*/
class Tex
{
public:

   friend class ManagerTex;

   //Default Tex simply displays the default texture as its image. This let's one easily
   //default construct a Tex and change it later using value semantics.
   Tex();
   Tex( std::shared_ptr<TexDataShared> texDataShared );
   Tex( const Tex& tex );
   ~Tex(); //This class is abstract and cannot be instantiated

   /**
      Returns a clone of this instance; behaves identically to proper copy constructor.
      This exists, however, because a copy constructor may be of a base type and accept
      a derived Tex. This method enforces that the new'd Tex is always of the
      exact subclass even though a base Tex* is returned.
   */
   //virtual Tex* cloneMe() = 0; //replace w/ value semantics -- copy

   std::shared_ptr<TexDataShared> getTexDataShared() const;

   /**
      This resets all properties owned by this Tex Object. This includes resetting
      the this Tex's Tex rotation matrix to identity, this Tex's translate
      Vector to (0,0,0), resetting all flags to default values.
   */
   void resetTexProperties();

   Tex& operator =( const Tex& tex );

   /**
      \return a string containg "FileName:'(fileName)', GLuint:'(glTexName)'".
   */
   std::string toString() const;

   /// \returns the file name loaded in this Tex or "" if not set.
   std::string getFileName() const;

   /// \returns the name of the OpenGL Tex. That is, this is the value to use in glBindTex.
   GLuint getGLTex() const;

   /**
      This flag remains false until the Tex is fully loaded into OGL. Once
      true, this Tex's TexDataShared->glTex can be bound and will show 
      the expected Tex.
      If this Tex is bound before this flag gets set to true, the engine's default
      Tex may be displayed but this will not cause undefined behavior.

      This flag is only useful if this Tex was loaded via
      ManagerTex::loadTexAsync(...); otherwise, this flag gets set to true
      before ManagerTex::loadTex(...) returns this object.

      ManagerTex::loadTexAsync(...) dispatches a separate thread to load the
      Tex so this can be done in parallel.
   */
   bool isTexFullyInitialized() { return this->tds->isTexFullyInitialized(); }

   unsigned int getWidthInPixels() const { return this->tds->getWidthInPixels(); }
   unsigned int getHeightInPixels() const { return this->tds->getHeightInPixels(); }
   bool isMipmapped() { return this->tds->isMipmapped(); }
   bool isUsingTexMatrix() { return this->UseTexMat; }
   void isUsingTexMatrix( bool useTexMatrix ) { this->UseTexMat = useTexMatrix; }

   /**
      Enables Tex to be outputted to an ostream.
   */
   friend std::ostream& operator <<( std::ostream&, const Tex& );

   void bind() const;
   void bindGL32() const;
   void bindCompatibility() const;

   void unbind() const;
   void unbindGL32() const;
   void unbindCompatibility() const;

   Mat4 const& getTexMatrix() const noexcept; ///< Only the current Direction Cosine Matrix (no translations or scalings)
   Mat4 getTexMatrixWithRotScaleAndTrans() const noexcept; ///< Full Tex Matrix expected by a Shader
   void setTexMatrix( const float* const m4x4ColMajorTexMatrix );
   void setTexMatrix( const Mat4& texMatrix );
   Vector transformVecThroughCurrentTexMatrix( const Vector& vec );
   Vector transformVecThroughCurrentTexMatrixAddingCurrentPosition( const Vector& vec );
   Vector transformVecThroughCurrentTexMatrixSubtractingCurrentPosition( const Vector& vec );
   Vector transformVecThroughInverseOfCurrentTexMatrix( const Vector& vec );
   Vector transformVecThroughInverseOfCurrentTexMatrixAddingCurrentPosition( const Vector& vec );
   Vector transformVecThroughInverseOfCurrentTexMatrixSubtractingCurrentPosition( const Vector& vec );

   void rotateAboutGlobalX( float deltaRadianAngle );
   void rotateAboutGlobalY( float deltaRadianAngle );
   void rotateAboutGlobalZ( float deltaRadianAngle );
   void rotateAboutRelAribitraryAxis( const Vector& axisOfRot, float radianDistance );
   void rotateAboutRelX( float deltaRadianAngle );
   void rotateAboutRelY( float deltaRadianAngle );
   void rotateAboutRelZ( float deltaRadianAngle );
   void rotateAboutGlobalAribitraryAxis( const Vector& axisOfRot, float radianDistance );

   void translateTex( const Vector& displacement );
   void setTexPosition( const Vector& position );
   Vector getTexPosition() const { return this->texTrans; }

   void setTexRotationPoint( const Vector& rotPointTexSpace ) { this->fixedPoint = rotPointTexSpace; }
   void setTexScaleFactor( const Vector& scaleFactor );
   void scaleTex( const Vector& deltaScaleToAddToCurrentScaleFactor );
   Vector getTexScaleFactor() const { return this->texScale; }
   void setTexRepeatsInX( float numRepeats );
   void setTexRepeatsInY( float numRepeats );
   void setTexRepeatsInZ( float numRepeats );
   void setTexRepeats( float numRepeats );
   void setWrapS( GLint glWrapParam );
   void setWrapT( GLint glWrapParam );

   void setMinFilter( GLenum minFilter );
   void setMagFilter( GLenum magFilter );

   void setEnvMode( GLenum envMode ) { this->envMode = envMode; }

private:


   /**
      Each Tex object has two parts... Shared data and per instance data. The shared
      data is stored in memory exactly once and is managed by the ManagerTex; this 
      is the fundamental data that does not ever change regardless of how it is 
      drawn. This includes things like the server side OpenGL handle, the filename 
      corresponding to that data, the actual image data, etc. This is owned 
      by the ManagerTex and should not be directly manipulated by the user without using 
      the ManagerTex's functions. This data is stored inside the TexDataShared Object.
      There is a many-to-one mapping where many Tex Objects map to a single 
      TexDataShared. A Tex Object has an internal pointer to a TexDataShared Object.

      The per instance data is unique to this Tex Object and is owned by this Tex Object.
      This includes such things as this Tex Object's Tex rotation matrix, Tex translate
      amount, various Tex related OpenGL flags (GL_CLAMP_TO_EDGE, GL_REPEAT, GL_CLAMP_TO_BORDER,
      GL_MIRRORED_REPEAT, GL_MIRROR_CLAMP_TO_EDGE), and other attributes
      that change the properties of the Tex when it is rendered.
   */
   std::shared_ptr<TexDataShared> tds; //if loaded via ManagerTex::loadAsyncTex(...) one reference is in the ManagerTex's internal map
                           //if loaded via ManagerTex::loadAsyncTex_unregistered(...) this is the only reference and will be destroyed if not kept alive

   /**
      Per instances data for this Tex Object. This data is owned by this Tex Object.
   */

   Mat4 texMat; ///< Tex rotation matrix
   Vector texTrans; ///< Tex translation in X,Y, and Z dimensions
   Vector texScale{ 1,1,1 }; ///< Tex scaling in X,Y, and Z dimensions
   Vector fixedPoint{0.5f,0.5f,0.5f}; ///< Point about which this Tex is rotated - for OpenGL, .5,.5,.5 is center of Tex halfway between [0,1]
   bool UseTexMat = false; ///< Iff true, this Tex is rotated via this matrix prior to each render; else texMat is not used
   bool UseTexTrans = false; ///< Iff true, this Tex is translated by texTrans vector prior to each render; else texTrans is not used
   bool UseTexScale = false; ///< Iff true, this Tex is scaled by texScale vector prior to each render; else texScale is not used
   
   //Tex OpenGL Flags
   //http://www.opengl.org/documentation/specs/man_pages/hardcopy/GL/html/gl/texparameter.html
   /**
      Sets this Tex's environment via glTexEnvi(). (not supported in core)
      This can be set to one of GL_MODULATE, GL_DECAL, GL_BLEND, or GL_REPLACE.
   */
   GLint envMode = GL_MODULATE;
   GLint wrapS = GL_REPEAT; ///< Sets the wrap parameter for Tex	coordinate s (u coord) to either GL_CLAMP_TO_EDGE, GL_REPEAT, GL_CLAMP_TO_BORDER, GL_MIRRORED_REPEAT, GL_MIRROR_CLAMP_TO_EDGE
   GLint wrapT = GL_REPEAT; ///< Sets the wrap parameter for Tex	coordinate t (v coord) to either GL_CLAMP_TO_EDGE, GL_REPEAT, GL_CLAMP_TO_BORDER, GL_MIRRORED_REPEAT, GL_MIRROR_CLAMP_TO_EDGE

   /**
      Sets the GL_Tex_MAG_FILTER for magnifying a texel smaller than a pixel up to one pixel.
      Valid values are: GL_NEAREST and GL_LINEAR.
      These are initially set to an intelligent default upon construction and takes
      this Tex's mipmapping status into account.
   */
   GLint magFilter = GL_LINEAR;
   
   /**
      Sets the GL_Tex_MIN_FILTER for shrinking a texel bigger than a pixel down to one pixel.
      Valid values are: GL_NEAREST, GL_LINEAR, GL_NEAREST_MIPMAP_NEAREST, GL_LINEAR_MIPMAP_NEAREST,
      GL_NEAREST_MIPMAP_LINEAR, or GL_LINEAR_MIPMAP_LINEAR.
      These are initially set to an intelligent default upon construction and takes
      this Tex's mipmapping status into account.
   */
   GLint minFilter = GL_LINEAR;

};

   std::ostream& operator <<( std::ostream&, const Tex& ); //actual declaration (not a friend qualifier)

} //namespace Aftr

