#ifndef Image_h_DEFINED
#define Image_h_DEFINED


#include <stdio.h>
#include <stdlib.h>
#include "../const.h"
#include "Init_para.h"

#ifdef AIBO_YES
#include <OPENR/OFbkImage.h>
#include <OPENR/ODataFormats.h>
#include <OPENR/OSyslog.h>
#include "ExpandLayer.h"
#endif



struct Image {
    byte**    data;     // graylevel values for each pixel
    int       width;    // width of image
    int       height;   // height of image
    int       offsetX;  // offset in the x direction with respect to the original image
    int       offsetY;  // offset in the y direction with respect to the original image
    
    
    // Initialize an empty image
    Image() {
        width   = 0;
        height  = 0;
        offsetX = 0;
        offsetY = 0;
        data = NULL;
    }

    // Initialize a black image with given width, height and offsets
    Image(int w, int h, int ox, int oy) {
        width   = w;
        height  = h;
        offsetX = ox;
        offsetY = oy;
        

        data = (byte**) calloc(width, sizeof(byte*));
        for (int i = 0; i < width; i++)
            data[i] = (byte*) calloc(height, sizeof(byte));


    }

    // Initialize an image with given width and height, reading the graylevel values from file
    Image(char* path, int w, int h) {
        width = w;
        height = h;
        offsetX = 0;
        offsetY = 0;
        
        // each row must have a muliple-of-four number of bytes!
        int r = (width * 3) % 4;


        data = (byte**) calloc(width, sizeof(byte*));
        for (int i = 0; i < width; i++)
            data[i] = (byte*) calloc(height, sizeof(byte));


        FILE* f = fopen(path, "rb");
        if (f == 0) {
           PRN(("can't open %s", path));
        }

        // Reading input file into image matrix
        fpos_t pos = BMP_HEADER_OFFSET;
        int x, y;
        byte pixels[3];

        fsetpos(f, &pos);
        for (y = height - 1 ; y >= 0; y--) {
            for (x = 0; x < width; x++) {
                fread(pixels, 1, 3, f);
                data[x][y] = pixels[0];
            }
            // Padding...
            if (r) {
               for (x = 0; x < 4 - r; x++) {
                   fread(&pixels[0], 1, 1, f);
               }
            }
        }
        fclose(f);
    }

    #ifdef AIBO_YES

    // Initialize an image with given resolution (layer), reading the graylevel values from file
    Image(char* path, OFbkImageLayer layer)  {

        if (layer == ofbkimageLAYER_C)
           data = NULL;

        if (layer == ofbkimageLAYER_L) {
           width     = WIDTH_L;
           height    = HEIGHT_L;
        }
        if (layer == ofbkimageLAYER_M) {
           width     = WIDTH_M;
           height    = HEIGHT_M;
        }
        if (layer == ofbkimageLAYER_H) {
           width     = WIDTH_H;
           height    = HEIGHT_H;
        }

        offsetX = 0;
        offsetY = 0;

        data = (byte**) calloc(width, sizeof(byte*));
        for (int i = 0; i < width; i++)
            data[i] = (byte*) calloc(height, sizeof(byte));



        FILE* f = fopen(path, "rb");
        if (f == 0) {
           PRN(("can't open %s", path));
        }

        // Reading input file into image matrix
        fpos_t pos = BMP_HEADER_OFFSET;
        int x, y;
        byte pixels[3];

        fsetpos(f, &pos);
        for (y = height - 1 ; y >= 0; y--) {
            for (x = 0; x < width; x++) {
                fread(pixels, 1, 3, f);
                data[x][y] = pixels[0];
                }
        }
        fclose(f);

    }
    


    // Initialize an image with given resolution (layer), reading the graylevel values from the OFbkImageVectorData object, according to the chosen band
    Image(OFbkImageVectorData* imageVec, OFbkImageLayer layer, OFbkImageBand band) {

        if (layer == ofbkimageLAYER_L) {
           width     = WIDTH_L;
           height    = HEIGHT_L;
        }
        if (layer == ofbkimageLAYER_M || layer == ofbkimageLAYER_C) {
           width     = WIDTH_M;
           height    = HEIGHT_M;
        }
        if (layer == ofbkimageLAYER_H) {
           width     = WIDTH_H;
           height    = HEIGHT_H;
        }
        
        offsetX = 0;
        offsetY = 0;
        
        OFbkImageInfo* info = imageVec->GetInfo(layer);
        byte*      ofbkdata = imageVec->GetData(layer);
    
        OFbkImage band_image(info, ofbkdata, band);
        
        data = (byte**) calloc(width, sizeof(byte*));
        for (int i = 0; i < width; i++)
            data[i] = (byte*) calloc(height, sizeof(byte));
        
        int x, y;
        byte plane = 0x01; // we're using only the first CDT channel

        for (y = height - 1 ; y >= 0; y--) {
            for (x = 0; x < width; x++) {
                if (band != ofbkimageBAND_CDT)
                    data[x][y] = band_image.Pixel(x, y);
                else  {
                    if (band_image.Pixel(x, y) & plane)
                         data[x][y] = WHITE;
                    else
                         data[x][y] = BLACK;
                }
            }
        }


    }
    // Initialize an image (Y-plane) with maximum resolution (Expanded High Layer), reading the graylevel values from the OFbkImageVectorData object.
    Image(OFbkImageVectorData* imageVec) {

        int w, h, x, y;
        byte* img;

        OFbkImageInfo* info     = imageVec->GetInfo(ofbkimageLAYER_H);
        byte*          ofbkdata = imageVec->GetData(ofbkimageLAYER_H);
    
        OFbkImage yLLImg(info, ofbkdata, ofbkimageBAND_Y); // Y_LL
        OFbkImage yLHImg(info, ofbkdata, ofbkimageBAND_Y_LH);
        OFbkImage yHLImg(info, ofbkdata, ofbkimageBAND_Y_HL);
        OFbkImage yHHImg(info, ofbkdata, ofbkimageBAND_Y_HH);

        w = yLLImg.Width();
        h = yLLImg.Height();
        
        
        width  = 2 * w;
        height = 2 * h;
        offsetX = 0;
        offsetY = 0;
        
        data = (byte**) calloc(width, sizeof(byte*));
        for (int i = 0; i < width; i++)
            data[i] = (byte*) calloc(height, sizeof(byte));
    
        img = (byte*)malloc((2*w)*(2*h));

        for (y = 0; y < h; y++) {
            for (x = 0; x < w; x++) {
                //
                // yLH, yHL, yHH : offset binary [0, 255] -> signed int [-128, 127]
                //
                int yLL = (int)yLLImg.Pixel(x, y);
                int yLH = (int)yLHImg.Pixel(x, y) - 128;
                int yHL = (int)yHLImg.Pixel(x, y) - 128;
                int yHH = (int)yHHImg.Pixel(x, y) - 128;
    
                int a = yLL + yLH + yHL + yHH; // ypix11
                int b = 2 * (yLL + yLH);       // ypix11 + ypix01
                int c = 2 * (yLL + yHL);       // ypix11 + ypix10
                int d = 2 * (yLL + yHH);       // ypix11 + ypix00
                
                byte ypix00 = ClipRange(d - a);
                byte ypix10 = ClipRange(c - a);
                byte ypix01 = ClipRange(b - a);
                byte ypix11 = ClipRange(a);

                PutPixel(img, 2*w, 2*x,   2*y,   ypix00);
                PutPixel(img, 2*w, 2*x+1, 2*y,   ypix10);
                PutPixel(img, 2*w, 2*x,   2*y+1, ypix01);
                PutPixel(img, 2*w, 2*x+1, 2*y+1, ypix11);
            }
        }

        for (y = height - 1 ; y >= 0; y--) {
            for (x = 0; x < width; x++) {
                data[x][y] = *(img + (width) * y + x);

            }
        }
        
        free(img);

    }

    #endif


};

 // Functions prototypes


    bool Close(const Image& image, Image& closed);
    bool Open(const Image& image, Image& opened);
    bool Dilate(const Image& image, Image& dilated);
    bool Erode(const Image& image, Image& eroded);
    bool EraseSinglePixels(const Image& image, Image& result);

    bool LabelComponents(const Image& image, Image& image_label, int& number, int*& labels);
    bool GetComponent(const Image& image_label, int label, Image& component);
    bool GetComponentGL(const Image& image_label, const Image& original, Image& component, int label);
    int GetMajorComp(const Image& label_image);
    bool MergeComp(const Image& comp1, const Image& comp2, const Image& rif, Image& merge);
    int Area(const Image& image);
    bool CentroidX(const Image& image, int& cx);
    bool CentroidY(const Image& image, int& cy);
    bool Centroid(const Image& image, int*& centroid);
    bool WeightedCentroid(const Image& image, int*& centroid);
    bool BoundingBox(const Image& image, int mode, int*& extremes);
    
    bool Background(const Image& image, Image& background);
    bool And(const Image& im1, const Image& im2, Image& result);
    bool Negative(const Image& image, Image& neg);
    bool Mean(const Image& image, Image& filtered);
    bool SobelX(const Image& image, Image& sobel_image);
    bool SobelY(const Image& image, Image& sobel_image);
    bool GradientImage(const Image& image, Image& gradient_image);
    bool GradientImageWithDirection(const Image& image, Image& gradient_image, double**& directions);
    bool Threshold(const Image& image, int th_min, int th_max, Image& th_image);
    bool ThresholdPerc(const Image& image, double perc, Image& th_image);
    bool ThresholdPerc(const Image& image, Image& th_image, double perc, int starting_gl, int ending_gl);
    bool ThresholdPerc(const Image& image, Image& th_image, double perc, int starting_gl, int ending_gl, int& th);

    bool HorProjection(const Image& image, int*& proj);
    bool VerProjection(const Image& image, int*& proj);
    bool BinaryHorProjection(const Image& image, int*& proj);
    bool BinaryVerProjection(const Image& image, int*& proj);
    bool Histogram(const Image& image, int*& histogram);
    bool HistogramWithoutBorder(const Image& image, int*& histogram);
    int Brightness(const Image& image);
    int Contrast(const Image& image);

    bool FindFace(const Image& clustered, Image& skin_map, int*& ext, int*& centroid,
                  const Init_para& parameters, int serialNumber);
    bool FindFaceCentroid(const Image& clustered, int*& centroid, const Init_para& parameters, int serialNumber);
    bool FindFaceAndMouthCentroid(const Image& clustered, const Image& cb, const Image& cr, 
                                  int*& face_centroid, int*& mouth_centroid, const Init_para& parameters, int serialNumber);

    bool MouthMap(const Image& cb, const Image& cr, Image& mouth, int dim, const Init_para& parameters, int serialNumber);
    bool FindMouth(int* ext, const Image& cb, const Image& cr, Image& mouth, int dim, const Init_para& parameters, int serialNumber);
    bool FindEye(const Image& skin_map, const Image& mouth, Image& eye, int which, const Init_para& parameters, int serialNumber);
    bool GetEyebrow(const Image& eye, const int* face_bb, const Image& im_exp, Image& eyebrow, const Init_para& parameters, int serialNumber);
    bool GetBothEyebrows(const Image& eye_left, const Image& eye_right, const int* face_bb, const Image& im_exp,
                         Image& eyebrow_left, Image& eyebrow_right, const Init_para& parameters, int serialNumber);
    bool FindCorners(const Image& image, int*& ext, const Init_para& parameters, int serialNumber);

    bool CropImage(int* ext, const Image& image, Image& cropped, int border);
    bool CopyImage(const Image& from, Image& to);
    void FreeImg(Image& image);
    
    bool ShowPoints(char* path, const Image& image, int* points);
    bool ShowBoundingBox(char* path, const Image& image, int* points);
    bool ShowComp(char* path, const Image& image, const Image& comp);




#endif // Image_h_DEFINED
