/*****************************************************************************
 * $CAMITK_LICENCE_BEGIN$
 *
 * CamiTK - Computer Assisted Medical Intervention ToolKit
 * (c) 2001-2018 Univ. Grenoble Alpes, CNRS, TIMC-IMAG UMR 5525 (GMCAO)
 *
 * Visit http://camitk.imag.fr for more information
 *
 * This file is part of CamiTK.
 *
 * CamiTK is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License version 3
 * only, as published by the Free Software Foundation.
 *
 * CamiTK 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 Lesser General Public License version 3 for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * version 3 along with CamiTK.  If not, see <http://www.gnu.org/licenses/>.
 *
 * $CAMITK_LICENCE_END$
 ****************************************************************************/

// -- Core image component stuff
#include "ImageLutWidget.h"
#include "ImageComponent.h"


// -- Core stuff
#include "Log.h"

// -- QT stuff
#include <QGraphicsTextItem>
#include <QColorDialog>
#include <QPushButton>

// -- stl stuff
#include <cfloat>

// -- VTK stuff
#include <vtkUnsignedCharArray.h>

using namespace camitk;

// ---------------------- Constructor ----------------------------
ImageLutWidget::ImageLutWidget(QWidget* parent) : QWidget(parent) {

    myComponent = NULL;

    lutMin = 0;
    lutMax = 255;
    nbHistoBins = 256;
    greyLevels = nullptr;

    // Qt model designed by QtDesigner (with qt4, nor more inheritance from the ui)
    ui.setupUi(this);

    initSlider(ui.sliderLutLevel, lutMin, lutMax, (lutMax - lutMin) / 2.0);
    initSlider(ui.sliderLutWindow, 0, (lutMax - lutMin), (lutMax - lutMin - 0.0) / 2.0);

    connect(ui.sliderLutLevel,    SIGNAL(valueChanged(int)), this, SLOT(sliderLUTLevelChanged(int)));
    connect(ui.sliderLutWindow,   SIGNAL(valueChanged(int)), this, SLOT(sliderLUTWindowChanged(int)));
    connect(ui.lineEditLutLevel,  SIGNAL(returnPressed()), this, SLOT(lineEditLUTLevelChanged()));
    connect(ui.lineEditLutWindow, SIGNAL(returnPressed()), this, SLOT(lineEditLUTWindowChanged()));

    connect(ui.maxTransparency, SIGNAL(stateChanged(int)), this, SLOT(applyLUT()));
    connect(ui.minTransparency, SIGNAL(stateChanged(int)), this, SLOT(applyLUT()));

    connect(ui.inversion, SIGNAL(clicked()), this, SLOT(invertButtonClicked()));
    connect(ui.resetLut,  SIGNAL(clicked()), this, SLOT(resetLUT()));

    connect(ui.minColor, SIGNAL(clicked()), this, SLOT(setMinColor()));
    connect(ui.maxColor, SIGNAL(clicked()), this, SLOT(setMaxColor()));

    // compute and draw the histogram
    ui.histogramGraphicsView->setScene(new QGraphicsScene(this));
    ui.histogramGraphicsView->setBackgroundBrush(QBrush(palette().window()));
    ui.histogramGraphicsView->setRenderHint(QPainter::Antialiasing);
}


//---------------------- destructor ----------------------------
ImageLutWidget::~ImageLutWidget() {
    delete greyLevels;
}

//---------------------- updateComponent ----------------------------
void ImageLutWidget::updateComponent(ImageComponent* comp) {

    myComponent = comp;

    // if there is no lookup table, just don't display this widget!
    if (myComponent->getLut() == NULL) {
        setEnabled(false);
        return;
    }
    else {
        setEnabled(true);
    }

    invert = false;

    // initialize the sliders value
    double* imgRange = myComponent->getImageData()->GetScalarRange();
    double imgMinColor = imgRange[0];
    double imgMaxColor = imgRange[1];
    double nbColorsOrg = imgMaxColor - imgMinColor;
    auto nbOfColors = (long int)(nbColorsOrg);

    // as this widget is only grayscale, compute the average of the color values
    lutMin = imgMinColor;
    lutMax = imgMaxColor;

    // update scale and table value information on the luts
    if ((unsigned)abs(lutMax - lutMin) > UINT_MAX) {
        nbHistoBins = UINT_MAX;
    }
    else {
        nbHistoBins = abs(lutMax - lutMin);
    }
    myComponent->getLut()->SetNumberOfTableValues(nbHistoBins);

    blockAllSignals(true);
    initLevelGUI(lutMin, lutMax, myComponent->getLut()->GetLevel());
    initWindowGUI(0, myComponent->getLut()->GetWindow(), myComponent->getLut()->GetWindow());

    // allocate memory to draw the histogram according to the number of available scalar component
    // free memory of previously drawn histogram if the lut widget handle another component
    if (greyLevels) {
        delete greyLevels;
    }

    // allocate the memory needed for that component
    greyLevels = new double[nbHistoBins + 1];

    // check alpha state for min and max
    double* color = myComponent->getLut()->GetTableValue(0);
    QColor minColor = QColor::fromRgbF(color[0], color[1], color[2], 1.0);
    ui.minColor->setPalette(QPalette(minColor));
    ui.minTransparency->setChecked((color[3] < 1e-5));
    color = myComponent->getLut()->GetTableValue(nbOfColors - 1);
    QColor maxColor = QColor::fromRgbF(color[0], color[1], color[2], 1.0);
    ui.maxColor->setPalette(QPalette(maxColor));
    ui.maxTransparency->setChecked((color[3] < 1e-5));

    // color scale brush
    updateGradient();

    // check inversion: if max color is not all black, take the min color
    if (color[0] * 255.0 < 1.0 && color[1] * 255 < 1.0 && color[2] * 255 < 1.0) {
        color = myComponent->getLut()->GetTableValue(0);
        invert = true;
    }
    blockAllSignals(false);

    // compute and draw the histogram
    applyLUT();
}

//---------------------- updateLUT ------------------------
void ImageLutWidget::applyLUT() {

    // set window level and width according to user set up
    myComponent->getLut()->SetWindow(ui.lineEditLutWindow->text().toInt());
    myComponent->getLut()->SetLevel(ui.lineEditLutLevel->text().toInt());

    // use the color to update table values
    int nbOfColors = myComponent->getLut()->GetNumberOfTableValues();
    QColor fadedColor = ui.minColor->palette().button().color();
    QColor strongColor = ui.maxColor->palette().button().color();
    QColor maxColor, minColor;
    if (!invert) {
        minColor = fadedColor;
        maxColor = strongColor;
    }
    else {
        minColor = strongColor;
        maxColor = fadedColor;
    }
    myComponent->getLut()->SetMinimumTableValue(minColor.redF(), minColor.greenF(), minColor.blueF(), 1.0);
    myComponent->getLut()->SetMaximumTableValue(maxColor.redF(), maxColor.greenF(), maxColor.blueF(), 1.0);
    // build using the given range
    myComponent->getLut()->Build();

    // once the whole colors are build change the min/max color alpha
    myComponent->getLut()->SetTableValue(0, minColor.redF(), minColor.greenF(), minColor.blueF(), (ui.minTransparency->isChecked() ? 0.0 : 1.0));
    myComponent->getLut()->SetTableValue(nbOfColors - 1, maxColor.redF(), maxColor.greenF(), maxColor.blueF(), (ui.maxTransparency->isChecked() ? 0.0 : 1.0));

    myComponent->refresh();

    draw();
}

//---------------------- resetLut ------------------------
void ImageLutWidget::resetLUT() {
    // init all palette values
    blockAllSignals(true);
    initLevelGUI(lutMin, lutMax, int ((lutMax - lutMin) / 2.0));
    initWindowGUI(0, (lutMax - lutMin), lutMax - lutMin);
    invert = false;

    ui.maxTransparency->setChecked(false);
    ui.minTransparency->setChecked(false);

    ui.minColor->setPalette(QPalette(Qt::black));
    ui.maxColor->setPalette(QPalette(Qt::white));
    blockAllSignals(false);

    // update palette color applied to the images and the histogram
    applyLUT();
}

//---------------------- draw ------------------------
void ImageLutWidget::draw() {

    //-- initialize histogram values to 0
    for (unsigned int i = 0; i < nbHistoBins; i++) {
        greyLevels[i] = 0.0;
    }

    //-- get image volume
    vtkSmartPointer<vtkImageData> data;
    if (myComponent) {
        data = myComponent->getImageData();
    }
    else {
        data = vtkSmartPointer<vtkImageData>::New();
    }

    //-- image dimensions
    int* dims = data->GetDimensions();
    unsigned int totalDim = dims[0] * dims[1] * dims[2];

    //-- computate the histogram given the value data type
    switch (data->GetScalarType()) {
        default:
        case VTK_ID_TYPE:
        case VTK_BIT:
        case VTK_LONG:
        case VTK_UNSIGNED_LONG:
            break;
        case VTK_CHAR:
        case VTK_SIGNED_CHAR:
            fillHistogramTable<char> ((char*) data->GetScalarPointer(), totalDim,
                                      (char) lutMin, (char) lutMax);
            break;
        case VTK_UNSIGNED_CHAR:
            fillHistogramTable<unsigned char> ((unsigned char*) data->GetScalarPointer(), totalDim,
                                               (unsigned char) lutMin, (unsigned char) lutMax);
            break;
        case VTK_SHORT:
            fillHistogramTable<short> ((short*) data->GetScalarPointer(), totalDim,
                                       (short) lutMin, (short) lutMax);
            break;
        case VTK_UNSIGNED_SHORT:
            fillHistogramTable<unsigned short> ((unsigned short*) data->GetScalarPointer(), totalDim,
                                                (unsigned short) lutMin, (unsigned short) lutMax);
            break;
        case VTK_INT:
            fillHistogramTable<int> ((int*) data->GetScalarPointer(), totalDim,
                                     (int) lutMin, (int) lutMax);
            break;
        case VTK_UNSIGNED_INT:
            fillHistogramTable<unsigned int> ((unsigned int*) data->GetScalarPointer(), totalDim,
                                              (unsigned int) lutMin, (unsigned int) lutMax);
            break;
        case VTK_FLOAT:
            fillHistogramTable<float> ((float*) data->GetScalarPointer(), totalDim,
                                       (float) lutMin, (float) lutMax);
            break;
        case VTK_DOUBLE:
            fillHistogramTable<double> ((double*) data->GetScalarPointer(), totalDim,
                                        (double) lutMin, (double) lutMax);
            break;
    }

    //-- prepare draw
    ui.histogramGraphicsView->scene()->clear();
    QPen pen;
    pen.setWidth(1);
    pen.setColor(palette().background().color());

    //-- background color
    // Scaling the graphic view is needed: in case highestGreyLevel is greater than max int on Win 32bits
    // Qt paint canvas bug crashes. When using Qt5, try to remove the scaling code to see if the bug has been fixed.
    double defaultHeight = 256.0;
    double scalingFactor = defaultHeight / highestGreyLevel;
    QRectF sceneRectangle(lutMin, 0.0, nbHistoBins, defaultHeight);

    //-- draw color table
    QLinearGradient colorGradient(sceneRectangle.x(), sceneRectangle.height() / 2.0, sceneRectangle.width(), sceneRectangle.height() / 2.0);
    int nbOfColors = myComponent->getLut()->GetNumberOfTableValues();
    double* color;
    for (int i = 0; i < nbOfColors; i++) {
        color = myComponent->getLut()->GetTableValue(i);
        colorGradient.setColorAt(double(i) / double(nbOfColors), QColor::fromRgbF(color[0], color[1], color[2]));
    }
    QBrush gradientBrush(colorGradient);
    sceneRectItem = new QGraphicsRectItem(sceneRectangle);
    sceneRectItem->setPen(pen);
    sceneRectItem->setBrush(gradientBrush);
    ui.histogramGraphicsView->scene()->addItem(sceneRectItem);

    //-- show transparency
    double window = myComponent->getLut()->GetWindow();
    double level = myComponent->getLut()->GetLevel();
    if (ui.maxTransparency->isChecked()) {
        QPointF maxCorner(sceneRectangle.x() + level + window / 2.0, sceneRectangle.y());
        if (maxCorner.x() < sceneRectangle.width()) {
            ui.histogramGraphicsView->scene()->addRect(maxCorner.x(), maxCorner.y(), sceneRectangle.width() - maxCorner.x(), sceneRectangle.height(), pen, QBrush(palette().window()));
        }
    }
    if (ui.minTransparency->isChecked()) {
        QPointF minCorner(sceneRectangle.x() + level - window / 2.0, sceneRectangle.y());
        if (minCorner.x() > sceneRectangle.x()) {
            ui.histogramGraphicsView->scene()->addRect(sceneRectangle.x(), sceneRectangle.y(), minCorner.x(), sceneRectangle.height(), pen, QBrush(palette().window()));
        }
    }

    //-- draw histogram
    qreal barHeight;
    qreal barWidth = 1.0; //sceneRectangle.width() / double(nbHistoBins);
    double x = sceneRectangle.x();
    QPainterPath histogramPath(QPointF(x, sceneRectangle.height()));
    for (unsigned int i = 0; i < nbHistoBins; i++) {
        barHeight = greyLevels[i] * scalingFactor;
        histogramPath.lineTo(x, sceneRectangle.height() - barHeight);
        x += barWidth;
    }
    histogramPath.lineTo(sceneRectangle.width(), sceneRectangle.height());
    histogramPath.lineTo(x, sceneRectangle.height());
    pen.setColor(Qt::white);
    ui.histogramGraphicsView->scene()->addPath(histogramPath, pen, Qt::blue);

    //-- draw lut level and window
    pen.setColor(Qt::darkGreen);
    pen.setStyle(Qt::DashLine);

    // pen width depends on numbers of bars in the histogram
    int penWidth = 3.0; // (int)(sceneRectangle.width() / 100.0);
    pen.setWidth(penWidth);

    ui.histogramGraphicsView->scene()->addLine(level, sceneRectangle.y(), level, sceneRectangle.height(), pen);

    pen.setStyle(Qt::SolidLine);
    // create the proper path for the LUT
    QPointF leftCorner(level - window / 2.0, sceneRectangle.height());
    QPointF rightCorner(level + window / 2.0, sceneRectangle.y());
    QPainterPath path;
    path.moveTo(sceneRectangle.x(), sceneRectangle.height());
    path.lineTo(leftCorner);
    path.lineTo(rightCorner);
    path.lineTo(sceneRectangle.width(), sceneRectangle.y());

    pen.setWidth(penWidth);
    ui.histogramGraphicsView->scene()->addPath(path, pen);

    //-- fit all in viewer
    fitView();
}


//---------------------- fillHistogramTable ------------------------
template<class DATATYPE>
void ImageLutWidget::fillHistogramTable(DATATYPE* data, unsigned int dataDim, DATATYPE minVal, DATATYPE maxVal) {

    if (((DATATYPE)nbHistoBins) == 0) {
        CAMITK_ERROR(tr("Number of bins is equals to zero."))
    }

    // scale factor from [min,max] to [0..nbHistoBins]
    auto intervalLength = (DATATYPE)((maxVal - minVal) / (DATATYPE)nbHistoBins);

    if (intervalLength != 0) {
        //-- build histogram
        for (unsigned int i = 0; i < dataDim; i++) {
            // Find the corresponding interval index
            auto pointIndex = (unsigned int)((data[i] - minVal) / intervalLength);
            // build the histogram (increment the corresponding value)
            greyLevels[pointIndex] += 1.0;
        }

        //-- compute max value
        highestGreyLevel = 0;
        for (unsigned int i = 0; i < nbHistoBins; i++) {
            if (greyLevels[i] > highestGreyLevel) {
                highestGreyLevel = greyLevels[i];
            }
        }
    }
    else {
        CAMITK_ERROR(tr("Not enough levels in the image, level in [%1,%2]").arg(QString::number(minVal), QString::number(maxVal)))
    }
}

//---------------------- resizeEvent  ----------------------------
void ImageLutWidget::resizeEvent(QResizeEvent*) {
    fitView();
}

//---------------------- showEvent  ----------------------------
void ImageLutWidget::showEvent(QShowEvent*) {
    fitView();
}

//---------------------- fitView  ----------------------------
void ImageLutWidget::fitView() {
    ui.histogramGraphicsView->fitInView(sceneRectItem);
    ui.histogramGraphicsView->centerOn(sceneRectItem->rect().center());
    ui.histogramGraphicsView->setSceneRect(sceneRectItem->rect());
}

//---------------------- initLevelGUI  ----------------------------
void ImageLutWidget::initLevelGUI(int min, int max, int value) {
    initSlider(ui.sliderLutLevel, min, max, value);
    // add validator for the slide range
    QValidator* validator = new QIntValidator(min, max, this);
    ui.lineEditLutLevel->setValidator(validator);
    ui.lineEditLutLevel->setText(QString::number(value));
}

//---------------------- initWindowGUI  ----------------------------
void ImageLutWidget::initWindowGUI(int min, int max, int value) {
    initSlider(ui.sliderLutWindow, min, max, value);
    // add validator for the slide range
    QValidator* validator = new QIntValidator(min, max, this);
    ui.lineEditLutWindow->setValidator(validator);
    ui.lineEditLutWindow->setText(QString::number(value));
}

//---------------------- initSlider  ----------------------------
void ImageLutWidget::initSlider(QSlider* slider, int min, int max, int value) {
    // set slider range
    slider->setRange(min, max);
    // current value
    slider->setValue(value);
    // set steps
    int range = max - min;
    slider->setSingleStep(range / 100);
    slider->setPageStep(range / 10);
}

// ---------------------- lineEditLUTLevelChanged  ----------------------------
void ImageLutWidget::lineEditLUTLevelChanged() {
    // update slider value
    blockAllSignals(true);
    ui.sliderLutLevel->setValue(ui.lineEditLutLevel->text().toInt());
    blockAllSignals(false);

    // update palette color applied to the images and the histogram
    applyLUT();
}

// ---------------------- sliderLUTLevelChanged  ----------------------------
void ImageLutWidget::sliderLUTLevelChanged(int level) {
    // update line edit text
    blockAllSignals(true);
    ui.lineEditLutLevel->setText(QString::number(level));
    blockAllSignals(false);

    // update palette color applied to the images and the histogram
    applyLUT();
}

// ---------------------- sliderLUTWindowChanged  ----------------------------
void ImageLutWidget::sliderLUTWindowChanged(int window) {
    // update slider value
    blockAllSignals(true);
    ui.lineEditLutWindow->setText(QString::number(window));
    blockAllSignals(false);

    // update palette color applied to the images and the histogram
    applyLUT();
}

// ---------------------- lineEditLUTWindowChanged  ----------------------------
void ImageLutWidget::lineEditLUTWindowChanged() {
    // update line edit text
    blockAllSignals(true);
    ui.sliderLutWindow->setValue(ui.lineEditLutWindow->text().toInt());
    blockAllSignals(false);

    // update palette color applied to the images and the histogram
    applyLUT();
}

// ---------------------- invertButtonClicked  ----------------------------
void ImageLutWidget::invertButtonClicked() {
    invert = !invert;
    applyLUT();
    /*    QColor maxColor = ui.maxColor->palette().button().color();
        QColor minColor = ui.minColor->palette().button().color();
        ui.maxColor->setPalette(QPalette(minColor));
        ui.minColor->setPalette(QPalette(maxColor));
        */
    updateGradient();
}

// ---------------------- updateGradient  ----------------------------
void ImageLutWidget::updateGradient() {
    QColor maxColor = ui.maxColor->palette().button().color();
    QColor minColor = ui.minColor->palette().button().color();
    QString style = "background-color: qlineargradient(x1:0 y1:0, x2:1 y2:0, stop:0 rgba(" +
                    QString::number(minColor.red()) + ", " +
                    QString::number(minColor.green()) + ", " +
                    QString::number(minColor.blue()) + ", " +
                    QString::number(minColor.alpha()) + " ), stop:1 rgba( " +
                    QString::number(maxColor.red()) + ", " +
                    QString::number(maxColor.green()) + ", " +
                    QString::number(maxColor.blue()) + ", " +
                    QString::number(maxColor.alpha()) + " ) )";
    ui.colorScale->setStyleSheet(style);
}

// ---------------------- setMaxColor ----------------------------
void ImageLutWidget::setMaxColor() {
    QColor oldColor = ui.maxColor->palette().button().color();
    QColor maxColor = QColorDialog::getColor(oldColor, this, "Max color");

    ui.maxColor->setPalette(QPalette(maxColor));
    updateGradient();
    applyLUT();
}

// ---------------------- setMinColor ----------------------------
void ImageLutWidget::setMinColor() {
    QColor oldColor = ui.minColor->palette().button().color();
    QColor minColor = QColorDialog::getColor(oldColor, this, "Max color");

    ui.minColor->setPalette(QPalette(minColor));
    updateGradient();
    applyLUT();
}

// ---------------------- blockAllSignals ----------------------------
void ImageLutWidget::blockAllSignals(bool block) {
    ui.sliderLutLevel->blockSignals(block);
    ui.sliderLutWindow->blockSignals(block);
    ui.lineEditLutLevel->blockSignals(block);
    ui.lineEditLutWindow->blockSignals(block);
    ui.maxTransparency->blockSignals(block);
    ui.minTransparency->blockSignals(block);
    ui.inversion->blockSignals(block);
    ui.resetLut->blockSignals(block);
    ui.minColor->blockSignals(block);
    ui.maxColor->blockSignals(block);
}

