#pragma once
#include "AftrConfig.h"
#ifdef AFTR_CONFIG_USE_IMGUI
#include "Vector.h"
#include "Tex.h"
#include <functional>

struct ImDrawList; //ImGui struct forward declarations
struct ImDrawCmd;  //ImGui struct forward declarations

namespace Aftr
{
   class GLSLShader;
   class IndexedGeometryQuad;

   /// Written by Scott Nykl.
   /// This is a simple class that will draw a Dear ImGui Window that displays a Texture*. The user may
   /// set the this->dock to any of the following locations and the window will remain docked there upon
   /// any resize in the main window or window showing the texture. A slider bar will let the user quickly
   /// adjust the size of the texture for easy viewing. Calling setTexDesiredWidth_maintainAspectRatio()
   /// will change the texture's size (scale) instantly.
   /// This class does *not* take ownership of the in passed texture.
   
   using shdr_bind_functor = std::function<void( Tex const& )>;

   //Returns the ModelViewProjection matrix for ImGui 2D ortho overlay. This is used by the
   //AftrImGuiTextureViewerWindow inside the custom renderer
   //The input param sets the display matrix for the quad's geometry on which the texture is placed.
   //Defaults to identity, but is helpful if the user wants to "flip the Y" axis by rotating 180 deg
   //about the Y.
   Aftr::Mat4 create_ortho_MVP_Matrix_for_ImGui2DOverlay( Mat4 const& displayMatrix_for_quadGeo = Mat4() );

   class AftrImGui_TextureViewerWindow
   {
   public:
      /// Setting the dock member to one of the enums will cause the window/texture to remain
      /// docked to the Upper Left, Upper Right, Lower Left, Lower Right, or Center portion of the screen, respectively.
      /// None will not perform any manipulation of the image.   
      enum class DOCKING_BEHAVIOR { UL, UR, LL, LR, C, NONE };
      AftrImGui_TextureViewerWindow();
      AftrImGui_TextureViewerWindow( Tex const& tex, std::string const& label );
      ~AftrImGui_TextureViewerWindow();
      void drawTex( bool* set_to_false_on_close = nullptr ) const;
      void drawTex( std::string const& label, bool* set_to_false_on_close = nullptr ) const;
      void setDock_Behavior( AftrImGui_TextureViewerWindow::DOCKING_BEHAVIOR dock_behavior ) { this->dock = dock_behavior; this->onLayoutChange = true; }
      void setLabel( std::string const& label ) { this->label = label; }
      void setFlipYAxis( bool flipY ) { this->flipYAxis = flipY; }

      /// When a user sets this method, AftrBurner takes over rendering this texture. Internally,
      /// a simple IndexedGeometryQuad is created, the specified texture is bound upon the quad,
      /// and the quad is rendered with this shader bound. However, the uniforms, attributes, etc
      /// for this shader may need to be updated each frame. Meaning the bind() method for the
      /// shader should be invoked to ensure the state is correct. Since this draw call is buried
      /// deep inside ImGui and not directly in the normal AftrBurner render loop, we cannot pass
      /// the bind(...) params normally, so we must type erase the bind behavior via an std::function
      /// that must be copy-able.
      void setGLSLShader( std::shared_ptr<GLSLShader> shdr, shdr_bind_functor const& bindFunc );
      
      //Defaults to using the GLSLShaderDefaultOrthoWOGUI_GL32_GL_REDtoLuminance with a bind
      //functor appropriate for ImGui texture viewing -- this is the *easiest* way to show grayscale images
      //instead of pairing an orthographic shader and bind func together.
      void setGLSLShader_toGrayscaleSingleChannelImage();
      // Assumes a typical OpenGL Depth buffer reside in the bound texture. Uses the 
      // GLSLShaderDefaultOrthoWOGUIDepthMapGL32 with a corresponding functor and ImGui ortho Projection settings
      void setGLSLShader_toGrayscaleSingleChannelDepthBufferImage( float nearPlaneDist, float farPlaneDist );
      void setGLSLShader_toRGB8Image(); //Default behavior
      const GLSLShader* getShader() const noexcept;
      GLSLShader* getShader() noexcept;
      void setTex( Tex const& tex );


      float get_desired_width() const noexcept { return this->desired_width; }
      Tex const& getTex() const noexcept { return this->tex; }

      /// Length is normalized from [0,1]. Upper Left is 0,0.
      void setTexDesiredWidth_maintainAspectRatio( float normalizedX );

      void onResizeWindow( int newWidthPix, int newHeightPix ) { this->onLayoutChange = true; }

      //Uses the current size of the content of this window (from the last frame) as well as the docking information
      //to compute the upper left corner for setting the windows location so it will align to the specified DOCK
      //behavior. May return a std::nullopt if docking behavior is NONE since no new pixel location is required
      std::optional<Vector> computePixel_Loc_UL_Corner_from_current_Layout( float window_width_pix, float window_height_pix ) const;

      //Stores ImGui window data where the window corner's upper left is saved.
      //To conver to OpenGL coords, use convert_ImGui_WinPos_to_OpenGL_2D_Pixel_coords(...)
      struct ImGui_WinPos
      {
         VectorI lower_left{ 0,0 }; //only uses .x and .y to indicate lower left corner of window for the OpenGL Viewport
         VectorI size_x_y{ 0,0 };   //only uses .x and .y to indicate horizontal x-length and vertical y-length
      };
      //sample usage:
      //auto [upper_left, lxly] = convert_ImGui_WinPos_to_OpenGL_2D_Pixel_coords( imgui_win );
      //Converts the upper_left corner of ImGui to OpenGL's desired lower_left corner
      static std::tuple< VectorI, VectorI > convert_ImGui_WinPos_to_OpenGL_2D_Pixel_coords( const ImGui_WinPos& p ) noexcept;

      //Some textures may not be a simple RGBA8 texture and therefore require their own GLSLShader to be
      //bound when this image is drawn. In order to do this, we render the texture using AftrBurner and
      //not ImGui's vertex buffers. As a result, we also create an IndexedGeometry quad that the corresponding
      //texture is bound upon.
      struct custom_render_to_quad
      {
         std::shared_ptr<GLSLShader> shdr;
         std::shared_ptr<IndexedGeometryQuad> quad;
         ImGui_WinPos winPos;
      };

      /// Execution of this is invoked from within ImGui's ImDrawList::AddCallback(...) mechanism.
      /// This implies some ImGui functions are currently unavailable, for example, ImGui::GetCursorScreenPos(...)
      /// is invalid and will cause a crash. Instead of querying ImGui state, all needed state should be saved
      /// inside this instances custom_render_to_quad struct. A user should not ever directly call this method,
      /// they just say:
      /// ImGui::GetWindowDrawList()->AddCallback( custom_render_dispatch_func, (void*)this );
      /// ... and the custom_render_dispatch_func() will invoke this render method.
      void renderGL32_IGQuad_with_GLSLShader_while_inside_ImGui_Callback( custom_render_to_quad ) const noexcept;


      AftrImGui_TextureViewerWindow( const AftrImGui_TextureViewerWindow& toCopy );
      AftrImGui_TextureViewerWindow( AftrImGui_TextureViewerWindow&& toMove ) noexcept;
      AftrImGui_TextureViewerWindow& operator=( const AftrImGui_TextureViewerWindow& toAssign );
      AftrImGui_TextureViewerWindow& operator=( AftrImGui_TextureViewerWindow&& toMoveAssign ) noexcept;

   private:
      Tex tex;
      std::string label;
      mutable bool flipYAxis = false; //turn upside down images rightside up - checkbox state to flip the V (vertical) UV coords

      mutable DOCKING_BEHAVIOR dock = DOCKING_BEHAVIOR::NONE;

      //When a custom shader / renderer is used and the tex viewer window is dragged outside of the main
      //ImGui window, it frequently turns black. The same is true if the window starts outside the main window
      //(is working), and then is docked inside the main window. Once turned black, the image no longer renders properly.
      //To work around this issue, sln decided to force the TexViewer window to be its own platform window that
      //is *not* dockable when a custom shader is being used. That is, when the window is created w/ a custom renderer,
      //it is created as ImGuiViewportFlags_NoAutoMerge
      bool drawTex_customRendererInsideMainWindow( std::string const& label, bool* set_to_false_on_close ) const;
      bool drawTex_customRendererOutsideMainWindow( std::string const& label, bool* set_to_false_on_close ) const;

      //drawTex() calls either this drawTex func or drawTex_customRenderer(). The key is that the proper flags
      //are passed to the first invocation of the ImGui Window's ImGui::Begin(...).
      void drawTex_defaultRenderer( std::string const& label, bool* set_to_false_on_close ) const;

      //These ImGui widget details are inconsequential w.r.t the texture they visualize, but we want the user
      //to be able to resize the view of this texture while still being able to draw from a const method. There
      //these internal variables are qualified as mutable to present the const qualifier of drawTex().
      //As long as the Texture (which is const) is not mutated, we should expect good behavior from this class.
      //In the future if multiple threads start touching these mutable variables (which would be a bad, evil thing
      //to do) this would not be thread safe.
      mutable float desired_width = 0.25f; ///< user desired width of the screen [0,1] this texture will occupy
      mutable float desired_width_cache = desired_width;

      mutable bool onLayoutChange{ true };
      mutable Vector lastFrame_sizeXY_px;
      mutable VectorI winPos_px; ///< ImGui's pixel location of the window, set immediately before the
      //custom rendering draw back is called.

      //eventually right click to context menu to dock to a corner and set horz size

      shdr_bind_functor custom_renderer_shdr_bind_func;
      mutable std::optional< custom_render_to_quad > custom_renderer_inMainWindow; //starts as nullopt, members, such as winPos, are updated
      //prior to scheduling the C-style callback. This could be more elegant, but a lambda or
      // std::function cannot be stored in a C function pointer, so a capture is not viable here.
      mutable std::optional< custom_render_to_quad > custom_renderer_outsideMainWindow; //starts as nullopt, members, such as winPos, are updated

      //Encapsulates the custom render callbacks. This way the callbacks can access private data of the TextureViewerWindow
      struct detail
      {
         static void custom_render_dispatch_func_inMainWindow( const ImDrawList* parent_list, const ImDrawCmd* pcmd );
         static void custom_render_dispatch_func_outsideMainWindow( const ImDrawList* parent_list, const ImDrawCmd* pcmd );
      };
   };

} //namespace Aftr
#endif