// $Id: griditem.cpp,v 1.4 2008/03/06 21:18:58 hvengel Exp $
//  
//  Lttle cms Profiler
//  Copyright (C) 1998-2001 Marti Maria
// Copyright (C) 2005 - 2007 Hal Engel
//
// THIS SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND,
// EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY
// WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
//
// IN NO EVENT SHALL THE DEVELOPERS BE LIABLE FOR ANY SPECIAL, INCIDENTAL,
// INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND,
// OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
// WHETHER OR NOT ADVISED OF THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF
// LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
// OF THIS SOFTWARE.
//
// This program 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.
// 
// This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
//
// Version 1.11

#include "lprofmain-priv.h"
#include "lprofmain.h"
#include <lcmsprf.h>

#include <qpainter.h>
#include <QFile>
 
// ----- The grid item --------------------------------------------------

GridItem::GridItem( Q3Canvas *canvas, QString Template, int SafeFrame )
    : Q3CanvasRectangle( 10, 10, canvas->width() - 10, canvas->height() - 10, canvas)
{
    // qDebug("GridItem::GridItem");
    SafeFrameFactor = SafeFrame / 100.;
    setBrush( Qt::NoBrush) ;

    LCMSHANDLE hIT8 = cmsxIT8LoadFromFile(Template.local8Bit());

    if (hIT8) 
    {
        int nRects = (int) cmsxIT8GetPropertyDbl(hIT8, "NUMBER_OF_SETS");
        char PatchName[256];

        for (int i=0; i < nRects; i++) 
        {
            if (cmsxIT8GetDataSetByPos(hIT8, 0, i, PatchName, 256)) 
            {

                GridLayout Patch;

                double x, y, xSize, ySize;

                cmsxIT8GetDataSetDbl(hIT8, PatchName, "POS_X", &x);
                cmsxIT8GetDataSetDbl(hIT8, PatchName, "POS_Y", &y);
                cmsxIT8GetDataSetDbl(hIT8, PatchName, "SIZE_X", &xSize);
                cmsxIT8GetDataSetDbl(hIT8, PatchName, "SIZE_Y", &ySize);

                strcpy(Patch.p.Name, (const char *) PatchName);
                Patch.rect.setRect(int(x), int(y), int(xSize), int (ySize));

                Layout.append(Patch);

            }
        }

        cmsxIT8Free(hIT8);
    }

    setZ(10);
    show();
}



static
        void DoDeflateRect(QRect& rect, int sx, int sy)
{
    // qDebug("DoDeflateRect");
    rect.setRect(rect.x() + sx, rect.y() + sy,
                 rect.width()-sx*2, rect.height()-sx*2);
}


void GridItem::ComputePatchRects(QRect& OurViewport, GridLayout* Patch,
                                 QRect& Frame, QRect& HotZone)
{
    QRect Scaled;

    // qDebug("GridItem::ComputePatchRects");
    Patch ->ScaleTo(OurViewport.width(), OurViewport.height(), Scaled);

    int translateX = OurViewport.x();
    int translateY = OurViewport.y();

    Scaled.moveBy(translateX, translateY);

    Frame = Scaled;

    int SafeFrame = (int) ((double) Scaled.width() * SafeFrameFactor / 2.);

    DoDeflateRect(Scaled, SafeFrame, SafeFrame);

    HotZone = Scaled;
}


// Compute our viewport extension

void GridItem::ComputeViewport(QRect& OurViewport, double sx, double sy)
{

    int x;
    int y;
    int w;
    int h;

    x=(int) (min(Marks.ll_x, Marks.ur_x) * sx);
    y=(int) (min( Marks.ur_y, Marks.ul_y) * sy);
    w=(int) (mmax(Marks.ur_x, Marks.lr_x) * sx - x);
    h=(int) (mmax(Marks.lr_y, Marks.ll_y) * sy - y);
    OurViewport.setRect(x, y, w, h);
}


void GridItem::drawShape( QPainter &p )
{
    QRect OurViewport;

    // qDebug("GridItem::drawShape");

    ComputeViewport(OurViewport, 1.0, 1.0);

    if (corners_ready) 
    {
        QList<GridLayout>::iterator itEnd = Layout.end();
        for ( QList<GridLayout>::iterator it = Layout.begin(); it != itEnd; ++it)
        {
            QRect Frame, HotZone;

            // Scale to our viewport
            ComputePatchRects(OurViewport, &(*it), Frame, HotZone);

            // Draw
            // p.setPen( Qt::white );
            // p.drawRect(Frame);

            p.setPen( Qt::green );
            p.drawRect(HotZone);     
        }  // end for
    } // enf if

    // Do this allways to mark corners but only mark corners after the user
    // clicks to add a mark

    p.setPen( Qt::red );

    // upper right corner only draw the corner mark of the user has clicked
    if (Marks.ur_x<measure_view_H && Marks.ur_y>0) {
        p.drawLine(Marks.ur_x, Marks.ur_y, Marks.ur_x - 50, Marks.ur_y);
        p.drawLine(Marks.ur_x, Marks.ur_y, Marks.ur_x, Marks.ur_y + 50);
    }

    // upper left corner
    if (Marks.ul_x>0 && Marks.ul_y>0) {
        p.drawLine(Marks.ul_x, Marks.ul_y, Marks.ul_x + 50, Marks.ul_y);
        p.drawLine(Marks.ul_x, Marks.ul_y, Marks.ul_x, Marks.ul_y + 50);
    }

    // lower right corner
    if (Marks.lr_x>0 && Marks.lr_y<measure_view_V) {
        p.drawLine(Marks.lr_x, Marks.lr_y, Marks.lr_x - 50, Marks.lr_y);
        p.drawLine(Marks.lr_x, Marks.lr_y, Marks.lr_x, Marks.lr_y - 50);
    }

    // lower left corner
    if (Marks.ll_x<measure_view_H && Marks.ll_y<measure_view_V) {
        p.drawLine(Marks.ll_x, Marks.ll_y, Marks.ll_x + 50, Marks.ll_y);
        p.drawLine(Marks.ll_x, Marks.ll_y, Marks.ll_x, Marks.ll_y - 50);
    }
}


void GridItem::Pick(QString InputProfileName, QImage& Image, double sx, double sy )
{
    // qDebug("GridItem::Pick");
    double mult;

    clippedPatches = 0.0;
    patchCount = 0.0;
    QRect OurViewport;
    cmsHTRANSFORM hInputXFORM;
    cmsHPROFILE hLab = NULL, hInputProfile = NULL;

    if (InputProfileName.isEmpty())
        hInputXFORM = NULL;
    else
    {
        QFile inProf(InputProfileName);
        if (inProf.exists()) 
        {
            hLab = cmsCreateLabProfile(NULL);
            hInputProfile = cmsOpenProfileFromFile(InputProfileName.toLocal8Bit(), "r");
            hInputXFORM   = cmsCreateTransform(hInputProfile, TYPE_RGB_DBL,
                                            hLab, TYPE_Lab_16, INTENT_ABSOLUTE_COLORIMETRIC,
                                            cmsFLAGS_NOTPRECALC);
        }
        else 
            hInputXFORM = NULL;
    }
    ComputeViewport(OurViewport, sx, sy);
    vigra::DRGBImage in(image_data->width(), image_data->height());
    vigra::importImage(*image_data, destImage(in));
    vigra::DRGBImage::Iterator point=in.upperLeft();

    if (uint8lprof) mult=1.0;
    else if (int16lprof) mult= 255.0/65535.0;
    else if (int32lprof) mult = 255.0/4294967295.0;
    else mult = 255.0;  // floating point type images
    
    QList<GridLayout>::iterator itEnd = Layout.end();
    for ( QList<GridLayout>::iterator it = Layout.begin(); it != itEnd; ++it )
    {
        // Scale to our viewport
        patchCount++;
        QRect Frame, HotZone;

        GridLayout& Patch ( *it );
        ComputePatchRects(OurViewport, &Patch, Frame, HotZone);

        // Ok, take

        Statistic r, g, b;  // These are doubles

        int xstart = HotZone.x();
        int ystart = HotZone.y();
        int xsize  = HotZone.width();
        int ysize  = HotZone.height();

        Patch.p.pos_x = xstart+xsize/2.0;
        Patch.p.pos_y = ystart+ysize/2.0;

        Patch.p.Colorant.RGB[0] = 0;
        Patch.p.Colorant.RGB[1] = 0;
        Patch.p.Colorant.RGB[2] = 0;
        Patch.p.dwFlags         = 0;

        if ((xstart < 0) || (ystart < 0) ||
            ((xstart + xsize) > Image.width()) ||
            ((ystart + ysize > Image.height())))
            continue;

        // debug code to see where hot zone patches
        // are really placed

        // printf("PICK %s [%d %d] [%d %d]\n",

        //      Patch->p.Name, xstart, ystart,

        //   xstart+xsize-1, ystart+ysize-1);

        // qDebug("interating image");

        double rv;
        double gv;
        double bv;
        int npixels;

        for (int i = npixels = 0; i < ysize; i++) 
        {
            for (int j=0; j < xsize; j++) 
            {
                rv = point(xstart+j, ystart+i).red();
                gv = point(xstart+j, ystart+i).green();
                bv = point(xstart+j, ystart+i).blue();
                // Fix for VIGRA bug
                if (int16lprof)
                {
                // This code is a work around for a bug in VIGRA
                    if (rv < 0.0)rv = rv + 65536.0;
                    if (gv < 0.0) gv = gv + 65536.0;
                    if (bv < 0.0) bv = bv + 65536.0;
                }
                else if (int32lprof)
                {
                    // This code is a work around for a bug in VIGRA
                    if (rv < 0.0) rv = rv + 4294967296.0;
                    if (gv < 0.0) gv = gv + 4294967296.0;
                    if (bv < 0.0) bv = bv + 4294967296.0;
                }
                // if (rv*mult > 255.0 || rv*mult<0.0)
                //  qDebug("red is out of range");
                r.Add(rv*mult); g.Add(gv*mult); b.Add(bv*mult);
                npixels++;
            }
        }
        Patch.p.dwFlags = PATCH_HAS_POS|PATCH_HAS_PIXELS|PATCH_HAS_RGB|
                          PATCH_HAS_STD_RGB|PATCH_HAS_CHISQ;

        Patch.p.Colorant.RGB[0] = r.Mean();
        Patch.p.Colorant.RGB[1] = g.Mean();
        Patch.p.Colorant.RGB[2] = b.Mean();

        Patch.p.Stdev.RGB[0] = r.Std();
        Patch.p.Stdev.RGB[1] = g.Std();
        Patch.p.Stdev.RGB[2] = b.Std();

        Patch.p.npixels = npixels;

        Patch.p.ChiSq = (r.Std() + g.Std() + b.Std()) / 3.0;
        
        // check for clipping
        if (r.Mean() > (255.0 - r.Std()))
            clippedPatches++;
        if (g.Mean() > (255.0 - g.Std()))
            clippedPatches++;
        if (b.Mean() > (255.0 - b.Std()))
            clippedPatches++;
        
        if (hInputXFORM) 
        {
            double divisor;
            double RGB[3];
            WORD LabEncoded[3];

            if (uint8lprof) divisor = 255.0;
            else if (int16lprof) divisor = 65536.0;
            else if (int32lprof) divisor = 4294967296.0;

            if (uint8lprof | int16lprof | int32lprof)
            {
                // convert to floating point encoding if needed
                RGB[0] = Patch.p.Colorant.RGB[0]/divisor;
                RGB[1] = Patch.p.Colorant.RGB[1]/divisor;
                RGB[2] = Patch.p.Colorant.RGB[2]/divisor;
            }
            else
            {
                RGB[0] = Patch.p.Colorant.RGB[0];
                RGB[1] = Patch.p.Colorant.RGB[1];
                RGB[2] = Patch.p.Colorant.RGB[2];
            }

            cmsDoTransform(hInputXFORM, RGB, LabEncoded, 1);
            cmsLabEncoded2Float(&Patch.p.Lab, LabEncoded);
            cmsLab2XYZ(NULL, &Patch.p.XYZ, &Patch.p.Lab);
        }
    }
    
    if (hInputXFORM) 
    {
        cmsDeleteTransform(hInputXFORM);
        cmsCloseProfile(hInputProfile);
        cmsCloseProfile(hLab);
    }
    // qDebug("GridItem::Pick done");
}


int GridLayout::Scale(int n, int amax)
{
    // qDebug("Gridlatout::Scale");
    return (int) (((double) (n*amax)/LAYOUT_MAX)+.5);
}


// This is where the template patches are layed out over the IT8 image.

void GridLayout::ScaleTo(int w, int h, QRect& Scaled)
{
    QPoint A, B, C, D;
    int length_w, length_h, offset_x, offset_y, offset_x2, offset_y2, dDA, dBA, dCD, dCB, adj_x, adj_y;

    A=grid_corners.point(0);
    B=grid_corners.point(1);
    C=grid_corners.point(2);
    D=grid_corners.point(3);

    adj_x=min(A.x(),D.x());
    A.setX(A.x()-adj_x);
    B.setX(B.x()-adj_x);
    C.setX(C.x()-adj_x);
    D.setX(D.x()-adj_x);
    adj_y=min(A.y(),B.y());
    A.setY(A.y()-adj_y);
    B.setY(B.y()-adj_y);
    C.setY(C.y()-adj_y);
    D.setY(D.y()-adj_y);

    dDA=A.x()-D.x();
    dBA=A.y()-B.y();
    dCD=C.y()-D.y();
    dCB=B.x()-C.x();

    if (dDA>0)
        offset_x= abs(dDA)- abs((int)((double)dDA  * ((double) rect.y()/ (double)LAYOUT_MAX)+.5));
    else
        offset_x= abs(dDA)- abs((int)((double)dDA  * (1.0 - ((double) rect.y()/ (double)LAYOUT_MAX)+.5)));

    if (dCB>0)
        offset_x2= abs(dCB)- abs((int)((double)dCB * (1.0 -((double) rect.y()/ (double)LAYOUT_MAX)+.5)));
    else
        offset_x2= abs(dCB)- abs((int) ((double)dCB * ((double) rect.y()/ (double)LAYOUT_MAX)+.5));

    if (dBA>0)
        offset_y=abs(dBA)- abs((int) ((double)dBA * ((double) rect.x()/ (double)LAYOUT_MAX)+.5));
    else
        offset_y= abs(dBA)- abs((int) ((double)dBA * (1.0 - ((double) rect.x()/ (double)LAYOUT_MAX)+.5)));

    if (dCD<0)
        offset_y2= abs(dCD)- abs((int)((double)dCD * ( 1.0 - ((double) rect.x()/ (double)LAYOUT_MAX)+.5)));
    else
        offset_y2= abs(dCD) - abs((int) ((double)dCD * ((double) rect.x()/ (double)LAYOUT_MAX)+.5));

    length_w = w - (offset_x + offset_x2);
    length_h = h - (offset_y + offset_y2);

    // qDebug("Gridlayout::ScaleTo");
    Scaled.setRect(Scale(rect.x(), length_w ) + offset_x,
                   Scale(rect.y(), length_h) + offset_y,
                         Scale(rect.width(), length_w),
                               Scale(rect.height(), length_h));
}
