// $Id: qtlcmswidgets.cpp,v 1.42 2008/03/06 21:18:58 hvengel Exp $
//  Little cms - profiler construction set
//  Copyright (C) 1998-2001 Marti Maria
// Copyright (C) 2005-2007  Hal Engel
//  Copyright (C) 2007  Joseph L. Simmon III
//
// 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.08a


// Some shared widgets for trolltech Qt

#include "qtlcmswidgets.h"
#include <qmessagebox.h>
#include <qtranslator.h>
#include <QPixmap>
#include <qdir.h>
#include <qlayout.h>

#include <qapplication.h>
#include <q3filedialog.h>
#include <q3textview.h>
#include <q3textstream.h>
#include <qvariant.h>
#include <qcursor.h>
#include <QPolygon>



#include <setjmp.h>

extern "C" {
#include "numlib.h"
}

QString fName;

bool profileOpen;
jmp_buf cmsJumpBuffer;

int cmsErrorHandler(int /*ErrorCode*/, const char *ErrorText)
{
    QMessageBox mb( QTranslator::tr("Invalid ICC Profile."),
                        QTranslator::tr("Littlecms : ") + QString::fromLocal8Bit(ErrorText) + QTranslator::tr(" in file ") + fName,
                        QMessageBox::Critical,
                        QMessageBox::Ok | QMessageBox::Default,
                        Qt::NoButton,  Qt::NoButton);

    mb.exec();
    longjmp(cmsJumpBuffer, 1);
    return 1;
}

// A selector widget

BaseSelector::BaseSelector(QString ThePrompt, QString DefaultDir,
                           QString Mask, BOOL lLineEdit, BOOL showFileDialog,
                           QWidget* parent, const char* name, Qt::WFlags f) :
    Q3HBox(parent, name, f),
    Dir(DefaultDir),
    FileMask(Mask),
    lUseDescriptor(0),
    lUseLineEdit(lLineEdit)
{
    lForOutput = FALSE;
    Prompt = new QLabel(ThePrompt, this);
    Prompt->setAlignment(Qt::AlignRight|Qt::AlignVCenter);
    Prompt->setSizePolicy(QSizePolicy (QSizePolicy::Preferred, QSizePolicy::Preferred));

    if (lUseLineEdit)
    {
        LineEdit = new QLineEdit(this);
        Prompt->setBuddy(LineEdit);
        Combo = NULL;
    }
    else
    {
        Combo = new QComboBox(FALSE, this),
        Combo->setEditable(FALSE);
        Combo-> setSizePolicy(QSizePolicy (QSizePolicy::Expanding, QSizePolicy::Preferred));
        Prompt->setBuddy(Combo);
        LineEdit = NULL;
    }

    setMargin(2);
    setSpacing(10);
    if (showFileDialog)
    {
        PickButton = new QPushButton(this);
        PickButton -> setSizePolicy(QSizePolicy (QSizePolicy::Minimum, QSizePolicy::Preferred));
        PickButton-> setText(tr (" Select Directory "));
        NewButtonPtr = NULL;
        connect(PickButton, SIGNAL( clicked() ), this, SLOT( slotButtonClicked() ) );
    }

    if (lUseLineEdit)
        connect( LineEdit, SIGNAL( textChanged(const QString&) ), this, SLOT( slotValueChanged() ) );
    else
        connect( Combo, SIGNAL( activated(int) ), this, SLOT( slotValueChanged() ) );
}


QString BaseSelector::getFilename() const
{
    if (lUseLineEdit) return LineEdit -> text();

    int Key = Combo->currentItem();
    if (Key < 0) return QString::null;

    return FileNames[Key];
}


void BaseSelector::setFilename(const QString name)
{
    if (lUseLineEdit)
    {
        LineEdit -> setText(name);
        return;
    }

    for (unsigned int i = 0; i < FileNames.count(); i++)
    {
        if (FileNames[i] == name) Combo->setCurrentItem(i);
    }
}

void BaseSelector::slotButtonClicked()
{
    QString fn;
    Q3FileDialog fd;

    fd.setShowHiddenFiles(TRUE);

    if (lUseLineEdit)
    {
        if (lForOutput)
        {
            fn = fd.getSaveFileName(Dir,
                                    FileMask,
                                    this,
                                    /*QString::null */ "",
                                    Prompt->text());
        }
        else
        {
                fn = fd.getOpenFileName(Dir,
                                        FileMask,
                                        this,
                                        /* QString::null */ "",
                                        Prompt->text());
        }
    }
    else
    {
        fn = fd.getExistingDirectory(Dir, this, "",
                                     QTranslator::tr("Select ") + getPrompt() + 
                                         QTranslator::tr(" directory"), TRUE);
    }


    if (fn != QString::null)
    {
        Dir = fn;
        FillValues();
        slotValueChanged();
    }

}


void BaseSelector::slotValueChanged()
{
    emit valueChanged();
}


void IT8FileSelector::FillValues()
{
    if (lUseLineEdit)
    {
            LineEdit -> setText(Dir);
    }
}



void IT8Selector::FillValues()
{
    // qDebug("In IT8Selector::FillValues()");
    if (lUseLineEdit)  return;

    Descriptions.clear();
    FileNames.clear();
    Combo->clear();

    QDir fd(Dir);

    if (!fd.isReadable()) 
    {
        QMessageBox mb( QTranslator::tr("Reference File Directory Not Readable"),
                        QTranslator::tr("Reference file directory") + Dir + QTranslator::tr(" is not readable.  Please fix the permissions on this directory."),
                                        QMessageBox::Critical,
                                        QMessageBox::Ok | QMessageBox::Default,
                                        Qt::NoButton,  Qt::NoButton);
        mb.exec();
        return;
    }

    //QApplication::setOverrideCursor(WaitCursor);

    fd.setFilter(QDir::Files | QDir::Hidden);
    fd.setNameFilter(FileMask);

    const QFileInfoList& files = fd.entryInfoList();
    QString FileName;

    QList<QFileInfo>::const_iterator itEnd = files.end();
    for ( QList<QFileInfo>::const_iterator it = files.begin(); it != itEnd; ++it )
    {
        FileName = (*it).filePath();
        if ((*it).isReadable ())
        {
            LCMSHANDLE hIT8 = cmsxIT8LoadFromFile((const char *) FileName.toLocal8Bit());
            if (hIT8)
            {
                if (lUseDescriptor)
                {
                    const char *Descriptor = cmsxIT8GetProperty(hIT8, "DESCRIPTOR");
    
                    if (*Descriptor == '\0')
                        Descriptor = cmsxIT8GetProperty(hIT8, "ORIGINATOR");
    
                    Descriptions << QString::fromLocal8Bit(Descriptor);
    
                }
                else
                {
                    QString Serial  = QString::fromLocal8Bit(cmsxIT8GetProperty(hIT8, "SERIAL"));
                    QString Manufacturer = QString::fromLocal8Bit(cmsxIT8GetProperty(hIT8, "ORIGINATOR"));
                    if (Manufacturer == QString::null)
                        Manufacturer   = QString::fromLocal8Bit(cmsxIT8GetProperty(hIT8, "MANUFACTURER"));
                    if (Manufacturer == QString::null)
                        Manufacturer   = QString::fromLocal8Bit(cmsxIT8GetProperty(hIT8, "MANUFACTURE"));
                    QString Backing = QString::fromLocal8Bit(cmsxIT8GetProperty(hIT8, "SAMPLE_BACKING"));
                    if (Backing != QString::null)
                        Backing = QString::fromLocal8Bit(" ") + Backing + QString::fromLocal8Bit(" backing");
    
    
                    if (Serial != QString::null && Manufacturer != QString::null)
                    {
                        if (Manufacturer == QString::fromLocal8Bit("LProf Reference File Installer"))
                        {
                            Manufacturer = QString::fromLocal8Bit(cmsxIT8GetProperty(hIT8, "MANUFACTURER"));
                            const char *Descriptor = cmsxIT8GetProperty(hIT8, "DESCRIPTOR");
                            Descriptions << (Serial + QString::fromLocal8Bit(" ") + QString::fromLocal8Bit(Descriptor) + QString::fromLocal8Bit(" ") + Manufacturer);
                        }
                        else  
                            Descriptions << (Serial + Backing + QString::fromLocal8Bit(" ") + Manufacturer);
                    }
                    else
                    {
                        const char *Descriptor = cmsxIT8GetProperty(hIT8, "ORIGINATOR");
                        Descriptions << QString::fromLocal8Bit(Descriptor);
                    }
                }
    
                FileNames << FileName;
    
                cmsxIT8Free(hIT8);
            }
            else
                qDebug((QString::fromLocal8Bit("failed to open reference file ") + FileName).toLocal8Bit());
        }
        else
        {
            QMessageBox mb( QTranslator::tr("Reference File Not Readable"),
                            QTranslator::tr("Reference file ") + fName + QTranslator::tr(" is not readable.  Please fix the file permissions on this file."),
                                    QMessageBox::Critical,
                                    QMessageBox::Ok | QMessageBox::Default,
                                    Qt::NoButton,  Qt::NoButton);
            mb.exec();
        }
    }

    Combo->insertStringList(Descriptions);
    QApplication::restoreOverrideCursor();
}

bool IT8SelectorWithFileDialog::HCT(QString FileName, QString &Description)
{    
    bool OK = FALSE;
    QString inString;
    
    QFile inFile(FileName);
    inFile.open(QIODevice::ReadOnly);
    Q3TextStream stream(&inFile);
    
    stream >> Description;
    if(Description == QString::fromLocal8Bit("HCT"))
    {
        while (inString != QString::fromLocal8Bit("File"))
            stream >> inString;

        stream >> inString;

        Description = Description + QString::fromLocal8Bit(" ") + inString;
        OK = TRUE;
    }
    inFile.close();
    return OK;
}

bool IT8SelectorWithFileDialog::Logo(QString FileName, QString &Description)
{
    QString inString;
    bool OK = FALSE;
    
    QFile inFile(FileName);
    inFile.open(QIODevice::ReadOnly);
    Q3TextStream stream(&inFile);
    
    stream >> inString;
    
    // Validation for Colorchecker and other LOGO targets.
    if (inString == QString::fromLocal8Bit("LGOROWLENGTH"))
    {
        QString gmFilename;
        int startOfFileName;
        QString gmDescription;
        
        startOfFileName = FileName.findRev(QString::fromLocal8Bit("/"), -1);
        gmFilename = FileName;		
        gmFilename.remove(0, startOfFileName + 1);
        gmFilename.remove(gmFilename.findRev(QString::fromLocal8Bit("."), -1), 4);
        
        while(!stream.atEnd())
        {
            if (inString == QString::fromLocal8Bit("ILLUMINATION_NAME"))
            {   
                OK = TRUE;
                break;
            }
            else
                stream >> inString;
        }
                    
        if (OK)  // found it so add it to the list
        {    
            stream >> inString;
            inString.remove(0, 1);
            inString.remove(inString.length() - 1, 1);
        
            // Description for Colorchecker files.
            if (gmFilename.left(7) == QString::fromLocal8Bit("CMP Dig"))  // DigitaL TargeT 003
                Description = gmFilename + QString::fromLocal8Bit(" ") + inString;
            else
                Description = QString::fromLocal8Bit("GretagMacbeth ") + gmFilename + QString::fromLocal8Bit(" ") + inString;
        }
    }
    inFile.close();
    return OK;
}

bool IT8SelectorWithFileDialog::CGATS(QString FileName)
        
{
    QFile inFile(FileName);
    QString inString;
    inFile.open(QIODevice::ReadOnly);
    Q3TextStream stream(&inFile);
    stream >> inString;
    inFile.close();
    
    // qDebug("IT8SelectorWithFileDialog::CGATS");
    
    if (inString.left(5) == QString::fromLocal8Bit("IT8.7") ||            // IT8.7 type reference files.
        inString.left(4) == QString::fromLocal8Bit("CTI3") ||             // ArgyllCMS type reference files.
        inString.left(4) == QString::fromLocal8Bit("LCMS"))               // LProf generated reference file
        return TRUE;
    else
        return FALSE;   
}

void IT8SelectorWithFileDialog::FillValues()
{
    if (lUseLineEdit)  return;

    Descriptions.clear();
    FileNames.clear();
    Combo->clear();

    QDir fd(Dir);

    if (!fd.isReadable()) 
    {
        QMessageBox mb( QTranslator::tr("Reference File Directory Not Readable"),
                        QTranslator::tr("Reference file directory") + Dir + QTranslator::tr(" is not readable.  Please fix the permissions on this directory or use another directory."),
                                        QMessageBox::Critical,
                                        QMessageBox::Ok | QMessageBox::Default,
                                        Qt::NoButton,  Qt::NoButton);
        mb.exec();
        return;
    }

    // QApplication::setOverrideCursor(WaitCursor);

    fd.setFilter(QDir::Files | QDir::Hidden);
    fd.setNameFilter(FileMask);

    const QFileInfoList& files = fd.entryInfoList();

    QString FileName;
    bool OK = FALSE;
    QString description;

    QList<QFileInfo>::const_iterator itEnd = files.end();
    for ( QList<QFileInfo>::const_iterator it = files.begin(); it != itEnd; ++it )
    {
        FileName = (*it).filePath();
        if ((*it).isReadable ())
        {
            description = QString::fromLocal8Bit("");
    
            if(HCT(FileName, description))
            {
                Descriptions << description;
                FileNames << FileName;
                OK = TRUE;
            }
            else if (Logo(FileName, description))
            {
                Descriptions << description;
                FileNames << FileName;
                OK = TRUE;
            }    
            else if (CGATS(FileName)) 
            {
                LCMSHANDLE hIT8 = cmsxIT8LoadFromFile((const char *) FileName.toLocal8Bit());
        
                if (hIT8)
                {
                    if (lUseDescriptor)
                    {
                        const char *Descriptor = cmsxIT8GetProperty(hIT8, "DESCRIPTOR");
    
                        if (*Descriptor == '\0')
                            Descriptor = cmsxIT8GetProperty(hIT8, "ORIGINATOR");
    
                        Descriptions << QString::fromLocal8Bit(Descriptor);
    
                    }
                    else
                    {
                        QString other = QString::fromLocal8Bit("");
                        QString Serial = QString::fromLocal8Bit(cmsxIT8GetProperty(hIT8, "SERIAL"));
                        QString Manufacturer = QString::fromLocal8Bit(cmsxIT8GetProperty(hIT8, "ORIGINATOR")) + QString::fromLocal8Bit(" ");
                        if (Manufacturer == QString::null)
                            Manufacturer   = QString::fromLocal8Bit(cmsxIT8GetProperty(hIT8, "MANUFACTURE")) + QString::fromLocal8Bit(" ");
                        QString Backing = QString::fromLocal8Bit(cmsxIT8GetProperty(hIT8, "SAMPLE_BACKING"));
                        if (Backing != QString::null)
                            Backing = QString::fromLocal8Bit(" ") + Backing + QString::fromLocal8Bit(" backing ");
                        QString temp = QString::fromLocal8Bit(cmsxIT8GetProperty(hIT8, "DESCRIPTOR"));
                        if (temp.find(QString::fromLocal8Bit("(")) > -1 && temp.findRev(QString::fromLocal8Bit(")")) == temp.length() - 1)
                            other = QString::fromLocal8Bit(" ") + temp.mid(temp.find(QString::fromLocal8Bit("(")) + 1, temp.length() - temp.find(QString::fromLocal8Bit("(")) - 2) + QString::fromLocal8Bit(" ");
                        QString numFields = QString::fromLocal8Bit(" Number of Fields ") + QString::fromLocal8Bit(cmsxIT8GetProperty(hIT8, "NUMBER_OF_FIELDS")); 
    
                        if (Serial != QString::null && Manufacturer != QString::null)
                            Descriptions << (Manufacturer + Serial + other + Backing + numFields);
                        else
                        {
                            const char *Descriptor = cmsxIT8GetProperty(hIT8, "ORIGINATOR");
                            Descriptions << QString::fromLocal8Bit(Descriptor);
                        }
                    }
    
                    FileNames << FileName;
                    OK = TRUE;
                    cmsxIT8Free(hIT8);
                }
                else
                    qDebug((QString::fromLocal8Bit("failed to open reference file ") + FileName).toLocal8Bit());   
            }
        }
        else
        {
            QMessageBox mb( QTranslator::tr("Reference File Not Readable"),
                            QTranslator::tr("Reference file ") + fName + QTranslator::tr(" is not readable.  Please fix the file permissions on this file."),
                                            QMessageBox::Critical,
                                            QMessageBox::Ok | QMessageBox::Default,
                                            Qt::NoButton,  Qt::NoButton);
            mb.exec();
        }
    }
    if (OK)
        Combo->insertStringList(Descriptions);
    QApplication::restoreOverrideCursor();
}

void ICCProfileSelector::FillValues()
{
    // qDebug("ICCProfileSelector::FillValues()");

    cmsHPROFILE hICM = NULL;
    if (lUseLineEdit) return;

    Descriptions.clear();
    FileNames.clear();
    Combo->clear();

    QDir fd(Dir);
    Combo->clear();

    if (!fd.exists())
        return;

    if (!fd.isReadable()) 
    {
        QMessageBox mb( QTranslator::tr("ICC Profile Directory Not Readable"),
                        QTranslator::tr("ICC Profile directory") + Dir + QTranslator::tr(" is not readable.  Please fix the permissions on this directory or use another directory."),
                                        QMessageBox::Critical,
                                        QMessageBox::Ok | QMessageBox::Default,
                                        Qt::NoButton,  Qt::NoButton);
        mb.exec();
        return;
    }

    // QApplication::setOverrideCursor(WaitCursor);

    fd.setFilter(QDir::Files | QDir::Hidden);
    fd.setNameFilter(FileMask);

    const QFileInfoList& files = fd.entryInfoList();
    QString FileName;

    cmsSetErrorHandler(&cmsErrorHandler);
    QList<QFileInfo>::const_iterator itEnd = files.end();
    for ( QList<QFileInfo>::const_iterator it = files.begin(); it != itEnd; ++it )
    {
        if (setjmp(cmsJumpBuffer))
        {
            cmsSetErrorHandler(NULL);
            if (hICM)
            {
                if (profileOpen)
                   cmsCloseProfile(hICM);
                hICM = NULL;
            }
            cmsSetErrorHandler(&cmsErrorHandler);
            continue;
        }

        FileName = (*it).filePath();
        fName = FileName;
        profileOpen = false;
        
        if ((*it).isReadable ())
        { 
            hICM = cmsOpenProfileFromFile((const char *) FileName.toLocal8Bit(), "r");
            if (hICM)
            {
                profileOpen = true;
               if ((RestrictClass == -1 ||
                    ((int) cmsGetDeviceClass(hICM) == RestrictClass)) &&
                    (RestrictColorspace == -1 ||
                    ((int) cmsGetColorSpace(hICM) == RestrictColorspace)))
                {
                    const char *Descriptor = cmsTakeProductDesc(hICM);
                    if (strlen(Descriptor) > 0) // egnore bad profiles
                    {
                        Descriptions       << QString::fromLocal8Bit(Descriptor);
                        FileNames          << FileName;
                    }
                }
                cmsCloseProfile(hICM);
            }
        }
        else
        {
            QMessageBox mb( QTranslator::tr("ICC Profile Not Readable"),
                            QTranslator::tr("ICC Profile ") + fName + QTranslator::tr(" is not readable.  Please fix the file permissions on this file."),
                                            QMessageBox::Critical,
                                            QMessageBox::Ok | QMessageBox::Default,
                                            Qt::NoButton,  Qt::NoButton);
          mb.exec();
        }
    }
    cmsSetErrorHandler(NULL);

    Combo->insertStringList(Descriptions);

    QApplication::restoreOverrideCursor();
    // qDebug("ICCProfileSelector::FillValues() done");
}


// ---------------------------------------------------------------- Results

// Relative colorfulness distance

static
double LabColorfulnessDist(LPcmsCIELab l1, LPcmsCIELab l2)
{

    if (l1 -> L == 0 && l2 ->L == 0) return 0;	// Pure black

       // L*a*b define relative colorfulness as
       //
       // Cab = sqrt(a^2+b^2)

    double adist, bdist;

    adist = fabs(l1->a - l2->a);
    bdist = fabs(l1->b - l2->b);

    return sqrt((adist*adist + bdist*bdist));

}

// Hue distance in degree, -1 if don't care

static
double LabHueDist(LPcmsCIELab l1, LPcmsCIELab l2)
{

    if ((fabs(l1->a) < 4.5) && (fabs(l1->b) < 4.5)) return -1;

    double l = fabs(atan2(l1->a, l1->b) - atan2(l2->a, l2->b));

    l *= (180.0/M_PI);

    while (l < 0)
            l += 360;

    return l;
}


static
double LabLDist(LPcmsCIELab l1, LPcmsCIELab l2)
{
    return fabs(l1-> L - l2 ->L);
}


static
void SetGridItem(Q3Table* Grid, int row, int col, QString& s)
{
    Q3TableItem* Item = new Q3TableItem(Grid, Q3TableItem::Never, s);
    Grid -> setItem(row, col, Item);
}


static
int Scale(WORD i)
{
    return (int) ((double) i / 257. + .5);
}

static
QPixmap* CreateProofPixmap(WORD Inside[3], WORD Frame[3])
{
    QColor ColorFrame(Scale(Frame[0]), Scale(Frame[1]), Scale(Frame[2]));
    QColor ColorInside(Scale(Inside[0]), Scale(Inside[1]), Scale(Inside[2]));
    QPixmap* pix = new QPixmap(34, 32);

    QPainter Painter(pix);


    Painter.fillRect(0, 0, 2,  32, Qt::white);
    Painter.fillRect(2, 0, 32, 32, ColorFrame);
    Painter.fillRect(8, 6, 20, 20, ColorInside);

    return pix;
}


static void
SetGridPatch(Q3Table* Grid, int row, int col, WORD RGB[3], WORD LabProof[3])
{
    QPixmap* Pixmap = CreateProofPixmap(LabProof, RGB);

    Q3TableItem* Item = new Q3TableItem(Grid, Q3TableItem::Never, QString::null, *Pixmap);
    Grid -> setItem(row, col, Item);

}

void
CheckProfile::CreateFancyReport(QString& Report,
                                Statistics& st,
                                Statistics& st94,
                                CheckerCorrection corr,
                                double params[])
{
    QString frm1 =

            QTranslator::tr("<qt><font size=3>"
	    "  <table cellspacing=0 cellpadding=0> "
            "  <tr> "
            "    <td align=left valign=bottom><b><font color=blue>Metric</font></b></td>"
            "    <td align=right valign=bottom><b><font color=red>Average</font></b></td>"
            "    <td align=right valign=bottom><b><font color=blue>Standard<br>Deviation</font></b></td>"
            "    <td align=right valign=bottom><b><font color=blue>Min</font></b></td>"
            "    <td align=right valign=bottom><b><font color=blue>Peak</font></b></td>"
            "  </tr>"
            "  <tr> "
	    "    <td align=left>Delta E L*a*b*</td>"
            "    <td align=right><font color=red>%1</font></td>"
            "    <td align=right>%2</td>"
            "    <td align=right>%3</td>"
            "    <td align=right>%4</td>"
            "  </tr> "
            "  <tr> "
	    "    <td align=left>Delta E CIE94</td>"
            "    <td align=right><font color=red>%5</font></td>"
            "    <td align=right>%6</td>"
            "    <td align=right>%7</td>"
            "    <td align=right>%8</td>"
            "  </tr> "
            "  </table> "
	    );

    QString frm2 =

            QTranslator::tr("<hr><table cellspacing=0 cellpadding=0>"
            "  <tr>"
            "    <td align=left>Average Target error (99% confidence):</td>"
            "    <td align=right>%1</td>"
            "  </tr>"
            "  <tr>"
            "    <td align=left><font color=blue>Maximum error induced by target itself:</font></td>"
            "    <td align=right><font color=blue>%2</font></td>"
            "  </tr>"
            " </table>"
	    );

    QString Text;

    Text = frm1.arg( st.Mean(), 0, 'f', 2);
    Text = Text.arg( st.Std(), 0, 'f', 2);
    Text = Text.arg( st.Min, 0, 'f', 2);
    Text = Text.arg( st.Peak, 0, 'f', 2);
    Text = Text.arg( st94.Mean(), 0, 'f', 2);
    Text = Text.arg( st94.Std(), 0, 'f', 2);
    Text = Text.arg( st94.Min, 0, 'f', 2);
    Text = Text.arg( st94.Peak, 0, 'f', 2);

    Report += Text;

    if (st.TargetErrorMean() > 0.0)
    {
        Text = frm2.arg(st.TargetErrorMean(), 0, 'f', 2);
        Text = Text.arg(st.TargetErrorMax(), 0, 'f', 2);
        Report += Text;
    }

    switch (corr)
    {
	case CHK_CORR_LUMINANCE:

	    Report += QTranslator::tr(
		"<hr>All reported errors are <b>after</b> correcting "
		"the profile by luminance scaling by factor %1"
	    ).arg(params[0], 0, 'f', 2);
	    break;

	case CHK_CORR_MATRIX:

	    Report += QTranslator::tr(
		"<hr> "
		"All reported errors are <b>after</b> correcting the profile with the matrix "
		"<table cellspacing=0 cellpadding=0> "
		"  <tr> "
		"    <td align=left valign=middle rowspan=3>XYZ(corr) =</td> "
		"    <td align=left>&lceil;</td> "
		"    <td align=right>%1</td><td align=right>%2</td><td align=right>%3</td> "
		"    <td align=right>&rceil;</td> "
		"    <td align=left valign=middle rowspan=3>* XYZ(profile)</td> "
		"  </tr><tr> "
		"    <td align=left>|</td> "
		"    <td align=right>%4</td><td align=right>%5</td><td align=right>%6</td> "
		"    <td align=right>|</td> "
		"  </tr><tr> "
		"    <td align=left>&lfloor;</td> "
		"    <td align=right>%7</td><td align=right>%8</td><td align=right>%9</td> "
		"    <td align=right>&rfloor;</td> "
		"  </tr> "
		"</table> "
	    )
	    .arg(params[0], 0, 'f', 2)
	    .arg(params[1], 0, 'f', 2)
	    .arg(params[2], 0, 'f', 2)
	    .arg(params[3], 0, 'f', 2)
	    .arg(params[4], 0, 'f', 2)
	    .arg(params[5], 0, 'f', 2)
	    .arg(params[6], 0, 'f', 2)
	    .arg(params[7], 0, 'f', 2)
	    .arg(params[8], 0, 'f', 2);
	    break;

	case CHK_CORR_WP_BDF:

	    Report += QTranslator::tr(
		"<hr>All reported errors are <b>after</b> correcting "
		"the profile by Bradford whitepoint scaling by factors "
		"[%1 %2 %3]"
	    )
	    .arg(params[0], 0, 'f', 2)
	    .arg(params[1], 0, 'f', 2)
	    .arg(params[2], 0, 'f', 2);
	    break;

	case CHK_CORR_WP_WVK:

	    Report += QTranslator::tr(
		"<hr>All reported errors are <b>after</b> correcting "
		"the profile by Wrong von Kries whitepoint scaling "
		"by factors [%1 %2 %3]"
	    )
	    .arg(params[0], 0, 'f', 2)
	    .arg(params[1], 0, 'f', 2)
	    .arg(params[2], 0, 'f', 2);
	    break;

    	default:
	    break;
    }

    Report += QString::fromLocal8Bit("</font></qt>");
}

struct CorrOptData
{
    CheckProfile::CheckerCorrection corr;
    int nPatches;
    int numEvals;
    cmsCIEXYZ WP;
    cmsCIEXYZ *Ref;
    cmsCIEXYZ *Prof;

    CorrOptData(int maxPatches, cmsCIEXYZ &wp) {
    	nPatches = 0; WP = wp; numEvals = 0;
	corr = CheckProfile::CHK_CORR_NONE;
	Ref = new cmsCIEXYZ[maxPatches];
	Prof = new cmsCIEXYZ[maxPatches];
    }
    ~CorrOptData() {
    	delete [] Ref;
    	delete [] Prof;
    }
};

// Perform color correction

static void
DoCorrection(CorrOptData *opt, double par[],
             LPcmsCIEXYZ in, LPcmsCIEXYZ out,
	     BOOL doClip = TRUE)
{
    // Bradford matrix (and inverse)
    static double bdf[] = {
    	 0.8951,  0.2664, -0.1614,
	-0.7502,  1.7135,  0.0367,
	 0.0389, -0.0685,  1.0296
    };
    static double ibdf[] = {
    	 0.986992905466712145,  -0.147054256420990126,  0.15996265166373122,
	 0.432305269723394447,   0.518360271536777439,  0.0492912282128555937,
	-0.00852866457517732074, 0.0400428216540848619, 0.968486695787549978
    };

    cmsCIEXYZ tmp = *in;

    switch (opt->corr)
    {
    	case CheckProfile::CHK_CORR_LUMINANCE:
	    out->X = tmp.X * par[0];
	    out->Y = tmp.Y * par[0];
	    out->Z = tmp.Z * par[0];
	    break;

    	case CheckProfile::CHK_CORR_MATRIX:
	    out->X = par[0]*tmp.X + par[1]*tmp.Y + par[2]*tmp.Z;
	    out->Y = par[3]*tmp.X + par[4]*tmp.Y + par[5]*tmp.Z;
	    out->Z = par[6]*tmp.X + par[7]*tmp.Y + par[8]*tmp.Z;
	    break;

    	case CheckProfile::CHK_CORR_WP_WVK:
	    out->X = tmp.X * par[0];
	    out->Y = tmp.Y * par[1];
	    out->Z = tmp.Z * par[2];
	    break;

    	case CheckProfile::CHK_CORR_WP_BDF:
	    tmp.X = bdf[0]*in->X + bdf[1]*in->Y + bdf[2]*in->Z;
	    tmp.Y = bdf[3]*in->X + bdf[4]*in->Y + bdf[5]*in->Z;
	    tmp.Z = bdf[6]*in->X + bdf[7]*in->Y + bdf[8]*in->Z;
	    tmp.X *= par[0];
	    tmp.Y *= par[1];
	    tmp.Z *= par[2];
	    out->X = ibdf[0]*tmp.X + ibdf[1]*tmp.Y + ibdf[2]*tmp.Z;
	    out->Y = ibdf[3]*tmp.X + ibdf[4]*tmp.Y + ibdf[5]*tmp.Z;
	    out->Z = ibdf[6]*tmp.X + ibdf[7]*tmp.Y + ibdf[8]*tmp.Z;
	    break;

    	default:
	    *out = tmp;
	    break;
    }

    if (doClip) {
	if (out->X < 0) out->X = 0;
	if (out->Y < 0) out->Y = 0;
	if (out->Z < 0) out->Z = 0;
    }
}

// function being minimized
// to determine color correction

static double
CorrOptFunc(void *fdata, double par[])
{
    double error = 0;
    CorrOptData *opt = (CorrOptData*) fdata;

    opt->numEvals++;

    for (int i = 0; i < opt->nPatches; i++)
    {
	cmsCIEXYZ Prof;
	cmsCIELab ProfLab, RefLab;

	DoCorrection(opt, par, &opt->Prof[i], &Prof);

	cmsXYZ2Lab(&opt->WP, &ProfLab, &Prof);
	cmsXYZ2Lab(&opt->WP, &RefLab, &opt->Ref[i]);

	double dE94 = cmsCIE94DeltaE(&RefLab, &ProfLab);
	error += dE94 * dE94;

    }

    error /= opt->nPatches;
    return error;
}

static double
CorrOptFunc1(void *fdata, double scale)
{
    return CorrOptFunc(fdata, &scale);
}

CheckProfile::CheckProfile(QComboBox *corrCombo)
{
    CorrectionCombo = corrCombo;

    if (corrCombo)
    {
	corrCombo->clear();

	CorrByItem[0] = CHK_CORR_NONE;
	corrCombo->insertItem(QTranslator::tr("No Correction"));

	CorrByItem[1] = CHK_CORR_LUMINANCE;
	corrCombo->insertItem(QTranslator::tr("Luminance Correction"));

	CorrByItem[2] = CHK_CORR_MATRIX;
	corrCombo->insertItem(QTranslator::tr("Matrix Correction"));

	CorrByItem[3] = CHK_CORR_WP_WVK;
	corrCombo->insertItem(QTranslator::tr("Whitepoint Correction (Wrong von Kries)"));

	CorrByItem[4] = CHK_CORR_WP_BDF;
	corrCombo->insertItem(QTranslator::tr("Whitepoint Correction (Bradford)"));

	corrCombo->setCurrentItem(0);
    }
}

void CheckProfile::Results(Q3Table* ResultsGrid,
                           QString& ResultsText,
                           LPMEASUREMENT m,
                           const char* OutputProfileFile,
                           const char* ProofProfile)
{

    QString Text;

    CheckerCorrection corr = CorrectionCombo ?
	CorrByItem[CorrectionCombo->currentItem()] :
        CHK_CORR_NONE;

    Q3Header* Vertical   = ResultsGrid -> verticalHeader();
    Q3Header* Horizontal = ResultsGrid -> horizontalHeader();

    Vertical -> setResizeEnabled(FALSE);

    ResultsGrid -> setLeftMargin(60);
    ResultsGrid -> setNumCols(11);

    Horizontal -> setLabel(0, QString::fromLocal8Bit(""));
    Horizontal -> setLabel(1, QTranslator::tr("CIE La*b* dE"));
    Horizontal -> setLabel(2, QTranslator::tr("Target error"));
    Horizontal -> setLabel(3, QTranslator::tr("Chroma error"));
    Horizontal -> setLabel(4, QTranslator::tr("Hue error"));
    Horizontal -> setLabel(5, QTranslator::tr("Luma error"));
    Horizontal -> setLabel(6, QTranslator::tr("Target Lab"));
    Horizontal -> setLabel(7, QTranslator::tr("Got"));

    Horizontal -> setLabel(8, QTranslator::tr("dE CIE94"));
    Horizontal -> setLabel(9, QTranslator::tr("dE CMC"));
    Horizontal -> setLabel(10, QTranslator::tr("dE BFL"));

    ResultsGrid -> setColumnWidth (0, 37);

    cmsHPROFILE hProfile  = cmsOpenProfileFromFile(OutputProfileFile, "r");

    cmsCIEXYZ Wt;
    cmsTakeMediaWhitePoint(&Wt, hProfile);

    cmsCIExyY xyY;
    cmsXYZ2xyY(&xyY, &Wt);

    /*
    int Medium;
    if (cmsGetDeviceClass(hProfile) == icSigDisplayClass)
        Medium = MEDIUM_TRANSMISSIVE;
    else
        Medium = MEDIUM_REFLECTIVE_D50;
    */

    // Assure we got D50 Lab
    cmsxCompleteLabOfPatches(m, m ->Allowed, MEDIUM_REFLECTIVE_D50);
    cmsHPROFILE hLab      = cmsCreateLabProfile(NULL);
    cmsHPROFILE hXYZ      = cmsCreateXYZProfile();
    cmsHPROFILE hProofProfile, hCompute, hProof_Lab, hProof_RGB;
    if (ProofProfile == NULL) hProofProfile = cmsCreate_sRGBProfile();
    else hProofProfile = cmsOpenProfileFromFile(ProofProfile, "r");

    hCompute = cmsCreateTransform(hProfile,
                                  TYPE_RGB_16, hXYZ, TYPE_XYZ_DBL,
                                  INTENT_ABSOLUTE_COLORIMETRIC,
                                  cmsFLAGS_NOTPRECALC);

    hProof_Lab = cmsCreateTransform(hLab,
                                    TYPE_Lab_16,
                                    hProofProfile, TYPE_RGB_16,
                                    INTENT_ABSOLUTE_COLORIMETRIC,
                                    cmsFLAGS_NOTPRECALC);

    hProof_RGB = cmsCreateTransform(hProfile,
                                    TYPE_RGB_16,
                                    hProofProfile,
                                    TYPE_RGB_16,
                                    INTENT_ABSOLUTE_COLORIMETRIC,
                                    cmsFLAGS_NOTPRECALC);


    Statistics st;
    Statistics st94;
    st.ClearStatistics();
    st94.ClearStatistics();

    int i, j;
    double params[9] = { 0.0, 0.0, 0.0,  0.0, 0.0, 0.0,  0.0, 0.0, 0.0 };
    double search[9] = { 0.1, 0.1, 0.1,  0.1, 0.1, 0.1,  0.1, 0.1, 0.1 };
    CorrOptData opt(m->nPatches, m->Illuminant);

    // determine color correction

    if (corr != CHK_CORR_NONE)
    {
	for (i = j = 0; i < m->nPatches; i++)
	{
	    if (! m->Allowed[i]) continue;

	    WORD RGB[3];
	    LPPATCH p = &m->Patches[i];

	    RGB[0] = (WORD) floor(p->Colorant.RGB[0] * 257. + .5);
	    RGB[1] = (WORD) floor(p->Colorant.RGB[1] * 257. + .5);
	    RGB[2] = (WORD) floor(p->Colorant.RGB[2] * 257. + .5);

	    cmsDoTransform(hCompute, RGB, &opt.Prof[j], 1);

	    opt.Prof[j].X *= 100.0;
	    opt.Prof[j].Y *= 100.0;
	    opt.Prof[j].Z *= 100.0;

	    opt.Ref[j] = p->XYZ;
	    opt.nPatches = j++;
	}

	// always start by finding the best luminance scaling

	opt.corr = CHK_CORR_LUMINANCE;
	params[0] = cmsxGoldenSectionSearch (0.1, 10.0, CorrOptFunc1, &opt, 0.0);

	double err = 0;
	opt.numEvals = 0;
	opt.corr = corr;

	switch (corr)
	{
	    case CHK_CORR_MATRIX:
		params[4] = params[8] = params[0];
		if (powell(&err, 9, params, search, 0.0001, 10000, CorrOptFunc, &opt) != 0) {
		    printf("powell() failed to compute matrix correction\n");
		}
		break;

	    case CHK_CORR_WP_WVK:
	    case CHK_CORR_WP_BDF:
		params[1] = params[2] = params[0];
		if (powell(&err, 3, params, search, 0.0001, 10000, CorrOptFunc, &opt) != 0) {
		    printf("powell() failed to compute white point correction\n");
		}
		break;

	    default:
		break;
	}

	// printf("error=%f, numEvals=%d\n", sqrt(err), opt.numEvals);

	// for (i = 0; i < 9; i++) {
	//     printf("params[%d] = %f\n", i, params[i]);
	// }
    }

    ResultsGrid -> setNumRows(m->nPatches);

    for (i =0; i < m->nPatches; i++)
    {
        LPPATCH p = m->Patches + i;
        if (m->Allowed[i])
        {
            cmsCIEXYZ ProfileXYZ, tmpXYZ;
            cmsCIELab ProfileLab, RefLab;
            WORD RGB[3], RefLabEncoded[3];
            // WORD RGB[3], ProfileLabEncoded[3], RefLabEncoded[3];
            WORD RGBFromLabProof[3], RGBProof[3];

            ResultsGrid -> showRow(i);
            ResultsGrid -> setRowHeight(i, 37);

            // Take reference RGB
            RGB[0] = (WORD) floor(p -> Colorant.RGB[0] * 257. + .5);
            RGB[1] = (WORD) floor(p -> Colorant.RGB[1] * 257. + .5);
            RGB[2] = (WORD) floor(p -> Colorant.RGB[2] * 257. + .5);

            // Take reference Lab
            // RefLab.L = p->Lab.L;
            // RefLab.a = p->Lab.a;
            // RefLab.b = p->Lab.b;
	    cmsXYZ2Lab(&m->Illuminant, &RefLab, &p->XYZ);

            // Convert Lab to Encoded
            cmsFloat2LabEncoded(RefLabEncoded, &RefLab);

            // Check numbers
            cmsDoTransform(hCompute, RGB, &tmpXYZ, 1);
	    DoCorrection(&opt, params, &tmpXYZ, &ProfileXYZ);
	    ProfileXYZ.X *= 100.0; ProfileXYZ.Y *= 100.0; ProfileXYZ.Z *= 100.0;
            // cmsLabEncoded2Float(&ProfileLab, ProfileLabEncoded);
	    cmsXYZ2Lab(&m->Illuminant, &ProfileLab, &ProfileXYZ);

             // Check Visually
            cmsDoTransform(hProof_Lab, RefLabEncoded, RGBFromLabProof, 1);
            cmsDoTransform(hProof_RGB, RGB, RGBProof, 1);

            SetGridPatch(ResultsGrid, i, 0, RGBProof, RGBFromLabProof);

            cmsLab2XYZ(cmsD50_XYZ(), &p->XYZProof, &ProfileLab);
            p ->dwFlags |= PATCH_HAS_XYZ_PROOF;
            double dE   = cmsDeltaE(&RefLab, &ProfileLab);
            double dE94 = cmsCIE94DeltaE(&RefLab, &ProfileLab);
            double dECMC = cmsCMCdeltaE(&RefLab, &ProfileLab);
            double dEBFL = cmsBFDdeltaE(&RefLab, &ProfileLab);
            double dC = LabColorfulnessDist(&RefLab, &ProfileLab);
            double dH = LabHueDist(&RefLab, &ProfileLab);
            double dL = LabLDist(&RefLab, &ProfileLab);

            st.AddOnePatch(dE);
            st94.AddOnePatch(dE94);
            Text.sprintf(" %2.2f ", dE);
            SetGridItem(ResultsGrid, i, 1, Text);

            Text.sprintf(" %2.2f ", dC);
            SetGridItem(ResultsGrid, i, 3, Text);

            // Compute % of deviation of hue

            if (dH > 180)
                dH = 360 - dH;

            if (dH < 0)
                Text = QTranslator::tr("(achromatic)");
            else
                Text.sprintf(" %3.0f° (%2.2f%%) ", dH, (dH * 100. / 180.));
            SetGridItem(ResultsGrid, i, 4, Text);

            Text.sprintf(" %2.2f ", dL);
            SetGridItem(ResultsGrid, i, 5, Text);


            Text.sprintf("%3.0f %3.0f %3.0f", RefLab.L, RefLab.a, RefLab.b);
            SetGridItem(ResultsGrid, i, 6, Text);

            Text.sprintf("%3.0f %3.0f %3.0f", ProfileLab.L, ProfileLab.a, ProfileLab.b);
            SetGridItem(ResultsGrid, i, 7, Text);


            Text.sprintf(" %2.2f ", dE94);
            SetGridItem(ResultsGrid, i, 8, Text);

            Text.sprintf(" %2.2f ", dECMC);
            SetGridItem(ResultsGrid, i, 9, Text);

            Text.sprintf(" %2.2f ", dEBFL);
            SetGridItem(ResultsGrid, i, 10, Text);

            if (p ->dwFlags & PATCH_HAS_STD_DE)
            {
                    st.EstimateTargetError(p -> dEStd);
                    Text.sprintf(" %2.2f ", st.TargetError());
                    SetGridItem(ResultsGrid, i, 2, Text);
            }


            QString Title = QString::fromLocal8Bit(p -> Name);

            if (dE > 10)
                Title = QString::fromLocal8Bit("** ") + Title;

                Vertical -> setLabel( i, Title);


        }
         else
            ResultsGrid -> hideRow(i);

    }

    cmsDeleteTransform(hCompute);
    cmsDeleteTransform(hProof_Lab);
    cmsDeleteTransform(hProof_RGB);

    cmsCloseProfile(hProfile);
    cmsCloseProfile(hXYZ);
    cmsCloseProfile(hLab);
    cmsCloseProfile(hProofProfile);

    CreateFancyReport(ResultsText, st, st94, corr, params);
}

