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

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

#include "Core.h"
#include "Application.h"
#include "ui_Console.h"
#include "Viewer.h"
#include "Action.h"
#include "Component.h"
#include "PropertyObject.h"
#include "LogSyntaxHighlighter.h"
#include "Log.h"

// -- QT stuff
#include <QAction>
#include <QToolBar>
#include <QProgressBar>
#include <QStatusBar>
#include <QDockWidget>
#include <QSettings>
#include <QUrl>
#include <QCloseEvent>
#include <QMimeData>
#include <QStackedLayout>

namespace camitk {
// ------------- constructor -----------------
MainWindow::MainWindow(QString title) : QMainWindow() {
    // set window title
    mainTitle = title;
    setWindowTitle(title);

    setWindowIcon(QIcon(":/camiTKIcon"));

    // prepare GUI
    showStatusBar(false);

    // add permanent widget to the status bar (toggle console and progress bar)
    QWidget* statusBarAdditionalWidget = new QWidget();

    QGridLayout* statusBarLayout = new QGridLayout(statusBarAdditionalWidget);

    // if actions are available, add a toggle console
    Action* toggleAction = Application::getAction("Toggle Log Console");

    if (toggleAction) {
        auto* toggleConsole = new QToolBar();

        toggleConsole->addAction(toggleAction->getQAction());
        statusBarLayout->addWidget(toggleConsole, 0, 0, 1, 1, Qt::AlignVCenter | Qt::AlignLeft);
    }

    myProgressBar = new QProgressBar();
    myProgressBar->setMaximum(100);
    statusBarLayout->addWidget(myProgressBar, 0, 1, 1, 1, Qt::AlignVCenter | Qt::AlignRight);

    statusBar()->addPermanentWidget(statusBarAdditionalWidget);

    // create the console window
    consoleWindow = new QDockWidget();
    consoleWindow->setObjectName("Console Window");
    consoleWindow->setAllowedAreas(Qt::BottomDockWidgetArea);
    consoleWindow->setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetFloatable);

    Ui::ui_Console ui;
    ui.setupUi(consoleWindow);

    switch (Log::getLogger()->getLogLevel()) {
        case InterfaceLogger::ERROR:
            ui.errorLogLevelButton->setChecked(true);
            break;

        case InterfaceLogger::WARNING:
            ui.warningLogLevelButton->setChecked(true);
            break;

        case InterfaceLogger::INFO:
            ui.infoLogLevelButton->setChecked(true);
            break;

        case InterfaceLogger::TRACE:
            ui.traceLogLevelButton->setChecked(true);
            break;

        default:
            ui.noneLogLevelButton->setChecked(true);
            break;
    }

    ui.toggleDebug->setChecked(Log::getLogger()->getDebugInformation());
    ui.toggleTimestamp->setChecked(Log::getLogger()->getTimeStampInformation());

    // using new C++11 lambda, see https://wiki.qt.io/New_Signal_Slot_Syntax
    // advantage: ui is known here, no need to add a new private member
    connect(ui.noneLogLevelButton, &QPushButton::clicked, [ = ](bool) {
        ui.errorLogLevelButton->setChecked(false);
        ui.warningLogLevelButton->setChecked(false);
        ui.infoLogLevelButton->setChecked(false);
        ui.traceLogLevelButton->setChecked(false);
        Application::getPropertyObject()->setProperty("Logger Level", InterfaceLogger::NONE);
    });
    connect(ui.errorLogLevelButton, &QPushButton::clicked, [ = ](bool) {
        ui.noneLogLevelButton->setChecked(false);
        ui.warningLogLevelButton->setChecked(false);
        ui.infoLogLevelButton->setChecked(false);
        ui.traceLogLevelButton->setChecked(false);
        Application::getPropertyObject()->setProperty("Logger Level", InterfaceLogger::ERROR);
    });
    connect(ui.warningLogLevelButton, &QPushButton::clicked, [ = ](bool) {
        ui.noneLogLevelButton->setChecked(false);
        ui.errorLogLevelButton->setChecked(false);
        ui.infoLogLevelButton->setChecked(false);
        ui.traceLogLevelButton->setChecked(false);
        Application::getPropertyObject()->setProperty("Logger Level", InterfaceLogger::WARNING);
    });
    connect(ui.infoLogLevelButton, &QPushButton::clicked, [ = ](bool) {
        ui.noneLogLevelButton->setChecked(false);
        ui.errorLogLevelButton->setChecked(false);
        ui.warningLogLevelButton->setChecked(false);
        ui.traceLogLevelButton->setChecked(false);
        Application::getPropertyObject()->setProperty("Logger Level", InterfaceLogger::INFO);
    });
    connect(ui.traceLogLevelButton, &QPushButton::clicked, [ = ](bool) {
        ui.noneLogLevelButton->setChecked(false);
        ui.errorLogLevelButton->setChecked(false);
        ui.warningLogLevelButton->setChecked(false);
        ui.infoLogLevelButton->setChecked(false);
        Application::getPropertyObject()->setProperty("Logger Level", InterfaceLogger::TRACE);
    });
    connect(ui.toggleDebug, &QPushButton::clicked, [ = ](bool) {
        Application::getPropertyObject()->setProperty("Display Debug Information to Log Message", !Log::getLogger()->getDebugInformation());
    });
    connect(ui.toggleTimestamp, &QPushButton::clicked, [ = ](bool) {
        Application::getPropertyObject()->setProperty("Display Time Stamp Information to Log Message", !Log::getLogger()->getTimeStampInformation());
    });

    consoleWindowTextEdit = ui.consoleTextEdit;

    // setup the syntax highlighter
    logSyntaxHighlighter = new LogSyntaxHighlighter(ui.consoleTextEdit->document());

    // add the find logic
    connect(ui.findLineEdit, &QLineEdit::textChanged, [ =, this ](const QString & searchText) {
        logSyntaxHighlighter->setHighlight(searchText);
    });

    connect(ui.rehighlightButton, &QPushButton::pressed, [ =, this ]() {
        logSyntaxHighlighter->rehighlight();
    });

    consoleWindow->hide();
    addDockWidget(Qt::BottomDockWidgetArea, consoleWindow);

    // create the default (empty) central widget's layout inside which the central viewers can be embedded.
    // (for viewer debugging purpose it is a good idea to name the layout)
    centralLayout = new QStackedLayout();
    centralLayout->setObjectName("MainWindow Central Layout");
    QFrame* centralFrame = new QFrame();
    centralFrame->setLayout(centralLayout);
    setCentralWidget(centralFrame);
    centralViewer = nullptr;

    // accept drag and drop events
    setAcceptDrops(true);

    // set the ready message
    statusBar()->showMessage(tr("Ready."), 10000);
}

// ------------- destructor -----------------
MainWindow::~MainWindow() {
    delete consoleWindow;
    consoleWindow = nullptr;

    // delete the dock widgets
    // (the viewers should have been deleted and removed from the dock before hand
    // otherwise it's a double delete)
    auto dockList = dockWidgetMap.values();
    while (!dockList.isEmpty()) {
        delete dockList.takeFirst();
    }
}

// ------------- getName -----------------
QString MainWindow::getName() const {
    return mainTitle;
}

// ------------- setWindowSubtitle -----------------
void MainWindow::setWindowSubtitle(QString subtitle) {
    setWindowTitle(mainTitle + " - [ " + subtitle + " ]");
}

// ------------- addViewer -----------------
bool MainWindow::addViewer(Viewer* theViewer) {
    if (!viewers.contains(theViewer)) {
        viewers.append(theViewer);

        // connect
        connect(theViewer, SIGNAL(selectionChanged()), this, SLOT(refresh()));

        return true;
    }

    return false;
}

// ------------- showDockViewer -----------------
void MainWindow::showDockViewer(Viewer* theViewer, bool visible) {
    if (dockWidgetMap.contains(theViewer)) {
        // set visibility of the viewer
        dockWidgetMap[theViewer]->setVisible(visible);
    }
}

// -------------------- refreshViewers --------------------
void MainWindow::refreshViewers() {
    for (Viewer* v : viewers) {
        v->refresh();
    }
}

// ------------- addDockViewer -----------------
void MainWindow::addDockViewer(Qt::DockWidgetArea dockingArea, Viewer* theViewer) {
    if (addViewer(theViewer)) {
        // create the dock widget and insert it only if the viewer has a widget
        QDockWidget* viewerDock = new QDockWidget();

        if (theViewer->setDockWidget(viewerDock)) {
            // add the dock
            addDockWidget(dockingArea, viewerDock);

            // update the map
            dockWidgetMap.insert(theViewer, viewerDock);
        }
        else {
            delete viewerDock;
        }

    }

    // show the viewer anyway
    showDockViewer(theViewer, true);
}


// ------------- setCentralViewer -----------------
void MainWindow::setCentralViewer(Viewer* theViewer) {
    addViewer(theViewer);

    // update the pointer
    centralViewer = theViewer;

    // stack the viewer inside the central viewer
    centralViewer->setEmbedder(centralLayout);

    // save current central viewer in the settings (only if the central widget is visible
    if (centralWidget()->isVisible()) {
        QSettings& settings = Application::getSettings();
        settings.beginGroup(Application::getName() + ".MainWindow");
        settings.setValue("centralViewer", centralViewer->getName());
        settings.endGroup();
    }

    // the current central viewer has to be registered so that it can be refreshed
    // by the application and available to set components' visibility
    // (test is needed to avoid error messages due to multiple registering tentative)
    if (Application::getViewer(centralViewer->getName()) == nullptr) {
        Application::registerViewer(centralViewer);
    }
}

// ------------- getCentralViewer -----------------
Viewer* MainWindow::getCentralViewer() const {
    return centralViewer;
}

// ------------- refresh -----------------
void MainWindow::refresh() {
    Viewer* whoIsAsking = qobject_cast<Viewer*> (sender());

    for (Viewer* v : viewers) {
        if (v != whoIsAsking) {
            // When an action is triggered, only the viewer that are interested in viewing
            // action should be refreshed, the other viewers should not be involved.
            if (Application::getTriggeredAction() != nullptr) {
                if (v->getComponentClassNames().isEmpty()) {
                    // action viewer(s)
                    v->refresh();
                }
            }
            else {
                // the refresh was not triggered by the modification of the selected action
                // this is a normal case
                v->refresh();
            }
        }
    }
}

// ------------- showStatusBar -----------------
void MainWindow::showStatusBar(bool b) {
    statusBar()->setVisible(b);
}

//-------------------------- getProgressBar -------------------------------
QProgressBar* MainWindow::getProgressBar() {
    return myProgressBar;
}

// ------------- setApplicationConsole -----------------
void MainWindow::redirectToConsole(bool visible) {
    if (visible) {
        // plug the console to the textedit
        cout.init(&std::cout, consoleWindowTextEdit);
        cerr.init(&std::cerr, consoleWindowTextEdit);
        // also capture Qt logs
        qInstallMessageHandler([](QtMsgType, const QMessageLogContext &, const QString &msg) {
            std::cerr << msg.toStdString() << std::endl;
        });
        CAMITK_INFO(tr("%1 embedded log console ready...").arg(QString(Core::version())))
    }
    else {
        // unplug it
        cout.free();
        cerr.free();
        qInstallMessageHandler(nullptr);
        CAMITK_INFO(tr("%1 embedded log console unplugged...").arg(QString(Core::version())))
    }
}

// ------------- showConsole -----------------
void MainWindow::showConsole(bool show) {
    consoleWindow->setVisible(show);
}


// ------------- getConsoleVisibility -----------------
bool MainWindow::getConsoleVisibility() {
    if (consoleWindow) {
        return consoleWindow->isVisible();
    }
    else {
        return false;
    }
}

// ------------- show -----------------
void MainWindow::show() {
    QMainWindow::show();

    // update the log console toggle state (if actions are available)
    Action* toggleAction = Application::getAction("Toggle Log Console");

    if (toggleAction) {
        toggleAction->getQAction()->setChecked(consoleWindow->isVisible());
    }
    refresh();
}

// ------------- aboutToShow -----------------
void MainWindow::aboutToShow() {
    initSettings();
}

// ------------- initSettings -----------------
void MainWindow::initSettings() {
    QSettings& settings = Application::getSettings();
    settings.beginGroup(Application::getName() + ".MainWindow");
    restoreState(settings.value("windowState").toByteArray());
    QRect geom = settings.value("geometry", QRect(8, 30, 1024, 768)).toRect();

    // ensure ok size for non-compliant window managers
    if (geom.width() <= 0) {
        geom.setWidth(1024);
    }

    if (geom.height() <= 0) {
        geom.setHeight(768);
    }

    setGeometry(geom);

    // get central viewer
    QString centralViewerName = settings.value("centralViewer", "").toString();
    // if no specific name is found, just do nothing will use the one already set in the central viewer by default
    if (centralViewerName != "" && Application::getViewer(centralViewerName) != nullptr) {
        setCentralViewer(Application::getViewer(centralViewerName));
    }

    settings.endGroup();
}

// ------------- saveSettings -----------------
void MainWindow::saveSettings() {
    QSettings& settings = Application::getSettings();
    settings.beginGroup(Application::getName() + ".MainWindow");
    settings.setValue("geometry", geometry());
    settings.setValue("windowState", saveState());
    settings.endGroup();
}

// ------------- closeEvent -----------------
void MainWindow::closeEvent(QCloseEvent* event) {
    statusBar()->showMessage(tr("Exiting application..."));

    // Reset the stylesheet that was set in Application (fixes Qt crash on exit)
    setStyleSheet("");

    if (Application::getAction("Close All")->apply() == Action::SUCCESS) {
        saveSettings();
        event->accept();
    }
    else {
        event->ignore();
    }

}

// ------------- dragEnterEvent -----------------
void MainWindow::dragEnterEvent(QDragEnterEvent* event) {
    // from chapter9 of "C++ GUI Programming with Qt4, 2nd Edition", http://www.informit.com/articles/article.aspx?p=1405546
    // MIME type text/uri-list is used to store a list of uniform resource
    // identifiers (URIs), which can be file names, URLs (such as HTTP or FTP paths), or other global resource identifiers.
    // Standard MIME types are defined by the Internet Assigned Numbers Authority (IANA).
    // They consist of a type and a subtype separated by a slash.
    // The clipboard and the drag and drop system use MIME types to identify different types of data.
    // The official list of MIME types is available at http://www.iana.org/assignments/media-types/.
    if (event->mimeData()->hasUrls()) {
        event->acceptProposedAction();
    }
}


// ------------- dragMoveEvent -----------------
void MainWindow::dragMoveEvent(QDragMoveEvent* event) {
    if (event->mimeData()->hasUrls()) {
        event->acceptProposedAction();
    }
}


// ------------- dragLeaveEvent -----------------
void MainWindow::dragLeaveEvent(QDragLeaveEvent* event) {
    event->accept();
}

// ------------- dropEvent -----------------
void MainWindow::dropEvent(QDropEvent* event) {
    // get all URL of the file
    QList<QUrl> urls = event->mimeData()->urls();

    if (urls.isEmpty()) {
        return;
    }

    // open all given url (or try anyway)
    QList<QUrl>::const_iterator fileIterator = urls.constBegin();

    while (fileIterator != urls.constEnd() && Application::open(fileIterator->toLocalFile()) != nullptr) {
        ++fileIterator;
    }

    event->acceptProposedAction();
}

}

