/*
  This file is part of KDDockWidgets.

  SPDX-FileCopyrightText: 2019-2022 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
  Author: Sérgio Martins <sergio.martins@kdab.com>

  SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only

  Contact KDAB at <info@kdab.com> for commercial licensing options.
*/

#include "Stack_qtquick.h"
#include "Config.h"
#include "kddockwidgets/ViewFactory.h"

#include "kddockwidgets/controllers/Stack.h"
#include "kddockwidgets/controllers/Group.h"
#include "kddockwidgets/controllers/TabBar.h"

#include <QDebug>
#include <QScopedValueRollback>

using namespace KDDockWidgets;
using namespace KDDockWidgets::Views;

Stack_qtquick::Stack_qtquick(Controllers::Stack *controller, QQuickItem *parent)
    : View_qtquick(controller, Type::Stack, parent)
    , StackViewInterface(controller)
    , m_dockWidgetModel(new DockWidgetModel(this))
{
    connect(m_dockWidgetModel, &DockWidgetModel::countChanged, this,
            [this] {
                if (m_currentDockWidget && indexOfDockWidget(m_currentDockWidget) == -1) {
                    // The current dock widget was removed, set the first one as current
                    if (numDockWidgets() > 0)
                        setCurrentDockWidget(0);
                }

                Q_EMIT m_stack->countChanged(); });
}

void Stack_qtquick::init()
{
    m_tabBarAutoHideChanged = m_stack->tabBarAutoHideChanged.connect([this] {
        Q_EMIT tabBarAutoHideChanged();
    });

    // Emit even if it hasn't changed. When removing indexes lower than the current tab the current tab index
    // will shift. Too much refactoring to make this signal be emitted less than it's needed, but no big deal either,
    // as it's mostly used to update tab title's and such.
    connect(m_dockWidgetModel, &DockWidgetModel::dockWidgetRemoved, m_stack, [this] {
        Q_EMIT m_stack->currentTabChanged(m_stack->currentIndex());
    });

    Q_EMIT tabBarChanged();
}

Controllers::Stack *Stack_qtquick::stack() const
{
    return m_stack;
}

void Stack_qtquick::setDocumentMode(bool)
{
    qDebug() << "Not implemented";
}

int Stack_qtquick::numDockWidgets() const
{
    return m_dockWidgetModel->count();
}

void Stack_qtquick::removeDockWidget(Controllers::DockWidget *dw)
{
    m_dockWidgetModel->remove(dw);
}

int Stack_qtquick::indexOfDockWidget(const Controllers::DockWidget *dw) const
{
    return m_dockWidgetModel->indexOf(dw);
}

bool Stack_qtquick::isPositionDraggable(QPoint p) const
{
    Q_UNUSED(p);
    return true;
}

void Stack_qtquick::setCurrentDockWidget(int index)
{
    Controllers::DockWidget *dw = dockwidgetAt(index);

    if (m_currentDockWidget != dw) {
        m_currentDockWidget = dw;
        Q_EMIT m_stack->currentDockWidgetChanged(dw);
        Q_EMIT m_stack->currentTabChanged(index);
    }
}

QObject *Stack_qtquick::tabBarViewObj() const
{
    return View_qt::asQObject(m_stack->tabBar()->view());
}

Controllers::DockWidget *Stack_qtquick::currentDockWidget() const
{
    return m_currentDockWidget;
}

bool Stack_qtquick::insertDockWidget(int index, Controllers::DockWidget *dw, const QIcon &, const QString &title)
{
    Q_UNUSED(title); // todo
    return m_dockWidgetModel->insert(dw, index);
}

void Stack_qtquick::renameTab(int index, const QString &)
{
    Q_UNUSED(index);
    qWarning() << Q_FUNC_INFO << "Not implemented";
}

void Stack_qtquick::changeTabIcon(int index, const QIcon &)
{
    Q_UNUSED(index);
    qWarning() << Q_FUNC_INFO << "Not implemented";
}

Controllers::DockWidget *Stack_qtquick::dockwidgetAt(int index) const
{
    return m_dockWidgetModel->dockWidgetAt(index);
}

int Stack_qtquick::currentIndex() const
{
    if (!m_currentDockWidget)
        return -1;

    const int index = indexOfDockWidget(m_currentDockWidget);

    if (index == -1)
        qWarning() << Q_FUNC_INFO << "Unexpected null index for" << m_currentDockWidget << this
                   << "; count=" << m_dockWidgetModel->count();

    return index;
}

bool Stack_qtquick::tabBarAutoHide() const
{
    return m_stack->tabBarAutoHide();
}

DockWidgetModel *Stack_qtquick::dockWidgetModel() const
{
    return m_dockWidgetModel;
}

DockWidgetModel::DockWidgetModel(QObject *parent)
    : QAbstractListModel(parent)
{
}

int DockWidgetModel::count() const
{
    return m_dockWidgets.size();
}

int DockWidgetModel::rowCount(const QModelIndex &parent) const
{
    return parent.isValid() ? 0 : m_dockWidgets.size();
}

QVariant DockWidgetModel::data(const QModelIndex &index, int role) const
{
    const int row = index.row();
    if (row < 0 || row >= m_dockWidgets.size())
        return {};

    Controllers::DockWidget *dw = m_dockWidgets.at(row);

    switch (role) {
    case Role_Title:
        return dw->title();
    }

    return {};
}

Controllers::DockWidget *DockWidgetModel::dockWidgetAt(int index) const
{
    if (index < 0 || index >= m_dockWidgets.size()) {
        // Can happen. Benign.
        return nullptr;
    }

    return m_dockWidgets[index];
}

bool DockWidgetModel::contains(Controllers::DockWidget *dw) const
{
    return m_dockWidgets.contains(dw);
}

QHash<int, QByteArray> DockWidgetModel::roleNames() const
{
    return { { Role_Title, "title" } };
}

void DockWidgetModel::emitDataChangedFor(Controllers::DockWidget *dw)
{
    const int row = indexOf(dw);
    if (row == -1) {
        qWarning() << Q_FUNC_INFO << "Couldn't find" << dw;
    } else {
        QModelIndex index = this->index(row, 0);
        Q_EMIT dataChanged(index, index);
    }
}

void DockWidgetModel::remove(Controllers::DockWidget *dw)
{
    QScopedValueRollback<bool> guard(m_removeGuard, true);
    const int row = indexOf(dw);
    if (row == -1) {
        if (!m_removeGuard) {
            // can happen if there's reentrancy. Some user code reacting
            // to the signals and call remove for whatever reason.
            qWarning() << Q_FUNC_INFO << "Nothing to remove"
                       << static_cast<void *>(dw); // Print address only, as it might be deleted already
        }
    } else {
        const auto connections = m_connections.take(dw);
        for (const QMetaObject::Connection &conn : connections)
            disconnect(conn);

        beginRemoveRows(QModelIndex(), row, row);
        m_dockWidgets.removeOne(dw);
        endRemoveRows();

        Q_EMIT countChanged();
        Q_EMIT dockWidgetRemoved();
    }
}

int DockWidgetModel::indexOf(const Controllers::DockWidget *dw)
{
    return m_dockWidgets.indexOf(const_cast<Controllers::DockWidget *>(dw));
}

bool DockWidgetModel::insert(Controllers::DockWidget *dw, int index)
{
    if (m_dockWidgets.contains(dw)) {
        qWarning() << Q_FUNC_INFO << "Shouldn't happen";
        return false;
    }

    QMetaObject::Connection conn = connect(dw, &Controllers::DockWidget::titleChanged, this, [dw, this] {
        emitDataChangedFor(dw);
    });

    QMetaObject::Connection conn2 = connect(dw, &QObject::destroyed, this, [dw, this] {
        remove(dw);
    });

    m_connections[dw] = { conn, conn2 };

    beginInsertRows(QModelIndex(), index, index);
    m_dockWidgets.insert(index, dw);
    endInsertRows();

    Q_EMIT countChanged();
    return true;
}
