#pragma once
#include "AftrOpenGLIncludes.h"
#include "Tex.h"
#include "TexDataSharedID.h"
#include "AftrUtil_image.h"

#include <string>
#include <vector>
#include <functional>
#include <future>
#include <mutex>
#include <unordered_map>


//#include "Texture.h"
//#include "TextureData.h"
//#include "TextureOwnsTexData.h"
//#include "TextureOwnsTexDataSharesGLHandle.h"
//#include "TextureOwnsTexDataOwnsGLHandle.h"
//#include "TextureDataOwnsGLHandle.h"
//#include "TextureSharesTexData.h"
//#include "TextureSharesTexDataOwnsGLHandle.h"
//#include "TextureDataSharesGLHandle.h"



struct SDL_Surface;
class PBO_Tex_Xfer_Data;

namespace Aftr
{

   class TexDataShared;

class ManagerTex
{
public:

   ///The func_resume *will always occur* on the *main thread*, so it can mutate OpenGL!
   ///This is the lower-level function that sends VBO data to GPU memory.
   using func_send_to_GPU_on_main_thread = std::function<void()>;

   ~ManagerTex();
   ManagerTex( ManagerTex const& ) = delete;
   ManagerTex( ManagerTex&& ) = delete;
   ManagerTex& operator=( ManagerTex const& ) = delete;
   ManagerTex& operator=( ManagerTex&& ) = delete;

   static void init();
   static void shutdown();


   // Loads the texture at the file path 'fileToLoad'. If a requested pixel
   // width and heigh are passed in, the texture will be resized to those
   // dimensions instead of utilizing the native image size specified in 
   // the file.
   // 
   // Returns an optional<Tex>. If the optional is not std::nullopt, then
   // the image was found at the file location and is being asynchronously
   // loaded by a background thread. Once loaded, the texture will stream
   // to the GPU and become visible within a few frames.
   // 
   // If the returned optional<Tex> *is* nullopt, it is likely, the file
   // was not found on disk, see the console output for details.
   // 
   // Once a 'fileToLoad' has been loaded for a specific size, that data
   // is stored only once on the CPU/GPU in an internal cache. No matter
   // how many times a user requests to load the same image, it resides in
   // memory exactly once. This is the TexDataShared Portion of the data.
   // Each Tex is a lightweight wrapper specifying customization points to
   // draw the same TexDataShared w/ different properties, such as wrapping,
   // filtering, etc.
   // This employs a fly weight pattern, where a Tex is a lightweight,
   // easy-to-copy, value-semantics wrapper. Internally, the TexDataShared
   // is in a reference counted shared_ptr and will be destroyed when zero
   // references to the shared_ptr remain.
   static std::optional<Tex> loadTexAsync(
      std::string const& fileToLoad,
      unsigned int requested_pixelWidth = 0,
      unsigned int requested_pixelHeight = 0,
      bool createMipmap = true,
      GLint internalFormat = GL_RGBA, GLenum format = GL_RGBA, GLenum type = GL_UNSIGNED_BYTE );

   // Identical to loadTexAsync *except* does not internally store the
   // shared_ptr to the underlying TexDataShared. Which means the returned
   // Tex, if not std::nullopt, is the *only* Tex which refers to the
   // corresponding TexDataShared. If no copy of Tex is made, the TexDataShared
   // will be removed from CPU/GPU memory upon invocation of the Tex's ~Tex().
   static std::optional<Tex> loadTexAsync_unregistered(
      std::string const& fileToLoad,
      unsigned int requested_pixelWidth = 0,
      unsigned int requested_pixelHeight = 0,
      bool createMipmap = true,
      GLint internalFormat = GL_RGBA, GLenum format = GL_RGBA, GLenum type = GL_UNSIGNED_BYTE );

   // Returns a texture that is completely owned by the caller. This type of texture
   // is expected to be manipulated by Frame Buffer Objects (FBOs) for rendering to a
   // texture, or other more advanced usages beyond a simply texture loaded from a file.
   // 
   // The user is responsible for deleting this Texture and its TextureDataShared as well.
   static Tex loadDynamicTex( GLenum textureTarget = GL_TEXTURE_2D,
      GLint level = 0, unsigned int pixelWidth = 1, unsigned int pixelHeight = 1,
      GLint internalFormat = GL_RGBA, GLint border = 0,
      GLenum rawTexelFormat = GL_RGBA, GLenum rawTexelType = GL_UNSIGNED_BYTE,
      const GLvoid* pixels = NULL );

   static size_t sendPendingAsyncTexturesToGfxMemory();

   ///Returns a clone, use owns and must delete
   static Tex getDefaultTex() noexcept;
   ///Returns a clone, use owns and must delete
   static Tex getDefaultWhite2x2Tex() noexcept;

   static std::string toString();

private:
   ManagerTex() = default;
   static void internal_init( ManagerTex& s ); //only ever called exactly once!
   struct call_init_once;

   static ManagerTex& instance();
   std::vector< std::future< ManagerTex::func_send_to_GPU_on_main_thread > > parsed_Tex_ready_to_send_to_GPU_via_func;


   static std::optional< std::tuple< std::shared_ptr<TexDataShared>, SDL_Surface* > >
      load_tex_to_cpu_memory_no_gl_calls_sdl_image( std::shared_ptr<TexDataShared> pending_tds_to_load,
         unsigned int requested_pixelWidth, unsigned int requested_pixelHeight );

   //Calls AftrUtil_image
   static std::optional< std::tuple< std::shared_ptr<TexDataShared>, std::shared_ptr<Aftr_stb_image>> >
      load_tex_to_cpu_memory_no_gl_calls_stbi_image( std::shared_ptr<TexDataShared> pending_tds_to_load,
         unsigned int requested_pixelWidth, unsigned int requested_pixelHeight );

   static bool instantiateOpenGLTexture_SDLSurf( std::tuple< std::shared_ptr<TexDataShared>, SDL_Surface* > tex_n_rawDat );
   static bool instantiateOpenGLTexture_stbi( std::shared_ptr<TexDataShared > tds, 
                                              std::shared_ptr< Aftr_stb_image > raw_image );

   static SDL_Surface* convertSDLImageCoordsToOpenGLTexCoords( SDL_Surface* image );


   std::unordered_map< TexDataSharedID, std::shared_ptr<TexDataShared > > tds_map;
   //to be removed, and replaced with stb image
   std::mutex invokeSDLFunctionMutex; //locks SDL function calls that are not thread safe because of the SDL_Surface struct
   PBO_Tex_Xfer_Data* pbo_dat = nullptr;

   std::optional<Tex> default_tex_white; //Must be optional or the Tex needs to be created before ManagerTex is created, chick-n-egg deadlock on startup
   std::optional<Tex> default_tex;       //Must be optional or the Tex needs to be created before ManagerTex is created, chick-n-egg deadlock on startup
      
};


}
