/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/

#include "EditorUI_QT_Precompiled.h"
#include "PanelWidget.h"
#include <PanelWidget/ui_PanelWidget.h>

#include <PanelWidget/PanelWidget.moc>

#include "paneltitlebar.h"
#include "../AttributeListView.h"
#include "ActionWidgetColoredCheckbox.h"
#include "IEditorParticleUtils.h"


#include <QDockWidget>
#include <QLabel>
#include <QVBoxLayout>
#include <QSpacerItem>
#include <QScrollArea>
#include "AttributeItem.h"
#include "ContextMenu.h"

#define USE_CUSTOM_CHECKBOXES 1 //Set to 1 to have color coded show/hide context menu checkboxes for panels

#define EXTRA_ROOM_TO_CORRECT_FOR_RESIZE_ISSUES 1024


PanelWidget::PanelWidget(QWidget* parent)
    : QMainWindow(parent)
    , ui(new Ui::PanelWidget)
{
    ui->setupUi(this);
    statusBar()->hide();
    setCentralWidget(nullptr);
    setTabShape(QTabWidget::Triangular);
    setTabPosition(Qt::AllDockWidgetAreas, QTabWidget::South);
    //remove context menu
    setContextMenuPolicy(Qt::NoContextMenu);
    m_correctionWidget = new QDockWidget(this);
    m_correctionWidget->setWindowOpacity(0);
    m_correctionWidget->setTitleBarWidget(nullptr);
    m_correctionWidget->setFeatures(QDockWidget::DockWidgetFeature::NoDockWidgetFeatures);
    m_correctionWidget->setStyleSheet("QDockWidget::title{background: transparent; color: transparent;};");
    m_correctionWidget->setObjectName("PanelCorrectionWidget");
    addDockWidget(Qt::DockWidgetArea::BottomDockWidgetArea, m_correctionWidget, Qt::Orientation::Vertical);
}

PanelWidget::~PanelWidget()
{
    if (ui)
    {
        delete ui;
        ui = nullptr;
    }
}

QWidget* PanelWidget::addItemPanel(const char* name, CAttributeItem* attr_item, bool scrollableArea /*=false*/, bool isCustomPanel /*=false*/, QString isShowInGroup /* = "both"*/)
{
    //Make sure the background color of our contents is different from our parent

    CAttributeListView* dWidget = new CAttributeListView(isCustomPanel, isShowInGroup);
    QScrollArea* dWidgetScrollArea;
    if (scrollableArea)
    {
        dWidgetScrollArea = new QScrollArea();
        dWidgetScrollArea->setFrameShape(QFrame::NoFrame);
        dWidgetScrollArea->setWidget(attr_item);
        dWidgetScrollArea->setMaximumHeight(attr_item->layout()->minimumSize().height());

        dWidgetScrollArea->setWidgetResizable(true);
        attr_item->setSizePolicy(QSizePolicy(QSizePolicy::Ignored, QSizePolicy::Maximum));
        dWidget->setWidget(dWidgetScrollArea);
    }
    else
    {
        //This breaks horizontal resizing when only the left panel is expanded,
        // this fixes the problem where the panels take up all the remaining space.
        //attr_item->setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Maximum));
        dWidget->setWidget(attr_item);

        attr_item->setSizePolicy(QSizePolicy(attr_item->sizePolicy().horizontalPolicy(), QSizePolicy::Maximum));
    }

    connect(dWidget, &QDockWidget::topLevelChanged, dWidget, [dWidget](bool isTopLevel)
        {
            dWidget->setWindowOpacity(isTopLevel ? 0.5 : 1.0);
        });

    dWidget->setObjectName(QString("obj_") + name);
    dWidget->setWindowTitle(name);
    ItemPanelTitleBar* bar = new ItemPanelTitleBar(dWidget, attr_item, isCustomPanel);
    bar->setCollapsed(false);
    dWidget->setTitleBarWidget(bar);
    dWidget->setFeatures(isCustomPanel ? (QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable) : QDockWidget::DockWidgetMovable);
    if (isCustomPanel)
    {
        connect(bar, &ItemPanelTitleBar::SignalPanelRenameStart, this, &PanelWidget::PassThroughSignalPanelRename);
        connect(bar, &ItemPanelTitleBar::SignalPanelExportPanel, this, &PanelWidget::PassThroughSignalPanelExport);
        connect(bar, &ItemPanelTitleBar::SignalPanelClosed, this, &PanelWidget::ClosePanel);
        connect(bar, &ItemPanelTitleBar::SignalRemoveAllParams, this, [=](QDockWidget* panel){emit SignalRemoveAllParams(panel); });
        CreateActions(dWidget);
    }
    addDockWidget(m_mainDockWidgetArea, dWidget, Qt::Orientation::Vertical);
    dWidget->show();
    bar->setCollapsed(true);
    m_widgets.push_back(dWidget);
    bar->SetCallbackOnCollapseEvent([=](){ FixSizing(true); });
    return dWidget;
}

QWidget* PanelWidget::addPanel(const char* name, QWidget* contentWidget, bool scrollableArea /*=false*/)
{
    //Make sure the background color of our contents is different from our parent
    QDockWidget* dWidget = new QDockWidget();
    QScrollArea* dWidgetScrollArea;
    if (scrollableArea)
    {
        dWidgetScrollArea = new QScrollArea();
        dWidgetScrollArea->setFrameShape(QFrame::NoFrame);
        dWidgetScrollArea->setWidget(contentWidget);
        dWidgetScrollArea->setMaximumHeight(contentWidget->layout()->minimumSize().height());

        dWidgetScrollArea->setWidgetResizable(true);
        contentWidget->setSizePolicy(QSizePolicy(QSizePolicy::Ignored, QSizePolicy::Maximum));
        dWidget->setWidget(dWidgetScrollArea);
        this->stackUnder(dWidgetScrollArea);
    }
    else
    {
        //This breaks horizontal resizing when only the left panel is expanded,
        // this fixes the problem where the panels take up all the remaining space.
        //contentWidget->setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Maximum));
        dWidget->setWidget(contentWidget);
        this->stackUnder(contentWidget);
    }

    dWidget->setObjectName(QString("obj_") + name);
    dWidget->setWindowTitle(name);
    PanelTitleBar* bar = new PanelTitleBar(dWidget);
    bar->setCollapsed(false);
    dWidget->setTitleBarWidget(bar);
    dWidget->setFeatures(QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetClosable);
    addDockWidget(m_mainDockWidgetArea, dWidget, Qt::Orientation::Vertical);
    dWidget->show();
    bar->setCollapsed(true);

    return dWidget;
}

void PanelWidget::addShowPanelItemsToMenu(QMenu* menu)
{
    //Counters used to determine if extra menu options for show/hide should be visible
    int visiblePanelCount = 0;
    int panelCount = 0;
    int visibleChangedPanels = 0;
    int invisibleChangedPanels = 0;

    for (auto it : children())
    {
        // check if it's the type we're looking for
        QDockWidget* dw = qobject_cast<QDockWidget*>(it);
        if (dw == nullptr)
        {
            continue;
        }
        if (!(dw->features() & QDockWidget::DockWidgetClosable))
        {
            continue;
        }

        if (dw->isVisible())
        {
            visiblePanelCount++;
        }
        panelCount++;

        PanelTitleBar* titlebar = qobject_cast<PanelTitleBar*>(dw->titleBarWidget());
        bool panelContainsChanges = false;

        ItemPanelTitleBar* itembar = qobject_cast<ItemPanelTitleBar*>(dw->titleBarWidget());

        if (itembar != nullptr)
        {
            panelContainsChanges = !itembar->isDefaultValue();
            if (!itembar->isDefaultValue())
            {
                if (itembar->isVisible())
                {
                    visibleChangedPanels++;
                }
                else
                {
                    invisibleChangedPanels++;
                }
            }
        }

        QAction* action;
#if USE_CUSTOM_CHECKBOXES
        ActionWidgetColoredCheckboxAction* widgetAction = new ActionWidgetColoredCheckboxAction(menu);
        action = widgetAction;
#endif
        if (titlebar != nullptr)
        {
#if USE_CUSTOM_CHECKBOXES
            widgetAction->setCaption(titlebar->parentWidget()->windowTitle());
#else
            action = menu->addAction(titlebar->parentWidget()->windowTitle());
#endif
        }
        else
        {
#if USE_CUSTOM_CHECKBOXES
            widgetAction->setCaption("Unnamed widget");
#else
            action = menu->addAction("Unnamed widget");
#endif
        }
#if USE_CUSTOM_CHECKBOXES
        widgetAction->setColored(panelContainsChanges);
        widgetAction->setChecked(titlebar->isVisible());
#else
        if (panelContainsChanges)
        {
            QFont font = action->font();
            font.setBold(true);
            action->setFont(font);
        }
        action->setCheckable(true);
        action->setChecked(titlebar->isVisible());
#endif

        connect(action, &QAction::triggered, dw, [dw, menu]
            {
                //Set panel visibility
                dw->setVisible(!dw->isVisible());

#if USE_CUSTOM_CHECKBOXES
                //Hide menu
                QWidget* w = menu;
                while (w)
                {
                    if (qobject_cast<QMenu*>(w))
                    {
                        w->hide();
                        w = w->parentWidget();
                    }
                    else
                    {
                        break;
                    }
                }
#endif
            }
            );
        menu->addAction(action);
    }
    menu->addSeparator();


    //Add extra menu options to show/hide multiple panels
    if (visiblePanelCount < panelCount)
    {
        connect(menu->addAction("Show All"), &QAction::triggered, this, [this]()
            {
                for (auto it : children())
                {
                    // check if it's the type we're looking for
                    QDockWidget* dw = qobject_cast<QDockWidget*>(it);
                    if (dw == nullptr)
                    {
                        continue;
                    }

                    //Set panel visibility
                    dw->setVisible(true);
                }
            });
    }

    if (visiblePanelCount > 0)
    {
        connect(menu->addAction("Hide All"), &QAction::triggered, this, [this]()
            {
                for (auto it : children())
                {
                    // check if it's the type we're looking for
                    QDockWidget* dw = qobject_cast<QDockWidget*>(it);
                    if (dw == nullptr)
                    {
                        continue;
                    }

                    //Set panel visibility
                    dw->setVisible(false);
                }
            });
    }

    if (invisibleChangedPanels > 0)
    {
        connect(menu->addAction("Show Changed"), &QAction::triggered, this, [this]()
            {
                for (auto it : children())
                {
                    // check if it's the type we're looking for
                    QDockWidget* dw = qobject_cast<QDockWidget*>(it);
                    if (dw == nullptr)
                    {
                        continue;
                    }

                    ItemPanelTitleBar* itembar = qobject_cast<ItemPanelTitleBar*>(dw->titleBarWidget());

                    if (itembar != nullptr)
                    {
                        if (!itembar->isDefaultValue())
                        {
                            //Set panel visibility
                            dw->setVisible(true);
                        }
                    }
                }
            });
    }
}

QString PanelWidget::saveCollapsedState()
{
    QString str;

    iterateOver([&str](QDockWidget* dw, PanelTitleBar* titlebar)
        {
            // store when it's uncollapsed
            if (titlebar->getCollapsed() == false)
            {
                str += dw->windowTitle() + "||";
            }
        });
    return str;
}

void PanelWidget::loadCollapsedState(const QString& str)
{
    QStringList uncollapsed = str.split("||", Qt::SkipEmptyParts);

    // build a set so that we can quickly determine whether to uncollapse something
    QSet<QString> uncollapsedSet;
    for (auto str : uncollapsed)
    {
        uncollapsedSet.insert(str);
    }

    // uncollapse children if needed
    iterateOver([&uncollapsedSet](QDockWidget* dw, PanelTitleBar* titlebar)
        {
            if (uncollapsedSet.contains(dw->windowTitle()))
            {
                titlebar->setCollapsed(false);
            }
            else
            {
                titlebar->setCollapsed(true);
            }
        });
}

QString PanelWidget::saveAdvancedState()
{
    QString str;

    iterateOver([&str](QDockWidget* dw, PanelTitleBar* titlebar)
        {
            // store when advanced options are visible
            ItemPanelTitleBar* itemTitleBar = qobject_cast<ItemPanelTitleBar*>(titlebar);
            if (itemTitleBar)
            {
                if (itemTitleBar->isAdvancedVisible())
                {
                    str += dw->windowTitle() + "||";
                }
            }
        });
    return str;
}

void PanelWidget::loadAdvancedState(const QString& str)
{
#ifdef _ENABLE_ADVANCED_BASIC_BUTTONS_
    QStringList advanced = str.split("||", Qt::SkipEmptyParts);

    // build a set so that we can quickly determine whether a panel has advanced options visible
    QSet<QString> unadvancedSet;
    for (auto str : advanced)
    {
        unadvancedSet.insert(str);
    }

    // enable/disable advanced options on children
    iterateOver([this, &str, &unadvancedSet](QDockWidget* dw, PanelTitleBar* titlebar)
        {
            ItemPanelTitleBar* itemTitleBar = qobject_cast<ItemPanelTitleBar*>(titlebar);
            if (itemTitleBar)
            {
                if (unadvancedSet.contains(dw->windowTitle()))
                {
                    itemTitleBar->showAdvanced(true);
                }
                else
                {
                    itemTitleBar->showAdvanced(false);
                }
            }
        });
#else
    iterateOver([](QDockWidget* dw, PanelTitleBar* titlebar)
        {
            ItemPanelTitleBar* itemTitleBar = qobject_cast<ItemPanelTitleBar*>(titlebar);
            itemTitleBar->showAdvanced(true);
        });
#endif
}

void PanelWidget::finalizePanels()
{
    m_defaultSettings = saveAll();
}

void PanelWidget::iterateOver(std::function<void(QDockWidget*, PanelTitleBar*)> iteratorReceiver)
{
    for (auto it : m_widgets)
    {
        // check if it's the type we're looking for
        QDockWidget* dw = qobject_cast<QDockWidget*>(it);
        if (dw == nullptr)
        {
            continue;
        }
        PanelTitleBar* titlebar = qobject_cast<PanelTitleBar*>(dw->titleBarWidget());
        if (titlebar == nullptr)
        {
            continue;
        }

        iteratorReceiver(dw, titlebar);
    }
}

bool PanelWidget::isEmpty()
{
    bool empty = true;
    iterateOver([&empty](QDockWidget*, PanelTitleBar*){empty = false; }); //Overkill
    return empty;
}

void PanelWidget::ClearAllPanels()
{
    if (!isEmpty())
    {
        iterateOver([=](QDockWidget* widget, PanelTitleBar* titlebar)
            {
                ClosePanel(widget);
            });

        while (m_widgets.count())
        {
            delete m_widgets.takeFirst();
        }

        m_widgets.clear();
    }
}

void PanelWidget::ClearCustomPanels()
{
    if (!isEmpty())
    {
        iterateOver([=](QDockWidget* widget, PanelTitleBar* titlebar)
            {
                CAttributeListView* listView = static_cast<CAttributeListView*>(widget);
                if (listView->isCustomPanel())
                {
                    ClosePanel(widget);
                    for (int i = 0; i < m_widgets.count(); i++)
                    {
                        if (m_widgets[i] == widget)
                        {
                            m_widgets.remove(i);
                            break;
                        }
                    }
                }
            });
    }
}


void PanelWidget::ResetGeometry()
{
    loadAll(m_defaultSettings);
}

static unsigned int gSaveMagicValue = 0x9340245;

QByteArray PanelWidget::saveAll()
{
    QByteArray combined;
    combined.append((char*)&gSaveMagicValue, sizeof(gSaveMagicValue));

    QByteArray geometry = saveState(0);
    int geometryLength = geometry.size();
    combined.append((char*)&geometryLength, sizeof(int));
    combined.append(geometry);

    QString collapsedState = saveCollapsedState();
    QByteArray collapsedStateBytes = collapsedState.toUtf8();
    int collapsedStateLength = collapsedStateBytes.size();
    combined.append((char*)&collapsedStateLength, sizeof(int));
    combined.append(collapsedStateBytes);

    QString advancedState = saveAdvancedState();
    QByteArray advancedStateBytes = advancedState.toUtf8();
    int advancedStateLength = advancedStateBytes.size();
    combined.append((char*)&advancedStateLength, sizeof(int));
    combined.append(advancedStateBytes);

    return combined;
}

void PanelWidget::loadAll(const QByteArray& vdata)
{
    int offset = 0;

    unsigned int& magicValue = *(unsigned int*)(vdata.data() + offset);
    if (magicValue != gSaveMagicValue)  // check if save still valid
    {
        QMessageLogger logger;
        logger.warning("PanelWidget::loadAll vdata not valid");
        return;
    }
    offset += sizeof(gSaveMagicValue);

    int& geometryLength = *(int*)(vdata.data() + offset);
    QByteArray geometry = vdata.mid(offset + sizeof(int), geometryLength);
    restoreState(geometry, 0);
    offset += sizeof(int) + geometryLength;

    int& collapsedStateLength = *(int*)(vdata.data() + offset);
    QByteArray collapsedStateBytes = vdata.mid(offset + sizeof(int), collapsedStateLength);
    QString collapsedState = QString::fromUtf8(collapsedStateBytes);
    loadCollapsedState(collapsedState);
    offset += sizeof(int) + collapsedStateLength;

    int& advancedStateLength = *(int*)(vdata.data() + offset);
    QByteArray advancedStateBytes = vdata.mid(offset + sizeof(int), advancedStateLength);
    QString advancedState = QString::fromUtf8(advancedStateBytes);
    loadAdvancedState(advancedState);
    offset += sizeof(int) + collapsedStateLength;
}

void PanelWidget::paintEvent(QPaintEvent*)
{
    QStyleOption opt;
    opt.init(this);
    QPainter p(this);
    style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this);
}

void PanelWidget::FixSizing(bool includeRoomForExpansion /*= false*/)
{
    //magic number is to provide "growing room" for the panels,
    //this fixes the bug where dragging panels that are side by side to one above the other could
    //fail due to running out of room.
    int roomForExpansion = EXTRA_ROOM_TO_CORRECT_FOR_RESIZE_ISSUES;
    int expectedHeight = GetTotalPanelHeight(roomForExpansion);
    if (!includeRoomForExpansion)
    {
        roomForExpansion = 0;
    }
    setMinimumHeight(expectedHeight + roomForExpansion);
}

int PanelWidget::GetTotalPanelHeight(int& largestHeight)
{
    int total = 0;
    int current = 0;
    for (QDockWidget* widget : m_widgets)
    {
        if (widget != nullptr)
        {
            current = widget->height();
            total += current;
            if (current > largestHeight)
            {
                largestHeight = current;
            }
        }
    }
    return total;
}

void PanelWidget::AddCustomPanel(QDockWidget* dockPanel, bool addToTop /*= true*/)
{
    CRY_ASSERT(dockPanel);

    if (addToTop && m_widgets.count() > 0)
    {
        // Find Top widget by position y
        QDockWidget* topWidget = nullptr;
        for (unsigned int i = 0; i < m_widgets.count(); i++)
        {
            if (m_widgets[i]->pos().y() < 0)
            {
                continue;
            }
            if (!m_widgets[i]->isVisible())
            {
                continue;
            }
            if (!topWidget)
            {
                topWidget = m_widgets[i];
            }
            else
            {
                if (m_widgets[i]->pos().y() <= topWidget->pos().y())
                {
                    topWidget = m_widgets[i];
                }
            }
        }
        if (topWidget)
        {
            topWidget->show();
            if (!tabifiedDockWidgets(topWidget).isEmpty())
            {
                tabifyDockWidget(topWidget, dockPanel);
            }
            else
            {
                // Insert Top Widget
                splitDockWidget(topWidget, dockPanel, Qt::Orientation::Vertical);
                splitDockWidget(dockPanel, topWidget, Qt::Orientation::Vertical);
            }
        }
        else
        {
            addDockWidget(m_mainDockWidgetArea, dockPanel, Qt::Orientation::Vertical);
        }
    }
    else
    {
        addDockWidget(m_mainDockWidgetArea, dockPanel, Qt::Orientation::Vertical);
    }
    dockPanel->setFeatures(QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetClosable);

    ItemPanelTitleBar* bar = static_cast<ItemPanelTitleBar*>(dockPanel->titleBarWidget());
    if (bar)
    {
        connect(bar, &ItemPanelTitleBar::SignalPanelRenameStart, this, &PanelWidget::PassThroughSignalPanelRename);
        connect(bar, &ItemPanelTitleBar::SignalPanelExportPanel, this, &PanelWidget::PassThroughSignalPanelExport);
        connect(bar, &ItemPanelTitleBar::SignalPanelClosed, this, &PanelWidget::ClosePanel);
        connect(bar, &ItemPanelTitleBar::SignalRemoveAllParams, this, [=](QDockWidget* panel){emit SignalRemoveAllParams(panel); });
    }
    CreateActions(dockPanel);

    m_widgets.push_back(dockPanel);
    m_widgets.back()->installEventFilter(this);
    dockPanel->show();
    dockPanel->raise();
}

void PanelWidget::ClosePanel(QDockWidget* panel)
{
    emit SignalRemovePanel(panel);
    RemovePanel(panel);
}

void PanelWidget::RemovePanel(QDockWidget* dockPanel)
{
    if (!tabifiedDockWidgets(dockPanel).isEmpty())
    {
        dockPanel->setFloating(true);
        addDockWidget(Qt::DockWidgetArea::BottomDockWidgetArea, dockPanel, Qt::Orientation::Vertical);
        dockPanel->setFloating(false);
    }
    dockPanel->hide();
    removeDockWidget(dockPanel);
    dockPanel->removeEventFilter(this);
    for (unsigned int i = 0; i < m_widgets.count(); i++)
    {
        if (m_widgets[i] == dockPanel)
        {
            m_widgets.removeAt(i);
            return;
        }
    }
}

bool PanelWidget::eventFilter(QObject* obj, QEvent* e)
{
    if (e->type() == QEvent::Resize)
    {
        QResizeEvent* evnt = static_cast<QResizeEvent*>(e);
        if (evnt)
        {
            for (unsigned int i = 0; i < m_widgets.count(); i++)
            {
                if (obj == static_cast<QObject*>(m_widgets[i]))
                {
                    QSize objSize = evnt->size();
                    if (objSize.height() >  m_correctionWidget->size().height())
                    {
                        m_correctionWidget->resize(QSize(m_correctionWidget->width(), objSize.height()));
                    }
                }
            }
        }
    }
    return QMainWindow::eventFilter(obj, e);
}

void PanelWidget::CreateActions(QDockWidget* dwidget)
{
    CRY_ASSERT(GetIEditor());
    CRY_ASSERT(GetIEditor()->GetParticleUtils());
    IEditorParticleUtils* utils = GetIEditor()->GetParticleUtils();

    QAction* action = new QAction("Export...", this);
    action->setShortcut(utils->HotKey_GetShortcut("Attributes.Export Panel"));
    action->setShortcutContext(Qt::WidgetWithChildrenShortcut);
    connect(action, &QAction::triggered, this, [=]()
        {
            QString fileName = QFileDialog::getSaveFileName(this, tr("Export Panel"), QString(), tr("Panel file (*.custom_attribute)"));
            if (fileName.size() > 0)
            {
                emit SignalExportPanel(dwidget, fileName);
            }
        });
    dwidget->addAction(action);
}

void PanelWidget::PassThroughSignalPanelRename(QDockWidget* panel, QString origName, QString newName)
{
    emit SignalRenamePanel(panel, origName, newName);
}

void PanelWidget::PassThroughSignalPanelExport(QDockWidget* panel, QString name)
{
    emit SignalExportPanel(panel, name);
}