// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0

#include "designmode.h"

#include "coreconstants.h"
#include "coreicons.h"
#include "coreplugintr.h"
#include "editormanager/editormanager.h"
#include "editormanager/ieditor.h"
#include "icore.h"
#include "idocument.h"
#include "modemanager.h"

#include <extensionsystem/pluginmanager.h>

#include <utils/algorithm.h>
#include <utils/fancymainwindow.h>

#include <aggregation/aggregate.h>

#include <QDebug>
#include <QPointer>
#include <QStackedWidget>
#include <QStringList>

using namespace Utils;

/*!
    \class Core::DesignMode
    \inmodule QtCreator

    \brief The DesignMode class implements the mode for the Design mode, which is
    for example used by \QMLD and \QD.

    Other plugins can register themselves with registerDesignWidget(),
    giving a list of MIME types that the editor understands, as well as an instance
    to the main editor widget itself.
*/

namespace Core {

struct DesignEditorInfo
{
    Id id;
    int widgetIndex = -1;
    QStringList mimeTypes;
    Context context;
    QWidget *widget = nullptr;
    FancyMainWindow *mainWindow = nullptr;
};

class DesignModePrivate
{
public:
    DesignModePrivate();
    ~DesignModePrivate();

public:
    QPointer<IEditor> m_currentEditor;
    bool m_isActive = false;
    QList<DesignEditorInfo*> m_editors;
    QStackedWidget *m_stackWidget;
    Context m_activeContext;
};

DesignModePrivate::DesignModePrivate()
    : m_stackWidget(new QStackedWidget)
{}

DesignModePrivate::~DesignModePrivate()
{
    delete m_stackWidget;
}

static DesignMode *m_instance = nullptr;
static DesignModePrivate *d = nullptr;

DesignMode::DesignMode()
{
    ICore::addPreCloseListener([] {
        m_instance->currentEditorChanged(nullptr);
        return true;
    });

    setObjectName(QLatin1String("DesignMode"));
    setEnabled(false);
    setContext(Context(Constants::C_DESIGN_MODE));
    setWidget(d->m_stackWidget);
    setDisplayName(Tr::tr("Design"));
    setIcon(Icon::sideBarIcon(Icons::MODE_DESIGN_CLASSIC, Icons::MODE_DESIGN_FLAT));
    setPriority(Constants::P_MODE_DESIGN);
    setId(Constants::MODE_DESIGN);

    connect(EditorManager::instance(), &EditorManager::currentEditorChanged,
            this, &DesignMode::currentEditorChanged);

    connect(ModeManager::instance(), &ModeManager::currentModeChanged,
            this, &DesignMode::updateContext);
}

DesignMode::~DesignMode()
{
    qDeleteAll(d->m_editors);
}

DesignMode *DesignMode::instance()
{
    return m_instance;
}

void DesignMode::setDesignModeIsRequired()
{
    // d != nullptr indicates "isRequired".
    if (!d)
        d = new DesignModePrivate;
}

/**
  * Registers a widget to be displayed when an editor with a file specified in
  * mimeTypes is opened. This also appends the additionalContext in ICore to
  * the context, specified here.
  */
void DesignMode::registerDesignWidget(
    Id id,
    QWidget *widget,
    const QStringList &mimeTypes,
    const Context &context,
    Utils::FancyMainWindow *mainWindow)
{
    setDesignModeIsRequired();
    int index = d->m_stackWidget->addWidget(widget);
    auto info = new DesignEditorInfo;
    info->id = id;
    info->mimeTypes = mimeTypes;
    info->context = context;
    info->widgetIndex = index;
    info->widget = widget;
    info->mainWindow = mainWindow;
    d->m_editors.append(info);
}

void DesignMode::unregisterDesignWidget(QWidget *widget)
{
    d->m_stackWidget->removeWidget(widget);
    for (DesignEditorInfo *info : std::as_const(d->m_editors)) {
        if (info->widget == widget) {
            d->m_editors.removeAll(info);
            delete info;
            break;
        }
    }
}

// if editor changes, check if we have valid mimetype registered.
void DesignMode::currentEditorChanged(IEditor *editor)
{
    if (editor && (d->m_currentEditor.data() == editor))
        return;

    bool mimeEditorAvailable = false;

    if (editor) {
        const QString mimeType = editor->document()->mimeType();
        if (!mimeType.isEmpty()) {
            for (const DesignEditorInfo *editorInfo : std::as_const(d->m_editors)) {
                for (const QString &mime : editorInfo->mimeTypes) {
                    if (mime == mimeType) {
                        d->m_stackWidget->setCurrentIndex(editorInfo->widgetIndex);
                        setMainWindow(editorInfo->mainWindow);
                        setActiveContext(editorInfo->context);
                        mimeEditorAvailable = true;
                        setEnabled(true);
                        break;
                    }
                }
                if (mimeEditorAvailable)
                    break;
            }
        }
    }
    if (d->m_currentEditor)
        disconnect(d->m_currentEditor.data()->document(), &IDocument::changed, this, &DesignMode::updateActions);

    if (!mimeEditorAvailable) {
        setActiveContext(Context());
        if (ModeManager::currentModeId() == id())
            ModeManager::activateMode(Constants::MODE_EDIT);
        setEnabled(false);
        d->m_currentEditor = nullptr;
        emit actionsUpdated(d->m_currentEditor.data());
    } else {
        d->m_currentEditor = editor;

        if (d->m_currentEditor)
            connect(d->m_currentEditor.data()->document(), &IDocument::changed, this, &DesignMode::updateActions);

        emit actionsUpdated(d->m_currentEditor.data());
    }
}

void DesignMode::updateActions()
{
    emit actionsUpdated(d->m_currentEditor.data());
}

void DesignMode::updateContext(Utils::Id newMode, Utils::Id oldMode)
{
    if (newMode == id())
        ICore::addAdditionalContext(d->m_activeContext);
    else if (oldMode == id())
        ICore::removeAdditionalContext(d->m_activeContext);
}

void DesignMode::setActiveContext(const Context &context)
{
    if (d->m_activeContext == context)
        return;

    if (ModeManager::currentModeId() == id())
        ICore::updateAdditionalContexts(d->m_activeContext, context);

    d->m_activeContext = context;
}

void DesignMode::createModeIfRequired()
{
    if (d) {
        m_instance = new DesignMode;
        ExtensionSystem::PluginManager::addObject(m_instance);
    }
}

void DesignMode::destroyModeIfRequired()
{
    if (m_instance) {
        ExtensionSystem::PluginManager::removeObject(m_instance);
        delete m_instance;
    }
    delete d;
}

// for usage statistic
Id DesignMode::currentDesignWidget()
{
    QWidget *currentWidget = d->m_stackWidget->currentWidget();
    DesignEditorInfo *info
        = Utils::findOrDefault(d->m_editors, [currentWidget](DesignEditorInfo *info) {
              return info->widget == currentWidget;
          });
    return info ? info->id : Id();
}

} // namespace Core
