#pragma once
#include "AftrConfig.h"

#include <memory>
#include <string>
#include <expected>
#include <optional>
#include "Tex.h"


namespace Aftr
{
   //SLN BLUF - read/write jpg/png files:
   //use write_image_png(...) to write a png to disk
   //use load_image_from_file(...) to read a "common image file" like png or jpg (the engine uses this to load textures)
   // 
   // read/write grayscale files:
   //use write_image_pgm(...) for gray scale 8 or 16-bit depth. Use std::uint8_t as the template param for 8 bit
   // and use std::uint16_t for 16 bit depth -- like for monochrome images acquired from cameras
   //use load_image_from_file_cimg(...) to load the grayscale images. This can also do a lot more if ImageMagick is
   //installed, but I haven't added the simple helper stubs yet.

   //Provides a move-only RAII wrapper around the raw stbi image loader to avoid mem leaks and ensure, at compile
   //time, that no extra copies are made (move only)
   //This image format is always loaded as R  RG  RGB RGBA in interleaved pixel format.
   //The STBI loader is good for reading images, not as good for writing them -- it only writes 8 bit channel
   //imagery and is not that fast. It also does not support writing grayscale pgm. So CImg is also used
   //depending on what is desired to be written / read.

   //If you are reading and writing a normal colored image for a texture, use
   //load_image_from_file() and write_image_png()

   //AftrBurner also wraps CImg library to support pgm files and any other format
   // that ImageMagick supports (but you have to install Image Magick before CImg can process them).
   //If you are saving monochrome imagery, its a lot faster to use
   //load_image_from_file_cimg() and write_image_pgm()

   //See C:\repos\aburn\engine\src\aftr\gtest\AftrUtil_image_test.cpp and the tests for examples
   //reading and writing images... grayscale_timing_cimg_pnm


   //Must use the method load_image_from_file(...) to create an Aftr_stb_image
   class Aftr_stb_image;
   using Aftr_stb_imagePtr = std::shared_ptr<Aftr_stb_image>;
   //The Aftr_stb_image class wraps the STBI image header only library since it has a C malloc'd internal
   //loading system.
   class Aftr_stb_image
   {
   public:
      //A friend so it can access the private c'tor
      friend Aftr_stb_imagePtr load_image_from_file( std::string const& fileName, bool flipVertically, int desired_NumChannels );
      Aftr_stb_image() = delete;
      ~Aftr_stb_image();
      Aftr_stb_image( Aftr_stb_image const& toCopy ) = default;
      Aftr_stb_image( Aftr_stb_image&& toMove ) noexcept = default;
      Aftr_stb_image& operator=( Aftr_stb_image const& ) = default;
      Aftr_stb_image& operator=( Aftr_stb_image&& toMoveAssign ) noexcept = default;

      int w = -1;
      int h = -1;
      int numComp = 0;
      //owned by this instance, but allocated via C (malloc), must use stbi_image_free to deallocate.
      //Hence this class exists to RAII allocate and free the memory appropriately.
      unsigned char* data = nullptr; 
   private:
      //Private c'tor so only factory function can create this and return a shared_ptr.
      Aftr_stb_image( int wPix, int hPix, int numColorChannels, unsigned char* data );
   };


   template<typename>
   class ImageWrapper; //forward declare so toString free function is visible to ImageWrapper
      
   using ImageWrapper_uint8t  = ImageWrapper< std::uint8_t >;  //each color channel is 8bits
   using ImageWrapper_uint16t = ImageWrapper< std::uint16_t >; //each color channel is 16bits
   using ImageWrapper_uint32t = ImageWrapper< std::uint32_t >; //each color channel is 32bits
   using ImageWrapper_f32t    = ImageWrapper< float >;  //each color channel is 32 bit float

   //Free functions to print info about an ImageWrapper
   std::string toString( ImageWrapper_uint8t  const& img );
   std::string toString( ImageWrapper_uint16t const& img );
   std::string toString( ImageWrapper_uint32t const& img );
   std::string toString( ImageWrapper_f32t    const& img );

   //Stores only the meta data of an ImageWrapper. Helpful if one doesn't want to store an actual
   //ImageWrapper copy which will keep the shared_ptr to the pixel data alive.
   struct ImageWrapperInfo
   {
      int32_t w{ 0 };
      int32_t h{ 0 };
      int32_t numComponents{ 0 };
      int32_t bytesPerComponent{ 0 };
      bool    isInterleavedComp{ true };
      std::uint32_t getSizeBytes() const noexcept { return w * h * numComponents * bytesPerComponent; }
      std::string toString() const;
      auto operator<=>( const ImageWrapperInfo& ) const = default; //enable compairison on meta data
   };


   //The ImageWrapper class generically wraps data loaded from some other image loading library like
   // CImg. Eventually the Aftr_stb_image will be replaced with this.
   //Most common values for BYTES_PER_COMPONENT are
   // A component is a single part of an individual pixel. For example if one pixel
   // has RED,GREEN,BLUE, and ALPHA. The component is the underlying TYPE used to store each R,G,B, and A.
   // So if the component was std::uint8_t, then each pixel would be RGBA8, that is four 1 byte values
   // RED is std::uint8_t
   //std::uint8_t, std::uint16_t, std::uint32_t, std::float or std::double
   //if unsure, you probably mean std::uint8_t
   template< typename PIXEL_COMPONENT_TYPE = std::uint8_t >
   class ImageWrapper
   {
   public:

      //template< typename > //should be same type as PIXEL_COMPONENT_TYPE but gcc doesn't allow template shadowing
      //friend std::expected< ImageWrapper< PIXEL_COMPONENT_TYPE >, std::string > load_image_from_file_cimg( std::string const& fileName ); //factory friend function to access private c'tor

      using value_type = PIXEL_COMPONENT_TYPE; //The type of the size for each component of each pixel

      std::uint32_t getSizeBytes() const noexcept { return w * h * numComponents * bytesPerComponent; }

      std::int32_t w{ 0 }; // Width in pixels
      std::int32_t h{ 0 }; //height in pixels
      std::int32_t numComponents{ 0 }; // number of color channels per pixel
      std::int32_t bytesPerComponent{ sizeof(PIXEL_COMPONENT_TYPE) }; //size of each channel, in bytes -- this is the template param in CImg
      bool isInterleavedComp{ true }; //true if components are interlaved, ie RGB RGB RGB. If false, the components are planar RRR GGG BBB
      std::shared_ptr< PIXEL_COMPONENT_TYPE[] > dat = nullptr;

      ImageWrapper() = delete;
      ~ImageWrapper() = default;
      ImageWrapper( ImageWrapper const& toCopy ) = default;
      ImageWrapper( ImageWrapper&& toMove ) noexcept = default;
      ImageWrapper& operator=( ImageWrapper const& ) = default;
      ImageWrapper& operator=( ImageWrapper&& toMoveAssign ) noexcept = default;

      explicit ImageWrapper( std::int32_t w, std::int32_t h, std::int32_t numComp, 
         std::shared_ptr< PIXEL_COMPONENT_TYPE[] > dat, bool isInterleavedComp = true ) :
         w( w ), h( h ), numComponents( numComp ), bytesPerComponent( sizeof( PIXEL_COMPONENT_TYPE ) ), 
         isInterleavedComp( isInterleavedComp ), dat( dat ) {}

      //explicit ImageWrapper( cimg_library::CImg<PIXEL_COMPONENT_TYPE>&& takeOwnership );

      //use the Aftr::toString free function to toString an ImageWrapper
      std::string toString() const { return Aftr::toString( *this ); }
      ImageWrapperInfo getMetaInfo() const noexcept
         { return ImageWrapperInfo{ w, h, numComponents, bytesPerComponent, isInterleavedComp }; }
   };

   
   //----------------
   //STB Functions -- these use the header only STB library to read and write images. Good for reading
   // typical texture files for 3D Models.
   // 
   //Factory function that creates a shared_ptr to a Aftr_stb_image. The shared_ptr is reference counted, copy-able
   //and easily placed in std::function captures for execution on different threads.
   // By default, OpenGL Textures like 0,0 to be lower left instead of upper left, so the bool flips the image
   // vertically.
   // numChannelsInOutputImage parameter, if non-zero, will force the output image 
   // to have that many channels in the returned image.
   // To query an image about its internals before loading it, use stbi_info*
   Aftr_stb_imagePtr load_image_from_file( std::string const& fileName, bool flipVertically = true, 
                                           int numChannelsInOutputImage = 0 );

   //Writes in passed data to filename. No data is owned/freed by this function.
   //Because this uses stb_write, it ONLY supports 8 bits per channel (1 byte). Use CImg if need larger bytes per channel / component.
   std::expected<bool, std::string> write_image_grayscale_png( std::string const& filename, int width, int height,
                                                   unsigned char const* const data_nonOwningPtr, int numChannels = 1 );

   std::expected<bool, std::string> write_image_png( std::string const& filename, Aftr_stb_image const& img );

   //Uses STB to write the a bmp. This assumes each color channel is 8 bit
   std::expected<bool, std::string> write_image_bmp( std::string const& filename, Aftr::ImageWrapper_uint8t const& img );

   /// Convert a 3 channel RGB image to a grayscale luminance image with one color channel. If input not already interleaved, this method fails.
   /// Returns a single channel ImageWrapper if successful or an error string if failed.
   std::expected< ImageWrapper_uint8t, std::string > resizeRGB_stb( ImageWrapper_uint8t const& rgb_img,
                                                                    int widthPix, int heightPix );
   // End of STB Functions
   //----------------

   //----------------
   //Image Conversion Functions
   template< typename PIXEL_COMPONENT_TYPE >
   std::optional<Tex> create_Texture_from_Image( ImageWrapper<PIXEL_COMPONENT_TYPE> const& img );

   // Assumes in_out_Tex is  the same size, same number of components, same bytes per component as img. Essentially,
   // in_out_Tex must be memory-wise identical to to img. This can be done by first calling create_Texture_from_image(...)
   // and then subsequently calling this function using the Tex returned from create_Texture_from_image.
   template< typename PIXEL_COMPONENT_TYPE >
   bool update_Existing_Texture_from_Image( Tex& existing_tex_to_update_pix_data, ImageWrapper<PIXEL_COMPONENT_TYPE> const& img );
   //End of Image Conversion Functions
   //----------------

#ifdef AFTR_CONFIG_USE_CIMG
   //----------------
   //CImg Functions
   
   // The last parameter, bool layout_ImgData_as_interleaved, details how the pixel color data is layed out in memory.
   // Typically, OpenGL, OpenCV, and other image libraries store the pixel data as RGB, RGB, RGB (interleaves 
   // Red, then green, then blue) per pixel.
   // But CIMG stores pixel data as planar data, ie, RRRRRR..., GGGGGG...,  BBBBBB.... (all red pixels, then all green,
   // then all blue). If saving a ImageWrapper that is loaded as planar pixel data, then pass in false, 
   // otherwise, use true.
   template< typename PIXEL_COMPONENT_TYPE = std::uint8_t >
   std::expected< ImageWrapper< PIXEL_COMPONENT_TYPE >, std::string > load_image_from_file_cimg( std::string const& fileName, 
                                                                                   bool layout_ImgData_as_interleaved = true );


   //Automatically appends the .pgm to the end of fileName parameters.
   template< typename PIXEL_COMPONENT_TYPE = std::uint8_t >
   std::expected< bool, std::string > write_image_pgm( std::string const& fileName, 
                                                       ImageWrapper<PIXEL_COMPONENT_TYPE> const& img );

   // Saves image as a bmp. Can be either color or grayscale. Grayscale only supported by write_image_bmp_cimg.
   // CImg saving can be slower than STB image. If this causes a performance problem, consider Aftr::write_image_bmp.
   // However, STB can only write 8-bit channels, ie, RGB8.
   // The last parameter, bool isImgData_interleaved_rgb, details how the pixel color data is layed out in memory.
   // Typically, OpenGL, OpenCV, and other image libraries store the pixel data as RGB, RGB, RGB per pixel. But CIMG
   // stores pixel data as planar data, ie, RRRRRR..., GGGGGG...,  BBBBBB.... If saving a ImageWrapper that
   // is populated from Cimg, pass in false, otherwise, use true.
   template< typename PIXEL_COMPONENT_TYPE = std::uint8_t >
   std::expected< bool, std::string > write_image_bmp_cimg( std::string const& fileName, 
                                                            ImageWrapper<PIXEL_COMPONENT_TYPE> const& img );

   /// Convert a 3 channel RGB image to a grayscale luminance image with one color channel.
   /// Returns a single channel ImageWrapper if successful or an error string if failed.
   template< typename PIXEL_COMPONENT_TYPE = std::uint8_t >
   std::expected< ImageWrapper<PIXEL_COMPONENT_TYPE>, std::string > convertRGBtoLuminance( ImageWrapper<PIXEL_COMPONENT_TYPE> const& rgb_img );
   // End of CImg Functions, preprocessor may remove based on AFTR_CONFIG_USE_CIMG
   //----------------
#endif

} //namespace Aftr