/*****************************************************************************
 * $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$
 ****************************************************************************/


// -- Application Fancy stuff
#include "FancyMainWindow.h"

#include <Application.h>
#include <Action.h>
#include <InteractiveViewer.h>
#include <ImageComponent.h>
#include <SingleImageComponent.h>
#include <ArbitrarySingleImageComponent.h>
#include <Log.h>

// -- Qt stuff
#include <SliderSpinBoxWidget.h>
#include <QFrame>
#include <QLayout>
#include <QStackedLayout>
#include <QToolBar>

// - Std stuff
#include <utility> // as_const

using namespace camitk;

// ------------- constructor -----------------
FancyMainWindow::FancyMainWindow() : MainWindow("Fancy") {
    comp = nullptr;
    mainWidget = new QWidget;
    ui.setupUi(mainWidget);

    //-- actions of the File menu
    ui.openButton->setIcon(QPixmap(":/fileOpen"));
    ui.openButton->setShortcut(QKeySequence::Open);
    ui.dial->show();

    // add the medical image viewer
    visibility = VIEWER_AXIAL;

    // for this application, force the medical image viewer to be embedded
    Viewer* medicalImageViewer = Application::getViewer("Medical Image Viewer");
    if (medicalImageViewer != nullptr) {
        medicalImageViewer->setEmbedder(ui.viewer);
    }
    else {
        CAMITK_ERROR(tr("Cannot find \"Medical Image Viewer\". This viewer is mandatory for running camitk-fancy."))
    }

    // modify default slice viewer colors
    QStringList viewerNames;
    viewerNames << "Axial Viewer" << "Sagittal Viewer" << "Coronal Viewer" << "Arbitrary Viewer";

    for (auto& viewerName : std::as_const(viewerNames)) {
        // hide slide bar and modify the color and background
        InteractiveViewer* sliceViewer = dynamic_cast<InteractiveViewer*>(Application::getViewer(viewerName));

        if (sliceViewer != nullptr) {
            sliceViewer->setSideFrameVisible(false);
            sliceViewer->setGradientBackground(false);
            sliceViewer->setBackgroundColor(ui.dial->palette().window().color());
            sliceViewer->getToolBar()->hide();
        }
        else {
            CAMITK_ERROR(tr("Cannot find \"%1\". This viewer is mandatory for running camitk-fancy.").arg(viewerName))
        }
    }

    // add a 3D small little viewer (note that we need to set the visibility of this viewer
    // to true during refresh, as by default, the component does not know about it
    // and its visibility is false)
    viewer3D = dynamic_cast<InteractiveViewer*>(Application::getNewViewer("Small 3D Viewer", "InteractiveGeometryViewer"));
    Application::registerViewer(viewer3D);
    viewer3D->setEmbedder(ui.viewer3DLayout);
    viewer3D->setSideFrameVisible(false);
    viewer3D->setGradientBackground(false);
    viewer3D->setBackgroundColor(ui.dial->palette().window().color());
    viewer3D->getToolBar()->hide();

    updateAngleSlider(ui.xAngledial, ui.xAngleValue);
    updateAngleSlider(ui.yAngledial, ui.yAngleValue);
    updateAngleSlider(ui.zAngledial, ui.zAngleValue);
    showAngleDials(false);

    connect(ui.openButton, SIGNAL(clicked()),         this, SLOT(fancyFileOpen()));
    connect(ui.axial,      SIGNAL(clicked()),         this, SLOT(layoutChanged()));
    connect(ui.coronal,    SIGNAL(clicked()),         this, SLOT(layoutChanged()));
    connect(ui.saggital,   SIGNAL(clicked()),         this, SLOT(layoutChanged()));
    connect(ui.abstract,   SIGNAL(clicked()),         this, SLOT(layoutChanged()));
    connect(ui.xAngledial, SIGNAL(valueChanged(int)), this, SLOT(angleDialValueChanged(int)));
    connect(ui.yAngledial, SIGNAL(valueChanged(int)), this, SLOT(angleDialValueChanged(int)));
    connect(ui.zAngledial, SIGNAL(valueChanged(int)), this, SLOT(angleDialValueChanged(int)));
    connect(ui.dial,       SIGNAL(valueChanged(int)), this, SLOT(dialValueChanged(int)));

    layoutChanged();

    setCentralWidget(mainWidget);
}

// ------------- destructor -----------------
FancyMainWindow::~FancyMainWindow() {
    delete mainWidget;
}

// ------------- destructor -----------------
void FancyMainWindow::closeEvent(QCloseEvent* event) {
    // the current component is going to be closed, avoid dangling pointer
    comp = nullptr;
    MainWindow::closeEvent(event);
}

// ------------- updateComponent -----------------
void FancyMainWindow::updateComponent() {
    // get the current state from the current selected component top level
    comp = nullptr;

    if (Application::getTopLevelComponents().size() > 0) {
        comp = dynamic_cast<ImageComponent*>(Application::getTopLevelComponents().last());
    }

    viewer3D->resetCamera();
}

// ------------- fancyFileOpen -----------------
void FancyMainWindow::fancyFileOpen() {
    // the current component is going to be closed, avoid dangling pointer
    comp = nullptr;
    Application::getAction("Close All")->trigger(this);
    Application::getAction("Open")->trigger(this);

    updateComponent();
    refresh();
}

// -------------------- updateAngleSlider --------------------
void FancyMainWindow::updateAngleSlider(QDial* dial, QLabel* label) {
    dial->blockSignals(true);
    dial->setMinimum(0);
    dial->setMaximum(360);
    dial->blockSignals(false);

    if (label == ui.xAngleValue) {
        label->setText("X : <tt>" + QString("%1").arg(dial->value(), 3) + "</tt>°");
    }
    else {
        if (label == ui.yAngleValue) {
            label->setText("Y : <tt>" + QString("%1").arg(dial->value(), 3) + "</tt>°");
        }
        else {
            if (label == ui.zAngleValue) {
                label->setText("Z : <tt>" + QString("%1").arg(dial->value(), 3) + "</tt>°");
            }
        }
    }

    label->update();

}

// -------------------- updateDialSlider --------------------
void FancyMainWindow::updateDialSlider() {
    int currentSliceId, minSliceId, maxSliceId;
    currentSliceId = minSliceId = maxSliceId = 0;

    updateComponent();

    if (comp != nullptr) {
        switch (visibility) {
            case VIEWER_AXIAL:
                currentSliceId = comp->getAxialSlices()->getSlice();
                maxSliceId = comp->getAxialSlices()->getNumberOfSlices();
                break;

            case VIEWER_CORONAL:
                currentSliceId = comp->getCoronalSlices()->getSlice();
                maxSliceId = comp->getCoronalSlices()->getNumberOfSlices();
                break;

            case VIEWER_SAGITTAL:
                currentSliceId = comp->getSagittalSlices()->getSlice();
                maxSliceId = comp->getSagittalSlices()->getNumberOfSlices();
                break;

            case VIEWER_ARBITRARY:
                // this is a percentage of translation (50% = half way through the volume)
                currentSliceId = comp->getArbitrarySlices()->getPropertyValue("Translation").toDouble() * 100.0;
                maxSliceId = 100;
                break;

            default:
                break;

        }
    }

    ui.dial->blockSignals(true);
    ui.dial->setMinimum(minSliceId);
    ui.dial->setMaximum(maxSliceId);
    ui.dial->setValue(currentSliceId);
    ui.dial->blockSignals(false);

    ui.slideValue->setText(QString("%1").arg((currentSliceId + 1), 3) + "/" + QString("%1").arg(maxSliceId, 3));
    ui.slideValue->update();
}

// -------------------- showAngleDials --------------------
void FancyMainWindow::showAngleDials(bool isShown) {
    ui.xAngledial->setVisible(isShown);
    ui.xAngleValue->setVisible(isShown);
    ui.yAngledial->setVisible(isShown);
    ui.yAngleValue->setVisible(isShown);
    ui.zAngledial->setVisible(isShown);
    ui.zAngleValue->setVisible(isShown);
}

// -------------------- layoutChanged --------------------
void FancyMainWindow::layoutChanged() {
    if (ui.axial->isChecked()) {
        visibility = VIEWER_AXIAL;
    }
    else {
        if (ui.saggital->isChecked()) {
            visibility = VIEWER_SAGITTAL;
        }
        else {
            if (ui.coronal->isChecked()) {
                visibility = VIEWER_CORONAL;
            }
            else {
                visibility = VIEWER_ARBITRARY;
            }
        }
    }

    // switch off all viewers
    Application::getViewer("3D Viewer")->setVisible(false);
    Application::getViewer("Axial Viewer")->setVisible(false);
    Application::getViewer("Coronal Viewer")->setVisible(false);
    Application::getViewer("Sagittal Viewer")->setVisible(false);
    Application::getViewer("Arbitrary Viewer")->setVisible(false);
    // show the visible viewer
    getVisibleViewer()->setVisible(true);

    ui.dial->show();

    ui.slideValue->show();

    showAngleDials(visibility == VIEWER_ARBITRARY);

    refresh();
}

// -------------------- angleDialValueChanged --------------------
void FancyMainWindow::angleDialValueChanged(int value) {
    if (comp != nullptr) {
        if (visibility == VIEWER_ARBITRARY && comp->getArbitrarySlices() != nullptr) {
            comp->getArbitrarySlices()->setPropertyValue("Rotation", QVector3D(ui.xAngledial->value(), ui.yAngledial->value(), ui.zAngledial->value()));
        }
    }

    updateAngleSlider(ui.xAngledial, ui.xAngleValue);
    updateAngleSlider(ui.yAngledial, ui.yAngleValue);
    updateAngleSlider(ui.zAngledial, ui.zAngleValue);
    refresh();
}

// -------------------- dialValueChanged --------------------
void FancyMainWindow::dialValueChanged(int value) {
    if (comp != nullptr) {
        if (visibility == VIEWER_ARBITRARY && comp->getArbitrarySlices() != nullptr) {
            comp->getArbitrarySlices()->setPropertyValue("Translation", ((double) value) / 100.0);
        }
        else {
            getVisibleViewer()->sliderChanged(value);
        }

        updateDialSlider();
        refresh();
    }
}

// -------------------- refreshView --------------------
void FancyMainWindow::refresh() {
    getVisibleViewer()->refresh();

    // force visualization of arbitrary slice in 3D
    // We need to set the visibility of our viewer3D to true during as by default
    // the component does not know about it and the visibility in viewer3D is false
    if (comp != nullptr) {
        if (comp->getAxialSlices() != nullptr) {
            comp->getAxialSlices()->setVisibility(viewer3D->getName(), true);
        }

        if (comp->getSagittalSlices() != nullptr) {
            comp->getSagittalSlices()->setVisibility(viewer3D->getName(), true);
        }

        if (comp->getCoronalSlices() != nullptr) {
            comp->getCoronalSlices()->setVisibility(viewer3D->getName(), true);
        }

        if (comp->getArbitrarySlices() != nullptr) {
            comp->getArbitrarySlices()->setVisibility(viewer3D->getName(), true);
        }
    }

    viewer3D->refresh();
    updateDialSlider();
}

// -------------------- getVisibleViewer --------------------
InteractiveViewer* FancyMainWindow::getVisibleViewer() {
    Viewer* visibleViewer = nullptr;

    switch (visibility) {
        case VIEWER_AXIAL:
            visibleViewer = Application::getViewer("Axial Viewer");
            break;

        case VIEWER_CORONAL:
            visibleViewer = Application::getViewer("Coronal Viewer");
            break;

        case VIEWER_SAGITTAL:
            visibleViewer = Application::getViewer("Sagittal Viewer");
            break;

        default: // arbitrary (and... 3D!)
            visibleViewer = Application::getViewer("Arbitrary Viewer");
            break;
    }

    return dynamic_cast<InteractiveViewer*>(visibleViewer);
}
