//  ************************************************************************************************
//
//  BornAgain: simulate and fit reflection and scattering
//
//! @file      GUI/View/Dock/DocksController.cpp
//! @brief     Implements class DocksController.
//!
//! @homepage  http://www.bornagainproject.org
//! @license   GNU General Public License v3 or higher (see COPYING)
//! @copyright Forschungszentrum Jülich GmbH 2018
//! @authors   Scientific Computing Group at MLZ (see CITATION, AUTHORS)
//
//  ************************************************************************************************

#include "GUI/View/Dock/DocksController.h"
#include "Base/Util/Assert.h"
#include <QAbstractItemView>
#include <QAction>
#include <QCheckBox>
#include <QDockWidget>
#include <QEvent>
#include <QRadioButton>
#include <QTimer>
#include <QWidgetAction>

namespace {

const char dockWidgetActiveState[] = "DockWidgetActiveState";
const QString StateKey = "State";
const int settingsVersion = 2;

QString stripAccelerator(const QString& text)
{
    QString res = text;
    for (int index = res.indexOf('&'); index != -1; index = res.indexOf('&', index + 1))
        res.remove(index, 1);
    return res;
}

} // namespace

DocksController::DocksController(QMainWindow* mainWindow)
    : QObject(mainWindow)
    , m_main_window(mainWindow)
{
    m_main_window->setDocumentMode(true);
    m_main_window->setTabPosition(Qt::AllDockWidgetAreas, QTabWidget::South);
    m_main_window->setCorner(Qt::BottomLeftCorner, Qt::LeftDockWidgetArea);
    m_main_window->setCorner(Qt::BottomRightCorner, Qt::RightDockWidgetArea);
    m_main_window->installEventFilter(this);
}

QDockWidget* DocksController::addDockForWidget(QWidget* widget)
{
    auto* dockWidget = new QDockWidget(m_main_window);
    dockWidget->setWidget(widget);
    dockWidget->setFeatures(QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetClosable
                            | QDockWidget::DockWidgetFloatable);
    dockWidget->setObjectName(widget->objectName() + "DockWidget");

    QString title = widget->windowTitle();
    dockWidget->toggleViewAction()->setProperty("original_title", title);
    title = stripAccelerator(title);
    dockWidget->setWindowTitle(title);

    connect(dockWidget->toggleViewAction(), &QAction::triggered, [=] {
        if (dockWidget->isVisible())
            dockWidget->raise();
    });

    connect(dockWidget, &QDockWidget::visibilityChanged, [this, dockWidget](bool visible) {
        if (m_handle_dock_visibility_changes)
            dockWidget->setProperty(dockWidgetActiveState, visible);
    });

    dockWidget->setProperty(dockWidgetActiveState, true);

    return dockWidget;
}

void DocksController::addWidget(int id, QWidget* widget, Qt::DockWidgetArea area)
{
    ASSERT(m_docks.find(id) == m_docks.end()); // prevents duplicate entry

    auto* dock = addDockForWidget(widget);
    m_docks[id] = DockWidgetInfo(dock, widget, area);

    QVector<QAbstractItemView*> frames = widget->findChildren<QAbstractItemView*>();
    for (int i = 0; i < frames.count(); ++i)
        frames[i]->setFrameStyle(QFrame::NoFrame);
}

void DocksController::resetLayout()
{
    setTrackingEnabled(false);
    for (auto* dockWidget : dockWidgets()) {
        dockWidget->setFloating(false);
        m_main_window->removeDockWidget(dockWidget);
    }

    for (auto& it : m_docks)
        m_main_window->addDockWidget(it.second.area(), it.second.dock());

    if (!dockWidgets().empty())
        m_main_window->resizeDocks({dockWidgets().first()}, {10}, Qt::Horizontal);

    for (auto* dockWidget : dockWidgets())
        dockWidget->show();

    setTrackingEnabled(true);
}

void DocksController::toggleDock(int id)
{
    if (auto* dock = findDock(id))
        dock->setVisible(!dock->isVisible());
}

void DocksController::setDockVisible(int id, bool visible)
{
    if (auto* dock = findDock(id))
        dock->setVisible(visible);
}

QDockWidget* DocksController::findDock(int id)
{
    if (m_docks.find(id) == m_docks.end())
        return nullptr;

    return m_docks[id].dock();
}

QDockWidget* DocksController::findDock(QWidget* widget)
{
    for (auto& it : m_docks)
        if (it.second.widget() == widget)
            return it.second.dock();

    return nullptr;
}

QVector<QDockWidget*> DocksController::dockWidgets() const
{
    return m_main_window->findChildren<QDockWidget*>();
}

//! Show docks with id's from the list. Other docks will be hidden.

void DocksController::setVisibleDocks(const std::vector<int>& visibleDocks)
{
    for (auto& it : m_docks) {
        if (std::find(visibleDocks.begin(), visibleDocks.end(), it.first) != visibleDocks.end())
            it.second.dock()->show();
        else
            it.second.dock()->hide();
    }
}

//! A hack to request update of QDockWidget size if its child (e.g. InfoWidget) wants it.
//! The problem bypassed here is that there is no direct method to QMainWindow to recalculate
//! position of splitters surrounding given QDockWidget. So our child QWidget requests here
//! the change of Min/Max size of QDockWidget, this will trigger recalculation of QDockWidget
//! layout and will force QDockWidget to respect sizeHints provided by ChildWidget. Later (in one
//! single timer shot) we return min/max sizes of QDockWidget back to re-enable splitters
//! functionality.

void DocksController::setDockHeightForWidget(int height)
{
    QWidget* widget = qobject_cast<QWidget*>(sender());
    ASSERT(widget);
    QDockWidget* dock = findDock(widget);
    ASSERT(dock);

    m_dock_info.m_dock = dock;
    m_dock_info.m_min_size = dock->minimumSize();
    m_dock_info.m_max_size = dock->maximumSize();

    if (height > 0) {
        if (dock->height() < height)
            dock->setMinimumHeight(height);
        else
            dock->setMaximumHeight(height);
    }

    QTimer::singleShot(1, this, &DocksController::dockToMinMaxSizes);
}

void DocksController::dockToMinMaxSizes()
{
    ASSERT(m_dock_info.m_dock);
    m_dock_info.m_dock->setMinimumSize(m_dock_info.m_min_size);
    m_dock_info.m_dock->setMaximumSize(m_dock_info.m_max_size);
    m_dock_info.m_dock = nullptr;
}

void DocksController::setTrackingEnabled(bool enabled)
{
    if (enabled) {
        m_handle_dock_visibility_changes = true;
        for (auto* dockWidget : dockWidgets())
            dockWidget->setProperty(dockWidgetActiveState, dockWidget->isVisible());
    } else {
        m_handle_dock_visibility_changes = false;
    }
}

void DocksController::handleWindowVisibilityChanged(bool visible)
{
    m_handle_dock_visibility_changes = false;
    for (auto* dockWidget : dockWidgets()) {
        if (dockWidget->isFloating())
            dockWidget->setVisible(visible && dockWidget->property(dockWidgetActiveState).toBool());
    }
    if (visible)
        m_handle_dock_visibility_changes = true;
}

bool DocksController::eventFilter(QObject* obj, QEvent* event)
{
    if (event->type() == QEvent::Show)
        handleWindowVisibilityChanged(true);
    else if (event->type() == QEvent::Hide)
        handleWindowVisibilityChanged(false);

    return QObject::eventFilter(obj, event);
}

void DocksController::addDockActionsToMenu(QMenu* menu)
{
    QVector<QAction*> actions;
    for (auto* dockWidget : dockWidgets()) {
        if (dockWidget->parentWidget() == m_main_window) {
            QAction* action = dockWidget->toggleViewAction();
            action->setText(action->property("original_title").toString());
            actions.append(action);
        }
    }
    std::sort(actions.begin(), actions.end(), [](const QAction* action1, const QAction* action2) {
        return stripAccelerator(action1->text()).toLower()
               < stripAccelerator(action2->text()).toLower();
    });

    foreach (QAction* action, actions) {
        if (action->isCheckable()) {
            auto* widgetAction = new QWidgetAction(menu);
            widgetAction->setCheckable(true);
            auto* checkBox = new QCheckBox(action->text(), menu);
            checkBox->setChecked(action->isChecked());
            connect(checkBox, &QCheckBox::toggled, action, &QAction::trigger);
            widgetAction->setDefaultWidget(checkBox);
            menu->addAction(widgetAction);
        } else
            menu->addAction(action);
    }
}

QHash<QString, QVariant> DocksController::saveSettings() const
{
    QHash<QString, QVariant> settings;
    settings.insert(StateKey, m_main_window->saveState(settingsVersion));
    for (auto* dockWidget : dockWidgets())
        settings.insert(dockWidget->objectName(), dockWidget->property(dockWidgetActiveState));
    return settings;
}

void DocksController::restoreSettings(const QHash<QString, QVariant>& settings)
{
    QByteArray ba = settings.value(StateKey, QByteArray()).toByteArray();
    if (!ba.isEmpty())
        m_main_window->restoreState(ba, settingsVersion);
    for (auto* widget : dockWidgets())
        widget->setProperty(dockWidgetActiveState, settings.value(widget->objectName(), false));
}
