#ifndef _P1_IMAGE_SDK_BITMAP_IMAGE_H_
#define _P1_IMAGE_SDK_BITMAP_IMAGE_H_

#include <cstdint>
#include <memory>
#include "C_P1ImageCommonLinkage.h"
#include "C_P1ImageCommonTypes.h"

#include "P1ImageRect.hpp"

namespace P1
{
namespace ImageSdk
{
    class BitmapImagePimpl; // forward decl

    enum class DualExposureOutput
    {
        none = 0, // Default: Take first frame and warn user about existed second frame
        firstFrame,
        secondFrame,
        noiseReduction,
        HDR,
        HDR2,
        HDR3,
    };

    /** @brief Supported RGB bitmap formats */
    enum class BitmapFormat
    {
        Unknown = 0,       
        Rgb24=1,         /**< Each pixel is 3 bytes, 8-8-8 RGB */
        Rgb48=2,         /**< Each pixel is 6 bytes, 16-16-16 RGB */
        Argb32=3,        /**< Each pixel is 4 bytes, 8-8-8-8 RGB */
        Grey8=4,         /**< Each pixel is 1 byte, 8 Greytone */
        Grey16=5,        /**< Each pixel is 2 bytes, 16 Greytone */
        Argb64=6,         /**< Each pixel is 8 bytes, 16-16-16-16 RGB */
        Bayer16=7,
        Float32=8
    };

    /** @brief Supported Bayer data formats */
    enum class BayerFormat
    {
        Unknown = 0,

        Bayer16         /**< Each Bayer pixel is 16-bit, little endian */
    };



    class EXPORT_API BitmapBase
    {
        friend class RawImage;
#if STITCHING_ENABLED
        friend class Stitching;
        friend class Stacking;
#endif
    protected:
        MSVC_ALLOW_PUBLIC_STL(std::shared_ptr<BitmapImagePimpl> mPimpl);
    public:

        /** @brief Size of the data array, in bytes */
        int64_t ByteSize() const;

        uint32_t Stride() const;

        uint32_t BitsPerSample() const;
        uint32_t SamplesPerPixel() const;

        /** @brief 1-dimensional array of pixel data, in row-major order */
        MSVC_ALLOW_PUBLIC_STL(std::shared_ptr<uint8_t> Data());
    };


    /**
     * @brief In-memory representation of an RGB bitmap image
     * 
     */
    class EXPORT_API BitmapImage : public BitmapBase
    {
        friend class RawImage;
        friend class SensorBayerOutput;
#if STITCHING_ENABLED
        friend class Stitching;
        friend class Stacking;
#endif      
    public:
        BitmapImage() {}

        /** @brief The pixel format of the @ref Data array */
        BitmapFormat Format() const;

        ColorSpace GetColorSpace() const;
        
        /** @brief Image Width in pixels */
        uint32_t Width() const;

        /** @brief Image height in pixels */
        uint32_t Height() const;

        /** @brief If this image is cropped from a larger source image, 
         * this is the crop in source coordinates */
        Rect SourceCrop() const;
    };

    template <typename T>
    class EXPORT_API MutableBitmapImage : public BitmapImage
    {
    public:
        MutableBitmapImage(int width, int height, BitmapFormat format, int alignedBits = 0, std::allocator<uint8_t> alloc = std::allocator<uint8_t>());

        T* operator ()(int x, int y);
    };
    typedef MutableBitmapImage<float> MutableBitmapImageFloat;
    typedef MutableBitmapImage<uint8_t> MutableBitmapImageU8;
    typedef MutableBitmapImage<uint16_t> MutableBitmapImageU16;



    /**
     * @brief In-memory representation of the image sensor Bayer output data
     */
    class EXPORT_API SensorBayerOutput : public BitmapBase
    {
        bool mBlackSubtracted = false;
        bool mBlackClampCalibrated = true;

        friend class RawImage;
    public:


        /** @brief The pixel format of the @ref Data array */
        BayerFormat Format() const;

        /** @brief The area of the sensor in pixels, that is exposed to light */
        Rect ActiveArea() const;

        /** @brief Full sensor width in pixels, including borders not exposed to light */
        uint32_t FullWidth() const;

        /** @brief Full sensor height in pixels, including borders not exposed to light */
        uint32_t FullHeight() const;

        // Modification
        void OverwriteData(BitmapImage* refBitmap);

        void Transform(float a, float b, float c, int axis); // Transform with 2nd order polynomial a * x^2 + b * x + c
        void Transform(float ax, float bx, float cx, float ay, float by, float cy); // Transform with 2nd order polynomial (ax * x^2 + bx * x + cx)*(ay * y^2 + by * y + cy)
        void GainAndOffset(BitmapImage* gainBitmap, BitmapImage* offsetBitmap);
    };

}
}

#endif //_P1_IMAGE_SDK_BITMAP_IMAGE_H_
