/*****************************************************************************
 * $CAMITK_LICENCE_BEGIN$
 *
 * CamiTK - Computer Assisted Medical Intervention ToolKit
 * (c) 2001-2025 Univ. Grenoble Alpes, CNRS, Grenoble INP - UGA, TIMC, 38000 Grenoble, France
 *
 * 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$
 ****************************************************************************/

#include "VolumeRenderingWidget.h"
#include "VolumeRenderingAction.h"

#include <QFile>
#include <QString>
#include <QFileDialog>
#include <QTextStream>

// -- Qt XML stuff
#include <QtXml/QDomDocument>
#include <QtXml/QDomElement>
#include <QtXml/QDomNodeList>

//-- CamiTK
#include <Log.h>

using namespace camitk;

// ---------------------- Constructor ----------------------
VolumeRenderingWidget::VolumeRenderingWidget(VolumeRenderingAction* myAction, QWidget* parent) : QWidget(parent) {
    this->myAction = myAction;
    ui.setupUi(this);
}

// ---------------------- Destructor ----------------------
VolumeRenderingWidget::~VolumeRenderingWidget() {
    // TODO manage this when the colormap changes
    // Remove all existing colors
    QList<ColorPointWidget*>::const_iterator colorIt;

    for (colorIt = colorPoints.constBegin(); colorIt != colorPoints.constEnd(); ++colorIt) {
        ColorPointWidget* point = (*colorIt);
        ui.colorsLayout->removeWidget(point);
        delete point;
    }

    colorPoints.clear();

    // Remove all existing transparencies
    QList<TransparencyPointWidget*>::const_iterator transIt;

    for (transIt = transparencies.constBegin(); transIt != transparencies.constEnd(); ++transIt) {
        TransparencyPointWidget* point = (*transIt);
        ui.transparenciesLayout->removeWidget(point);
        delete point;
    }

    gradientOpacities.clear();
    // Remove all existing grandient opacities
    QList<GradientOpacityWidget*>::const_iterator gradientIt;

    for (gradientIt = gradientOpacities.constBegin(); gradientIt != gradientOpacities.constEnd(); ++gradientIt) {
        GradientOpacityWidget* point = (*gradientIt);
        ui.gradientOpacitiesLayout->removeWidget(point);
        delete point;
    }

    gradientOpacities.clear();
}

// ---------------------- updateImage ----------------------
void VolumeRenderingWidget::updateUI(QString name, bool existVR) {
    ui.imageNameLabel->setText(name);
    ui.showCheckBox->setChecked(existVR && myAction->getParameterValue("Volume Rendering Visibility").toBool());

    // TODO create a VRColorMap class to manage load/save and add a QMap<QString*,VRColorMap*> to save
    // the map when the user switch between different images
    // note : see vrColorMap.xsd and default.clm, move the XML read/write from load/save to the new
    // VRColorMap class.
    // TODO create a list of nice default colormaps for TDM and MRI
    // TODO add a popup that will load the default colormap (instead of the refresh button)
    if (!existVR) {
        resetColorMap();
    }
}

// ---------------------- visibilityToggled ----------------------
void VolumeRenderingWidget::visibilityToggled(bool visibility) {
    myAction->setParameterValue("Volume Rendering Visibility", visibility);
}

// ---------------------- refreshVolumeRendering ----------------------
void VolumeRenderingWidget::refreshVolumeRendering() {
    // the user clicked on the refresh button, just signal it
    emit refresh();
}

// ---------------------- addColor ----------------------
void VolumeRenderingWidget::addColor() {
    ColorPointWidget* point = new ColorPointWidget(this);
    this->colorPoints.push_back(point);
    ui.colorsLayout->addWidget(point);
}

void VolumeRenderingWidget::addColor(int grayLevel, QColor color) {
    ColorPointWidget* point = new ColorPointWidget(this, grayLevel, color);
    this->colorPoints.push_back(point);
    ui.colorsLayout->addWidget(point);
}

// ---------------------- removeColorPoint ----------------------
void VolumeRenderingWidget::removeColorPoint(ColorPointWidget* point) {
    ui.colorsLayout->removeWidget(point);
    this->colorPoints.removeAt(colorPoints.indexOf(point));
    delete point;
}

// ---------------------- setDefaultColors ----------------------
void VolumeRenderingWidget::setDefaultColors(QMap<int, QColor> defaultColors) {
    // Remove all existing colors
    QList<ColorPointWidget*>::const_iterator colorIt;

    for (colorIt = colorPoints.constBegin(); colorIt != colorPoints.constEnd(); ++colorIt) {
        ColorPointWidget* point = (*colorIt);
        ui.colorsLayout->removeWidget(point);
        delete point;
    }

    colorPoints.clear();

    // Add default colors given in parameters
    QMap<int, QColor>::const_iterator it;

    for (it = defaultColors.constBegin(); it != defaultColors.constEnd(); ++it) {
        int gray = it.key();
        QColor color = it.value();
        addColor(gray, color);
    }

}

// ---------------------- getColorPoints ----------------------
QMap<int, QColor> VolumeRenderingWidget::getColorPoints() {
    QMap<int, QColor> colors;

    for (QList<ColorPointWidget*>::const_iterator it = colorPoints.constBegin(); it != colorPoints.constEnd(); ++it) {
        colors.insert((*it)->getGrayLevel(), (*it)->getColor());
    }

    return colors;
}

// ---------------------- addTransparency ----------------------
void VolumeRenderingWidget::addTransparency() {
    TransparencyPointWidget* point = new TransparencyPointWidget(this);
    this->transparencies.push_back(point);
    ui.transparenciesLayout->addWidget(point);
}

void VolumeRenderingWidget::addTransparency(int grayLevel, double transparency) {
    TransparencyPointWidget* point = new TransparencyPointWidget(this, grayLevel, transparency);
    this->transparencies.push_back(point);
    ui.transparenciesLayout->addWidget(point);
}

// ---------------------- removeTransparencyPoint ----------------------
void VolumeRenderingWidget::removeTransparencyPoint(TransparencyPointWidget* point) {
    ui.transparenciesLayout->removeWidget(point);
    this->transparencies.removeAt(transparencies.indexOf(point));
    delete point;
}

// ---------------------- setDefaultTransparencies ----------------------
void VolumeRenderingWidget::setDefaultTransparencies(QMap<int, double> defaultTransparencies) {
    // Remove all existing transparencies
    QList<TransparencyPointWidget*>::const_iterator transIt;

    for (transIt = transparencies.constBegin(); transIt != transparencies.constEnd(); ++transIt) {
        TransparencyPointWidget* point = (*transIt);
        ui.transparenciesLayout->removeWidget(point);
        delete point;
    }

    transparencies.clear();

    // Add default colors given in parameters
    QMap<int, double>::const_iterator it;

    for (it = defaultTransparencies.constBegin(); it != defaultTransparencies.constEnd(); ++it) {
        int gray = it.key();
        double transparency = it.value();
        addTransparency(gray, transparency);
    }
}

// ---------------------- getTransparencyPoints ----------------------
QMap<int, double> VolumeRenderingWidget::getTransparencyPoints() {
    QMap<int, double> transPoints;

    for (QList<TransparencyPointWidget*>::const_iterator it = transparencies.constBegin(); it != transparencies.constEnd(); ++it) {
        transPoints.insert((*it)->getGrayLevel(), (*it)->getTransparency());
    }

    return transPoints;
}

// ---------------------- addGradientOpacity ----------------------
void VolumeRenderingWidget::addGradientOpacity() {
    GradientOpacityWidget* point = new GradientOpacityWidget(this);
    this->gradientOpacities.push_back(point);
    ui.gradientOpacitiesLayout->addWidget(point);
}

void VolumeRenderingWidget::addGradientOpacity(int grayLevel, double opacity) {
    GradientOpacityWidget* point = new GradientOpacityWidget(this, grayLevel, opacity);
    this->gradientOpacities.push_back(point);
    ui.gradientOpacitiesLayout->addWidget(point);
}

// ---------------------- removeOpacityPoint ----------------------
void VolumeRenderingWidget::removeOpacityPoint(GradientOpacityWidget* point) {
    ui.gradientOpacitiesLayout->removeWidget(point);
    this->gradientOpacities.removeAt(gradientOpacities.indexOf(point));
    delete point;
}

// ---------------------- setDefaultOpacities ----------------------
void VolumeRenderingWidget::setDefaultOpacities(QMap<int, double> defaultOpacities) {
    // Remove all existing colors
    QList<GradientOpacityWidget*>::const_iterator gradientIt;

    for (gradientIt = gradientOpacities.constBegin(); gradientIt != gradientOpacities.constEnd(); ++gradientIt) {
        GradientOpacityWidget* point = (*gradientIt);
        ui.gradientOpacitiesLayout->removeWidget(point);
        delete point;
    }

    gradientOpacities.clear();

    // Add default colors given in parameters
    QMap<int, double>::const_iterator it;

    for (it = defaultOpacities.constBegin(); it != defaultOpacities.constEnd(); ++it) {
        int gray = it.key();
        double opacity = it.value();
        addGradientOpacity(gray, opacity);
    }
}

// ---------------------- getOpacityPoints ----------------------
QMap<int, double> VolumeRenderingWidget::getOpacityPoints() {
    QMap<int, double> opacities;

    for (QList<GradientOpacityWidget*>::const_iterator it = gradientOpacities.constBegin(); it != gradientOpacities.constEnd(); ++it) {
        opacities.insert((*it)->getGrayLevel(), (*it)->getOpacity());
    }

    return opacities;
}

// ---------------------- setambient ----------------------
void VolumeRenderingWidget::setAmbient(double ambient) {
    ui.ambientSpinBox->setValue(ambient);
}

// ---------------------- getambient ----------------------
double VolumeRenderingWidget::getAmbient() {
    return ui.ambientSpinBox->value();
}

// ---------------------- setDiffuse ----------------------
void VolumeRenderingWidget::setDiffuse(double diffuse) {
    ui.diffuseSpinBox->setValue(diffuse);
}

// ---------------------- getDiffuse ----------------------
double VolumeRenderingWidget::getDiffuse() {
    return ui.diffuseSpinBox->value();
}

// ---------------------- setSpecular ----------------------
void VolumeRenderingWidget::setSpecular(double specular) {
    ui.specularSpinBox->setValue(specular);
}

// ---------------------- getSpecular ----------------------
double VolumeRenderingWidget::getSpecular() {
    return ui.specularSpinBox->value();
}

// ---------------------- load ----------------------
void VolumeRenderingWidget::load(QString filename) {
    QDomDocument doc;

    QFile file(filename);

    if (!file.open(QIODevice::ReadOnly)) {
        CAMITK_ERROR(tr("File not found: \"%1\"").arg(filename))
        return;
    }

    if (!doc.setContent(&file)) {
        file.close();
        CAMITK_ERROR(tr("File \"%1\" have no valid root (not a well formed XML document).").arg(filename))
        return;
    }

    QString rootName = doc.documentElement().nodeName();

    if (rootName != QString("vrColorMap")) {
        file.close();
        CAMITK_ERROR(tr("File \"%1\" is not a valid XML document (expecting <vrColorMap> as root element).").arg(filename))
        return;
    }

    // Ok, after all this checking, the file seems to be good looking,
    // set it as the right xml doc...
    file.close();

    QMap<int, double> defaultTransparencies;
    QMap<int, QColor> defaultColors;
    QMap<int, double> defaultGradientOpacities;
    double defaultambient;
    double defaultDiffuse;
    double defaultSpecular;

    QDomElement docElem = doc.documentElement();

    QDomNodeList xmlTransparencies = docElem.elementsByTagName("transparencies");
    QDomNodeList transparencyList = xmlTransparencies.item(0).toElement().elementsByTagName("transparency");

    for (int i = 0; i < transparencyList.size(); i++) {
        QDomElement aTransaprency = transparencyList.item(i).toElement();
        int gray = aTransaprency.attribute("gray").toInt();
        double value = aTransaprency.attribute("value").toDouble();
        defaultTransparencies.insert(gray, value);
    }

    QDomNodeList xmlColors = docElem.elementsByTagName("colors");
    QDomNodeList colorList = xmlColors.item(0).toElement().elementsByTagName("color");

    for (int i = 0; i < colorList.size(); i++) {
        QDomElement aColor = colorList.item(i).toElement();
        int gray = aColor.attribute("gray").toInt();
        int red  = aColor.attribute("red").toInt();
        int green = aColor.attribute("green").toInt();
        int blue = aColor.attribute("blue").toInt();
        int alpha = aColor.attribute("alpha").toInt();

        defaultColors.insert(gray, QColor(red, green, blue, alpha));
    }

    QDomNodeList xmlGradientOpacities = docElem.elementsByTagName("gradientOpacities");
    QDomNodeList gradientOpacitiesList = xmlGradientOpacities.item(0).toElement().elementsByTagName("gradientOpacity");

    for (int i = 0; i < gradientOpacitiesList.size(); i++) {
        QDomElement aTransaprency = gradientOpacitiesList.item(i).toElement();
        int gray = aTransaprency.attribute("gray").toInt();
        double value = aTransaprency.attribute("value").toDouble();
        defaultGradientOpacities.insert(gray, value);
    }

    QDomNodeList xmlShading = docElem.elementsByTagName("shading");
    QDomElement xmlambient = xmlShading.item(0).toElement().elementsByTagName("ambient").item(0).toElement();
    defaultambient = xmlambient.attribute("value").toDouble();

    QDomElement xmlDiffuse = xmlShading.item(0).toElement().elementsByTagName("diffuse").item(0).toElement();
    defaultDiffuse = xmlDiffuse.attribute("value").toDouble();

    QDomElement xmlSpecular = xmlShading.item(0).toElement().elementsByTagName("specular").item(0).toElement();
    defaultSpecular = xmlSpecular.attribute("value").toDouble();

    setDefaultTransparencies(defaultTransparencies);
    setDefaultColors(defaultColors);
    setDefaultOpacities(defaultGradientOpacities);
    ui.ambientSpinBox->setValue(defaultambient);
    ui.diffuseSpinBox->setValue(defaultDiffuse);
    ui.specularSpinBox->setValue(defaultSpecular);

    // update the action property
    myAction->setParameterValue("Color Map File", filename);
}

// ---------------------- loadColorMap ----------------------
void VolumeRenderingWidget::loadColorMap() {
    QString filename = QFileDialog::getOpenFileName(this, tr("Open a Volume Rendering Colormap XML Document"));
    load(filename);
}

// ---------------------- saveColorMap ----------------------
void VolumeRenderingWidget::saveColorMap() {
    QString filename = QFileDialog::getSaveFileName(this, tr("Save to a Volume Rendering Colormap XML Document"));

    QDomDocument doc;
    doc.createProcessingInstruction("xml", "version=\"1.0\" encoding=\"utf-8\"");
    QDomElement racine = doc.createElement("vrColorMap");
    doc.appendChild(racine);

    QDomElement xmlTransparencies = doc.createElement("transparencies");
    racine.appendChild(xmlTransparencies);

    QList<TransparencyPointWidget*>::const_iterator transIt;

    for (transIt = transparencies.constBegin(); transIt != transparencies.constEnd(); ++transIt) {
        int gray = (*transIt)->getGrayLevel();
        double transparency = (*transIt)->getTransparency();
        QString tmp;// = QString:number(transparency, 'g', 3);

        QDomElement trans = doc.createElement("transparency");
        trans.setAttribute("gray", tmp.number(gray));
        trans.setAttribute("value", tmp.number(transparency));

        xmlTransparencies.appendChild(trans);
    }

    QDomElement xmlColors = doc.createElement("colors");
    racine.appendChild(xmlColors);

    QList<ColorPointWidget*>::const_iterator colorIt;

    for (colorIt = colorPoints.constBegin(); colorIt != colorPoints.constEnd(); ++colorIt) {
        int gray = (*colorIt)->getGrayLevel();
        QColor color = (*colorIt)->getColor();
        QString tmp;

        QDomElement trans = doc.createElement("color");
        trans.setAttribute("gray", tmp.number(gray));
        trans.setAttribute("red",   tmp.number(color.red()));
        trans.setAttribute("green", tmp.number(color.green()));
        trans.setAttribute("blue",  tmp.number(color.blue()));
        trans.setAttribute("alpha", tmp.number(color.alpha()));

        xmlColors.appendChild(trans);
    }

    QDomElement xmlGradientOpacities = doc.createElement("gradientOpacities");
    racine.appendChild(xmlGradientOpacities);

    QList<GradientOpacityWidget*>::const_iterator gradIt;

    for (gradIt = gradientOpacities.constBegin(); gradIt != gradientOpacities.constEnd(); ++gradIt) {
        int gray = (*gradIt)->getGrayLevel();
        double transparency = (*gradIt)->getOpacity();
        QString tmp;// = QString:number(transparency, 'g', 3);

        QDomElement trans = doc.createElement("gradientOpacity");
        trans.setAttribute("gray", tmp.number(gray));
        trans.setAttribute("value", tmp.number(transparency));

        xmlGradientOpacities.appendChild(trans);
    }

    QDomElement xmlShading = doc.createElement("shading");
    racine.appendChild(xmlShading);

    QString shadeTmp;
    QDomElement xmlambient = doc.createElement("ambient");
    xmlambient.setAttribute("value", shadeTmp.number(ui.ambientSpinBox->value()));
    xmlShading.appendChild(xmlambient);

    QDomElement xmlDiffuse = doc.createElement("diffuse");
    xmlDiffuse.setAttribute("value", shadeTmp.number(ui.diffuseSpinBox->value()));
    xmlShading.appendChild(xmlDiffuse);

    QDomElement xmlSpecular = doc.createElement("specular");
    xmlSpecular.setAttribute("value", shadeTmp.number(ui.specularSpinBox->value()));
    xmlShading.appendChild(xmlSpecular);

    QFile file(filename);

    if (file.open(QFile::WriteOnly)) {
        QTextStream fileStream(&file);
        doc.save(fileStream, 2);
        file.close();
    }
    else {
        CAMITK_ERROR(tr("File \"%1\" not found: \".").arg(filename))
        return;
    }

}

// ---------------------- resetColorMap ----------------------
void VolumeRenderingWidget::resetColorMap() {
    load(myAction->getParameterValue("Color Map File").toString());
}

// ---------------------- ambientLightSliderChanged ----------------------
void VolumeRenderingWidget::ambientLightSliderChanged(int value) {
    double val = value / 100.0;
    ui.ambientSpinBox->setValue(val);
}

// ---------------------- ambientLightSpinBoxChanged ----------------------
void VolumeRenderingWidget::ambientLightSpinBoxChanged(double value) {
    int val = (int)(value * 100);
    ui.ambientSlider->blockSignals(true);
    ui.ambientSlider->setValue(val);
    ui.ambientSlider->blockSignals(false);
}

// ---------------------- diffuseLightSliderChanged ----------------------
void VolumeRenderingWidget::diffuseLightSliderChanged(int value) {
    double val = value / 100.0;
    ui.diffuseSpinBox->setValue(val);
}

// ---------------------- diffuseLightSpinBoxChanged ----------------------
void VolumeRenderingWidget::diffuseLightSpinBoxChanged(double value)  {
    int val = (int)(value * 100);
    ui.diffuseSlider->blockSignals(true);
    ui.diffuseSlider->setValue(val);
    ui.diffuseSlider->blockSignals(false);
}

// ---------------------- specularLightSliderChanged ----------------------
void VolumeRenderingWidget::specularLightSliderChanged(int value) {
    double val = value / 100.0;
    ui.specularSpinBox->setValue(val);
}

// ---------------------- specularLightSpinBoxChanged ----------------------
void VolumeRenderingWidget::specularLightSpinBoxChanged(double value) {
    int val = (int)(value * 100);
    ui.specularSlider->blockSignals(true);
    ui.specularSlider->setValue(val);
    ui.specularSlider->blockSignals(false);
}

