/*******************************************************************************
* Copyright 2003-2020 Intel Corporation.
*
* This software and the related documents are Intel copyrighted  materials,  and
* your use of  them is  governed by the  express license  under which  they were
* provided to you (License).  Unless the License provides otherwise, you may not
* use, modify, copy, publish, distribute,  disclose or transmit this software or
* the related documents without Intel's prior written permission.
*
* This software and the related documents  are provided as  is,  with no express
* or implied  warranties,  other  than those  that are  expressly stated  in the
* License.
*******************************************************************************/

#ifndef __BASE_IMAGE_H__
#define __BASE_IMAGE_H__

#include "base.h"

#ifndef BMP_IMAGE_MAX_SIZE
#define BMP_IMAGE_MAX_SIZE  4096 * 3072 * 4 * 1 /*resolution * num_of_channels * sample_size*/
#endif

#define BMP_GRAYSCALE_FILE "example_gray.bmp"
#define BMP_RGB_FILE "example_rgb.bmp"

enum ColorFormat
{
    CF_UNKNOWN = 0,
    CF_GRAY,
    CF_RGB,
    CF_BGR,
    CF_RGBA,
    CF_BGRA
};
static const char* colorFormatName[]    = {"UNKNOWN", "Grayscale", "RGB", "BGR", "RGBA", "BGRA"};

unsigned int GetSamplesNum(ColorFormat colorFormat);
bool         HasAlpha(ColorFormat colorFormat);

enum SampleFormat
{
    ST_UNKNOWN = 0,
    ST_8U,
    ST_8S,
    ST_16U,
    ST_16S,
    ST_32U,
    ST_32S,
    ST_32F,
    ST_64F
};
static const char* sampleFormatName[]  = {"UNKNOWN", "8U", "8S", "16U", "16S", "32U", "32S", "32F", "64F"};

unsigned int GetSampleSize(SampleFormat sampleFormat);

enum FileFormat
{
    FF_UNKNOWN = 0,
    FF_RAW,
    FF_BMP,
    FF_C            // Saves image as C array
};
static const char* fileFormatName[]  = {"UNKNOWN", "RAW", "BMP"};

enum MemPattern
{
    MP_NONE = 0,
    MP_WRAP
};

static const unsigned char defPalette[][4] =
{
    {0,255,0,255},
    {0,0,255,255},
    {0,128,255,255},
    {0,255,255,255},
    {255,128,0,255},
    {255,255,0,255},
    {255,0,0,255},
    {255,0,255,255},
    {255,255,255,255}
};

/*
// Image class
*/
class Image
{
public:
    Image();
    Image(Size size, unsigned int samples = 1, SampleFormat sampleFormat = ST_8U);
    Image(Size size, ColorFormat color, SampleFormat sampleFormat = ST_8U);
    Image(const char *pFileName, ColorFormat dstColor = CF_UNKNOWN, SampleFormat dstFormat = ST_UNKNOWN);
    virtual ~Image();

    Status ReadHeader(const char *pFileName);
    Status Read(const char *pFileName, ColorFormat dstColor = CF_UNKNOWN, SampleFormat dstFormat = ST_UNKNOWN);

    // Saves image based on extension name. Supported extensions are: .bmp - FF_BMP, .raw - FF_RAW, .c - FF_C
    // fileFormat overrides extension name
    Status Write(const char *pFileName, FileFormat fileFormat = FF_UNKNOWN);

    // Allocate image
    Status Alloc(Size size, unsigned int samples, SampleFormat sampleFormat = ST_8U);
    Status Alloc(Size size, ColorFormat color, SampleFormat sampleFormat = ST_8U);
    Status Alloc();

    // Releases image buffer and resets pointers
    Status Release();

    // Releases image and resets all vars to initial values
    Status Reset();

    // Attach external buffer to the image. All existing data will be released
    Status AttachBuffer(Size size, unsigned int samples, SampleFormat sampleFormat, void *pBuffer, size_t step);
    Status AttachBuffer(Size size, ColorFormat color, SampleFormat sampleFormat, void *pBuffer, size_t step);

    // convert image to dst color format, if dst is NULL it will be inplace conversion
    Status ConvertColor(ColorFormat dstColor, Image *pDstImage = 0, double alphaVal = MAX_DOUBLE_ABS);

    // convert image to dst pixel format, if dst is NULL it will be inplace conversion
    Status ConvertSamples(SampleFormat dstFormat, Image *pDstImage = 0, bool bRescale = true, bool bDetectMinMax = false, double min = 0, double max = 0);

    // fills buffer with sin pattern
    Status GenerateSin(double frequency = 2, double amplitude = 1, double shiftX = 0, double shiftY = 0);

    Status DrawPixel(long long x, long long y, unsigned char color[4]);
    Status DrawRect(Rect rect, unsigned char color[4], unsigned int thickness = 1);
    Status DrawLine(Vect vect, unsigned char color[4]);

    // Copies image data to another image buffer, allocates border around ROI if necessary
    Status CopyTo(Image &dst, BorderSize borderSize = BorderSize());

    // Maps image data to another image buffer. Image sizes may differ
    Status MapTo(Image &dst, Point dstOffset = Point(), Point srcOffset = Point());

    // Sets image channel to specified value
    Status Set(double val, int channel = -1);

    // Sets image data to specified pattern
    Status Set(Image val, int dstChannel = - 1, int srcChannel = 0);

    // Sets border around image without changing memory size
    Status BorderAdd(BorderSize borderSize);
    Status BorderSub(BorderSize borderSize);
    Status BorderSet(BorderSize borderSize);

    // Sets ROI for the current image
    Status RoiSet(Rect roi);
    Image  GetRoiImage(Rect roi);

    void   SetPixel(long long x, long long y, unsigned int channel, double value);
    double GetPixel(long long x, long long y, unsigned int channel);

    // Image structure comparison
    bool Compare(const Image &ref);
    bool operator==(const Image&);
    bool operator!=(const Image&);

    // Image content comparison
    // Inf Norm Difference
    std::vector<double> FindMaxDiff(Image &ref);

    // assignment overload
    Image(const Image&);
    Image& operator=(const Image&);

    inline void* ptr(long long y = 0, long long x = 0, unsigned int c = 0, int pattern = MP_NONE)
    {
        if(pattern == MP_WRAP)
        {
            x = x%this->m_size.width;
            y = y%this->m_size.height;
            c = c%this->m_samples;
        }
        return (this->m_pPointer + y*this->m_step + x*this->m_sampleSize*this->m_samples + c*this->m_sampleSize);
    }

    inline bool IsInitialized()
    {
        if(!m_pPointer || !m_size.width || !m_size.height || !m_samples || !m_step || !m_sampleSize || m_sampleFormat == ST_UNKNOWN)
            return false;
        return true;
    }

    inline bool IsFloat()
    {
        if(m_sampleFormat == ST_32F || m_sampleFormat == ST_64F)
            return true;
        return false;
    }

    inline long long GetByteSize()
    {
        if(!this->IsInitialized())
            return 0;
        return (m_size.width*m_size.height*m_sampleSize*m_samples);
    }

public:
    size_t         m_imageSize;         // actual size of image buffer relative to m_pPointer
    size_t         m_bufferSize;        // full size of buffer
    unsigned int   m_bufferAlignment;   // buffer allocation alignment
    size_t         m_step;              // size of buffer row in bytes
    unsigned int   m_stepAlignment;     // alignment of step value
    Size           m_size;              // Image size in pixels
    ColorFormat    m_color;             // samples pattern
    unsigned int   m_samples;           // amount of samples in pixel
    SampleFormat   m_sampleFormat;      // data format of a sample
    unsigned int   m_sampleSize;        // size of one sample
    BorderSize     m_border;            // size of border around the current ROI

private:
    SharedObject   m_buffer;
    unsigned char *m_pPointer;         // pointer to ROI start point
};


static Size tileSetDefaults(Size tileSize, Size imageSize, unsigned int threads = 1)
{
    if(threads > 1)
    {
        if(tileSize.width < 0)
            tileSize.width  = imageSize.width;
        else if(!tileSize.width)
            tileSize.width  = imageSize.width;
        if(tileSize.height < 0)
            tileSize.height = (imageSize.height + threads - 1)/(threads);
        else if(!tileSize.height)
            tileSize.height = imageSize.height;
    }
    else
    {
        if(tileSize.width < 0)
            tileSize.width  = (long long)(imageSize.width/5.777);
        else if(!tileSize.width)
            tileSize.width  = imageSize.width;
        if(tileSize.height < 0)
            tileSize.height  = (long long)(imageSize.height/5.777);
        else if(!tileSize.height)
            tileSize.height = imageSize.height;
    }
    return tileSize;
}

#endif
