/*
 *  This file is part of RawTherapee.
 *
 *  Copyright (c) 2004-2010 Gabor Horvath <hgabor@rawtherapee.com>
 *
 *  RawTherapee is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 * 
 *  RawTherapee is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with RawTherapee.  If not, see <http://www.gnu.org/licenses/>.
 */
#include "image16.h"
#include "imagefloat.h"
#include "image8.h"
#include <cstring>
#include <cstdio>
#include "rtengine.h"

using namespace rtengine;

Image16::Image16 () {
}

Image16::Image16 (int w, int h) {
    allocate (w, h);
}

Image16::~Image16 () {
}

void Image16::getScanline (int row, unsigned char* buffer, int bps) {

    if (data==NULL)
        return;

    if (bps==16) {
        int ix = 0;
        unsigned short* sbuffer = (unsigned short*) buffer;
        for (int i=0; i<width; i++) {
            sbuffer[ix++] = r(row,i);
            sbuffer[ix++] = g(row,i);
            sbuffer[ix++] = b(row,i);
        }
    }
    else if (bps==8) {
        int ix = 0;
        for (int i=0; i<width; i++) {
            buffer[ix++] = r(row,i) >> 8;
            buffer[ix++] = g(row,i) >> 8;
            buffer[ix++] = b(row,i) >> 8;
        }
    }
}

/*
 * void Image16::setScanline (int row, unsigned char* buffer, int bps, int minValue[3], int maxValue[3]);
 * has not been implemented yet, because as of now, this method is called for IIOSF_FLOAT sample format only
 */
void Image16::setScanline (int row, unsigned char* buffer, int bps, float *minValue, float *maxValue) {

    if (data==NULL)
        return;

     // For optimization purpose, we're assuming that this class never have to provide min/max bound
    assert(!minValue);

    switch (sampleFormat) {
    case (IIOSF_UNSIGNED_CHAR):
    {
        int ix = 0;
        for (int i=0; i<width; i++) {
            r(row,i) = (unsigned short)(buffer[ix++]) << 8;
            g(row,i) = (unsigned short)(buffer[ix++]) << 8;
            b(row,i) = (unsigned short)(buffer[ix++]) << 8;
        }
        break;
    }
    case (IIOSF_UNSIGNED_SHORT):
    {
        unsigned short* sbuffer = (unsigned short*) buffer;
        int ix = 0;
        for (int i=0; i<width; i++) {
            r(row,i) = sbuffer[ix++];
            g(row,i) = sbuffer[ix++];
            b(row,i) = sbuffer[ix++];
        }
        break;
    }
    default:
        // Other type are ignored, but could be implemented if necessary
        break;
    }
    /*
     * Not used for now
     *
     */
}

Image16* Image16::copy () {

  Image16* cp = new Image16 (width, height);
  copyData(cp);
  return cp;
}

void Image16::getStdImage (ColorTemp ctemp, int tran, Imagefloat* image, PreviewProps pp, bool first, procparams::ToneCurveParams hrp)
{

    // compute channel multipliers
    double drm, dgm, dbm;
    ctemp.getMultipliers (drm, dgm, dbm);
    float rm=drm,gm=dgm,bm=dbm;

    rm = 1.0 / rm;
    gm = 1.0 / gm;
    bm = 1.0 / bm;
    float mul_lum = 0.299*rm + 0.587*gm + 0.114*bm;
    rm /= mul_lum;
    gm /= mul_lum;
    bm /= mul_lum;

    int sx1, sy1, sx2, sy2;

    transform (pp, tran, sx1, sy1, sx2, sy2);

    int imwidth=image->width;   // Destination image
    int imheight=image->height; // Destination image
    if (((tran & TR_ROT) == TR_R90)||((tran & TR_ROT) == TR_R270)) {
        int swap = imwidth;
        imwidth=imheight;
        imheight=swap;
    }
    int maxx=width;  // Source image
    int maxy=height; // Source image
    int mtran = tran & TR_ROT;
    int skip = pp.skip;

    //if ((sx1 + skip*imwidth)>maxx) imwidth -- ; // we have a boundary condition that can cause errors

    // improve speed by integrating the area division into the multipliers
    // switched to using ints for the red/green/blue channel buffer.
    // Incidentally this improves accuracy too.
    float area=skip*skip;
    float rm2=rm;
    float gm2=gm;
    float bm2=bm;
    rm/=area;
    gm/=area;
    bm/=area;

    #define GCLIP( x ) Color::gamma_srgb(CLIP(x))

#ifdef _OPENMP
#pragma omp parallel
    {
#endif
    AlignedBuffer<float> abR(imwidth);
    AlignedBuffer<float> abG(imwidth);
    AlignedBuffer<float> abB(imwidth);
    float *lineR  = abR.data;
    float *lineG  = abG.data;
    float *lineB =  abB.data;

#ifdef _OPENMP
#pragma omp for
#endif
    // Iterating all the rows of the destination image
    for (int iy=0; iy<imheight; iy++) {
        if (skip==1) {
            // special case (speedup for 1:1 scale)
            // i: source image, first line of the current destination row
            int src_y=sy1+iy;

            // overflow security check, not sure that it's necessary
            if (src_y>=maxy)
                continue;

            for (int dst_x=0,src_x=sx1; dst_x<imwidth; dst_x++,src_x++) {
                // overflow security check, not sure that it's necessary
                if (src_x>=maxx)
                    continue;

                lineR[dst_x] = CLIP(rm2*r(src_y, src_x));
                lineG[dst_x] = CLIP(gm2*g(src_y, src_x));
                lineB[dst_x] = CLIP(bm2*b(src_y, src_x));
            }
        }
        else {
            // source image, first line of the current destination row
            int src_y=sy1+skip*iy;
            if (src_y>=maxy)
                continue;

            for (int dst_x=0,src_x=sx1; dst_x<imwidth; dst_x++,src_x+=skip) {
                if (src_x>=maxx)
                    continue;

                int src_sub_width = MIN(maxx-src_x, skip);
                int src_sub_height = MIN(maxy-src_y, skip);

                float rtot,gtot,btot; // RGB accumulators
                rtot=gtot=btot=0.;

                for (int src_sub_y=0; src_sub_y<src_sub_height; src_sub_y++)
                    for (int src_sub_x=0; src_sub_x<src_sub_width; src_sub_x++) {
                        rtot += r(src_y+src_sub_y, src_x+src_sub_x);
                        gtot += g(src_y+src_sub_y, src_x+src_sub_x);
                        btot += b(src_y+src_sub_y, src_x+src_sub_x);
                    }
                // convert back to gamma and clip
                if (src_sub_width == skip && src_sub_height == skip) {
                    // Common case where the sub-region is complete
                    lineR[dst_x] = CLIP(rm*rtot);
                    lineG[dst_x] = CLIP(gm*gtot);
                    lineB[dst_x] = CLIP(bm*btot);
                }
                else {
                    // computing a special factor for this incomplete sub-region
                    float area = src_sub_width*src_sub_height;
                    lineR[dst_x] = CLIP(rm2*rtot/area);
                    lineG[dst_x] = CLIP(gm2*gtot/area);
                    lineB[dst_x] = CLIP(bm2*btot/area);
                }
            }
        }

        if      (mtran == TR_NONE)
            for (int dst_x=0,src_x=sx1; dst_x<imwidth; dst_x++,src_x+=skip) {
                image->r(iy, dst_x) = lineR[dst_x];
                image->g(iy, dst_x) = lineG[dst_x];
                image->b(iy, dst_x) = lineB[dst_x];
            }
        else if (mtran == TR_R180)
            for (int dst_x=0; dst_x<imwidth; dst_x++) {
                image->r(imheight-1-iy, imwidth-1-dst_x) = lineR[dst_x];
                image->g(imheight-1-iy, imwidth-1-dst_x) = lineG[dst_x];
                image->b(imheight-1-iy, imwidth-1-dst_x) = lineB[dst_x];
            }
        else if (mtran == TR_R90)
            for (int dst_x=0,src_x=sx1; dst_x<imwidth; dst_x++,src_x+=skip) {
                image->r(dst_x, imheight-1-iy) = lineR[dst_x];
                image->g(dst_x, imheight-1-iy) = lineG[dst_x];
                image->b(dst_x, imheight-1-iy) = lineB[dst_x];
            }
        else if (mtran == TR_R270)
            for (int dst_x=0,src_x=sx1; dst_x<imwidth; dst_x++,src_x+=skip) {
                image->r(imwidth-1-dst_x, iy) = lineR[dst_x];
                image->g(imwidth-1-dst_x, iy) = lineG[dst_x];
                image->b(imwidth-1-dst_x, iy) = lineB[dst_x];
            }
    }
#ifdef _OPENMP
}
#endif
    #undef GCLIP
}

Image8* 
Image16::to8()
{
    Image8* img8 = new Image8(width,height);
    for ( int h = 0; h < height; ++h )
    {
        for ( int w = 0; w < width; ++w )
        {
            img8->r(h, w) = (unsigned char)( r(h,w) >> 8);
            img8->g(h, w) = (unsigned char)( g(h,w) >> 8);
            img8->b(h, w) = (unsigned char)( b(h,w) >> 8);
        }
    }
    return img8;
}

Imagefloat* 
Image16::tofloat()
{
    Imagefloat* imgfloat = new Imagefloat(width,height);
    for ( int h = 0; h < height; ++h )
    {
        for ( int w = 0; w < width; ++w )
        {
            imgfloat->r(h,w) = (float)r(h,w);
            imgfloat->g(h,w) = (float)g(h,w);
            imgfloat->b(h,w) = (float)b(h,w);
        }
    }
    return imgfloat;
}

// Parallized transformation; create transform with cmsFLAGS_NOCACHE!
void Image16::ExecCMSTransform(cmsHTRANSFORM hTransform) {
    //cmsDoTransform(hTransform, data, data, planestride);

    // LittleCMS cannot parallelize planar setups -- Hombre: LCMS2.4 can! But it we use this new feature, memory allocation have to be modified too
    // so build temporary buffers to allow multi processor execution
#ifdef _OPENMP
#pragma omp parallel
#endif
{
        AlignedBuffer<unsigned short> buffer(width*3);

#ifdef _OPENMP
#pragma omp for schedule(static)
#endif
    for (int y=0; y<height; y++) {
        unsigned short *p=buffer.data, *pR=r(y), *pG=g(y), *pB=b(y);

        for (int x=0; x<width; x++) {
            *(p++) = *(pR++); *(p++) = *(pG++); *(p++) = *(pB++);
        }

        cmsDoTransform (hTransform, buffer.data, buffer.data, width);

        p=buffer.data; pR=r(y); pG=g(y); pB=b(y);
        for (int x=0; x<width; x++) {
            *(pR++) = *(p++); *(pG++) = *(p++); *(pB++) = *(p++);
        }
} // End of parallelization
    }
}
