/*
 * Copyright (c) Contributors to the Open 3D Engine Project.
 * For complete copyright and license terms please see the LICENSE at the root of this distribution.
 *
 * SPDX-License-Identifier: Apache-2.0 OR MIT
 *
 */


// Description : ToolBox Macro System


#include "EditorDefs.h"

#include "ToolBox.h"

#include <AzCore/Utils/Utils.h>

// AzToolsFramework
#include <AzToolsFramework/API/EditorPythonRunnerRequestsBus.h>
#include <AzToolsFramework/API/ToolsApplicationAPI.h>

// Editor
#include "Settings.h"
#include "ActionManager.h"


//////////////////////////////////////////////////////////////////////////
// CToolBoxCommand
//////////////////////////////////////////////////////////////////////////
void CToolBoxCommand::Save(XmlNodeRef commandNode) const
{
    commandNode->setAttr("type", (int)m_type);
    commandNode->setAttr("text", m_text.toUtf8().data());
    commandNode->setAttr("bVariableToggle", m_bVariableToggle);
}

//////////////////////////////////////////////////////////////////////////
void CToolBoxCommand::Load(XmlNodeRef commandNode)
{
    int type = 0;
    commandNode->getAttr("type", type);
    m_type = CToolBoxCommand::EType(type);
    m_text = commandNode->getAttr("text");
    commandNode->getAttr("bVariableToggle", m_bVariableToggle);
}

//////////////////////////////////////////////////////////////////////////
void CToolBoxCommand::Execute() const
{
    if (m_type == CToolBoxCommand::eT_SCRIPT_COMMAND)
    {
        using namespace AzToolsFramework;
        EditorPythonRunnerRequestBus::Broadcast(&EditorPythonRunnerRequestBus::Events::ExecuteByString, m_text.toUtf8().data(), false);
    }
    else if (m_type == CToolBoxCommand::eT_CONSOLE_COMMAND)
    {
        if (m_bVariableToggle)
        {
            // Toggle the variable.
            float val = GetIEditor()->GetConsoleVar(m_text.toUtf8().data());
            bool bOn = val != 0;
            GetIEditor()->SetConsoleVar(m_text.toUtf8().data(), (bOn) ? 0.0f : 1.0f);
        }
        else
        {
            // does the command required an active Python interpreter?
            if (m_text.contains("pyRunFile") && !AzToolsFramework::EditorPythonRunnerRequestBus::HasHandlers())
            {
                AZ_Warning("toolbar", false, "The command '%s' requires an embedded Python interpreter. "
                                             "The gem named EditorPythonBindings offers this service. "
                                             "Please enable this gem for the project.", m_text.toUtf8().data());
                return;
            }
            GetIEditor()->GetSystem()->GetIConsole()->ExecuteString(m_text.toUtf8().data());
        }
    }
}

//////////////////////////////////////////////////////////////////////////
// CToolBoxMacro
//////////////////////////////////////////////////////////////////////////
void CToolBoxMacro::Save(XmlNodeRef macroNode) const
{
    for (size_t i = 0; i < m_commands.size(); ++i)
    {
        XmlNodeRef commandNode = macroNode->newChild("command");
        m_commands[i]->Save(commandNode);
    }
}

//////////////////////////////////////////////////////////////////////////
void CToolBoxMacro::Load(XmlNodeRef macroNode)
{
    for (int i = 0; i < macroNode->getChildCount(); ++i)
    {
        XmlNodeRef commandNode = macroNode->getChild(i);
        m_commands.push_back(new CToolBoxCommand);
        m_commands[i]->Load(commandNode);
    }
}

//////////////////////////////////////////////////////////////////////////
void CToolBoxMacro::AddCommand(CToolBoxCommand::EType type, const QString& command, bool bVariableToggle)
{
    CToolBoxCommand* pNewCommand = new CToolBoxCommand;
    pNewCommand->m_type = type;
    pNewCommand->m_text = command;
    pNewCommand->m_bVariableToggle = bVariableToggle;
    m_commands.push_back(pNewCommand);
}

//////////////////////////////////////////////////////////////////////////
void CToolBoxMacro::Clear()
{
    for (size_t i = 0; i < m_commands.size(); ++i)
    {
        delete m_commands[i];
    }

    m_commands.clear();
}

//////////////////////////////////////////////////////////////////////////
const CToolBoxCommand* CToolBoxMacro::GetCommandAt(int index) const
{
    assert(0 <= index && index < m_commands.size());

    return m_commands[index];
}

//////////////////////////////////////////////////////////////////////////
CToolBoxCommand* CToolBoxMacro::GetCommandAt(int index)
{
    assert(0 <= index && index < m_commands.size());

    return m_commands[index];
}

//////////////////////////////////////////////////////////////////////////
void CToolBoxMacro::SwapCommand(int index1, int index2)
{
    assert(0 <= index1 && index1 < m_commands.size());
    assert(0 <= index2 && index2 < m_commands.size());
    std::swap(m_commands[index1], m_commands[index2]);
}

void CToolBoxMacro::RemoveCommand(int index)
{
    assert(0 <= index && index < m_commands.size());

    m_commands.erase(m_commands.begin() + index);
}

//////////////////////////////////////////////////////////////////////////
void CToolBoxMacro::Execute() const
{
    for (size_t i = 0; i < m_commands.size(); ++i)
    {
        m_commands[i]->Execute();
    }
}

//////////////////////////////////////////////////////////////////////////
// CToolBoxManager
//////////////////////////////////////////////////////////////////////////
int CToolBoxManager::GetMacroCount(bool bToolbox) const
{
    if (bToolbox)
    {
        return int(m_macros.size());
    }
    return int(m_shelveMacros.size());
}

//////////////////////////////////////////////////////////////////////////
const CToolBoxMacro* CToolBoxManager::GetMacro(int iIndex, bool bToolbox) const
{
    if (bToolbox)
    {
        assert(0 <= iIndex && iIndex < m_macros.size());
        return m_macros[iIndex];
    }
    else
    {
        assert(0 <= iIndex && iIndex < m_shelveMacros.size());
        return m_shelveMacros[iIndex];
    }
}

//////////////////////////////////////////////////////////////////////////
CToolBoxMacro* CToolBoxManager::GetMacro(int iIndex, bool bToolbox)
{
    if (bToolbox)
    {
        assert(0 <= iIndex && iIndex < m_macros.size());
        return m_macros[iIndex];
    }
    else
    {
        assert(0 <= iIndex && iIndex < m_shelveMacros.size());
        return m_shelveMacros[iIndex];
    }
}

//////////////////////////////////////////////////////////////////////////
int CToolBoxManager::GetMacroIndex(const QString& title, bool bToolbox) const
{
    if (bToolbox)
    {
        for (size_t i = 0; i < m_macros.size(); ++i)
        {
            if (QString::compare(m_macros[i]->GetTitle(), title, Qt::CaseInsensitive) == 0)
            {
                return int(i);
            }
        }
    }
    else
    {
        for (size_t i = 0; i < m_shelveMacros.size(); ++i)
        {
            if (QString::compare(m_shelveMacros[i]->GetTitle(), title, Qt::CaseInsensitive) == 0)
            {
                return int(i);
            }
        }
    }

    return -1;
}

//////////////////////////////////////////////////////////////////////////
CToolBoxMacro* CToolBoxManager::NewMacro(const QString& title, bool bToolbox, int* newIdx)
{
    if (bToolbox)
    {
        const int macroCount = static_cast<int>(m_macros.size());
        if (macroCount > ID_TOOL_LAST - ID_TOOL_FIRST + 1)
        {
            return nullptr;
        }

        for (size_t i = 0; i < macroCount; ++i)
        {
            if (QString::compare(m_macros[i]->GetTitle(), title, Qt::CaseInsensitive) == 0)
            {
                return nullptr;
            }
        }

        CToolBoxMacro* pNewTool = new CToolBoxMacro(title);
        if (newIdx)
        {
            *newIdx = macroCount;
        }
        m_macros.push_back(pNewTool);
        return pNewTool;
    }
    else
    {
        const int shelveMacroCount = static_cast<int>(m_shelveMacros.size());
        if (shelveMacroCount > ID_TOOL_SHELVE_LAST - ID_TOOL_SHELVE_FIRST + 1)
        {
            return nullptr;
        }

        CToolBoxMacro* pNewTool = new CToolBoxMacro(title);
        if (newIdx)
        {
            *newIdx = shelveMacroCount;
        }
        m_shelveMacros.push_back(pNewTool);
        return pNewTool;
    }
}

//////////////////////////////////////////////////////////////////////////
bool CToolBoxManager::SetMacroTitle(int index, const QString& title, bool bToolbox)
{
    if (bToolbox)
    {
        assert(0 <= index && index < m_macros.size());
        for (size_t i = 0; i < m_macros.size(); ++i)
        {
            if (i == index)
            {
                continue;
            }

            if (QString::compare(m_macros[i]->GetTitle(), title, Qt::CaseInsensitive) == 0)
            {
                return false;
            }
        }

        m_macros[index]->SetTitle(title);
    }
    else
    {
        assert(0 <= index && index < m_shelveMacros.size());
        for (size_t i = 0; i < m_shelveMacros.size(); ++i)
        {
            if (i == index)
            {
                continue;
            }

            if (QString::compare(m_shelveMacros[i]->GetTitle(), title, Qt::CaseInsensitive) == 0)
            {
                return false;
            }
        }

        m_shelveMacros[index]->SetTitle(title);
    }
    return true;
}

//////////////////////////////////////////////////////////////////////////
void CToolBoxManager::Load([[maybe_unused]] ActionManager* actionManager)
{
    Clear();

    QString path;
    GetSaveFilePath(path);
    Load(path, nullptr, true, nullptr);
}

void CToolBoxManager::Load(QString xmlpath, AmazonToolbar* pToolbar, bool bToolbox, ActionManager* actionManager)
{
    XmlNodeRef toolBoxNode = XmlHelpers::LoadXmlFromFile(xmlpath.toUtf8().data());
    if (toolBoxNode == nullptr)
    {
        return;
    }

    if (!pToolbar)
    {
        GetIEditor()->GetSettingsManager()->AddSettingsNode(toolBoxNode);
    }
    else
    {
        const char* PRETTY_NAME_ATTR = "prettyName";
        const char* SHOW_BY_DEFAULT_ATTR = "showByDefault";
        const char* SHELF_NAME_ATTR = "shelfName";

        QString shelfName = pToolbar->GetName();

        if (toolBoxNode->haveAttr(SHELF_NAME_ATTR))
        {
            shelfName = toolBoxNode->getAttr(SHELF_NAME_ATTR);
        }

        QString prettyName = shelfName;

        if (toolBoxNode->haveAttr(PRETTY_NAME_ATTR))
        {
            prettyName = toolBoxNode->getAttr(PRETTY_NAME_ATTR);
        }

        pToolbar->SetName(shelfName, prettyName);

        if (toolBoxNode->haveAttr(SHOW_BY_DEFAULT_ATTR))
        {
            const char* showByDefaultAttrString = toolBoxNode->getAttr(SHOW_BY_DEFAULT_ATTR);

            QString showByDefaultString = QString(showByDefaultAttrString);
            showByDefaultString = showByDefaultString.trimmed();

            bool hideByDefault = (QString::compare(showByDefaultString, "false", Qt::CaseInsensitive) == 0) || (QString::compare(showByDefaultString, "0") == 0);
            pToolbar->SetShowByDefault(!hideByDefault);
        }
    }

    AZ::IO::FixedMaxPathString engineRoot = AZ::Utils::GetEnginePath();
    QDir engineDir = !engineRoot.empty() ? QDir(QString(engineRoot.c_str())) : QDir::current();

    AZStd::string enginePath = PathUtil::AddSlash(engineDir.absolutePath().toUtf8().data());

    for (int i = 0; i < toolBoxNode->getChildCount(); ++i)
    {
        XmlNodeRef macroNode = toolBoxNode->getChild(i);
        QString title = macroNode->getAttr("title");
        QString shortcutName = macroNode->getAttr("shortcut");
        QString iconPath = macroNode->getAttr("icon");

        int idx = -1;
        CToolBoxMacro* pMacro = NewMacro(title, bToolbox, &idx);
        if (!pMacro || idx == -1)
        {
            continue;
        }

        pMacro->Load(macroNode);
        pMacro->SetShortcutName(shortcutName);
        pMacro->SetIconPath(iconPath.toUtf8().data());
        pMacro->SetToolbarId(-1);

        if (!pToolbar)
        {
            continue;
        }

        AZStd::string shelfPath = PathUtil::GetParentDirectory(xmlpath.toUtf8().data());
        AZStd::string fullIconPath = enginePath + PathUtil::AddSlash(shelfPath.c_str());
        fullIconPath.append(iconPath.toUtf8().data());

        pMacro->SetIconPath(fullIconPath.c_str());

        QString toolTip(macroNode->getAttr("tooltip"));
        pMacro->action()->setToolTip(toolTip);

        int actionId;

        if (pMacro->GetCommandCount() == 0 || pMacro->GetCommandAt(0)->m_type == CToolBoxCommand::eT_INVALID_COMMAND)
        {
            actionId = ID_TOOLBAR_SEPARATOR;
        }
        else
        {
            actionId = bToolbox ? ID_TOOL_FIRST + idx : ID_TOOL_SHELVE_FIRST + idx;

            // ActionManager uses a QSignalMapper internally. We need to disconnect any existing
            // connections here otherwise the macro will be called more than once.
            QObject::disconnect(pMacro->action(), &QAction::triggered, nullptr, nullptr);

            actionManager->AddAction(actionId, pMacro->action());
        }

        pToolbar->AddAction(actionId);
    }
}

//////////////////////////////////////////////////////////////////////////
void CToolBoxManager::Save() const
{
    XmlNodeRef toolBoxNode = XmlHelpers::CreateXmlNode(TOOLBOXMACROS_NODE);
    for (size_t i = 0; i < m_macros.size(); ++i)
    {
        if (m_macros[i]->GetToolbarId() != -1)
        {
            continue;
        }

        XmlNodeRef macroNode = toolBoxNode->newChild("macro");
        macroNode->setAttr("title", m_macros[i]->GetTitle().toUtf8().data());
        macroNode->setAttr("shortcut", m_macros[i]->GetShortcutName().toString().toUtf8().data());
        macroNode->setAttr("icon", m_macros[i]->GetIconPath().toUtf8().data());
        m_macros[i]->Save(macroNode);
    }
    QString path;
    GetSaveFilePath(path);
    XmlHelpers::SaveXmlNode(GetIEditor()->GetFileUtil(), toolBoxNode, path.toUtf8().data());
}

//////////////////////////////////////////////////////////////////////////
void CToolBoxManager::Clear()
{
    for (size_t i = 0; i < m_macros.size(); ++i)
    {
        delete m_macros[i];
    }
    m_macros.clear();
}

//////////////////////////////////////////////////////////////////////////
void CToolBoxManager::ExecuteMacro(int iIndex, bool bToolbox) const
{
    if (iIndex >= 0 && iIndex < GetMacroCount(bToolbox) && GetMacro(iIndex, bToolbox))
    {
        GetMacro(iIndex, bToolbox)->Execute();
    }
}

//////////////////////////////////////////////////////////////////////////
void CToolBoxManager::ExecuteMacro(const QString& name, bool bToolbox) const
{
    if (bToolbox)
    {
        // Find tool with this name.
        for (size_t i = 0; i < m_macros.size(); ++i)
        {
            if (QString::compare(m_macros[i]->GetTitle(), name, Qt::CaseInsensitive) == 0)
            {
                ExecuteMacro(int(i), bToolbox);
                break;
            }
        }
    }
    else
    {
        // Find tool with this name.
        for (size_t i = 0; i < m_shelveMacros.size(); ++i)
        {
            if (QString::compare(m_shelveMacros[i]->GetTitle(), name, Qt::CaseInsensitive) == 0)
            {
                ExecuteMacro(int(i), bToolbox);
                break;
            }
        }
    }
}

//////////////////////////////////////////////////////////////////////////
void CToolBoxManager::SwapMacro(int index1, int index2, bool bToolbox)
{
    assert(0 <= index1 && index1 < GetMacroCount(bToolbox));
    assert(0 <= index2 && index2 < GetMacroCount(bToolbox));
    if (bToolbox)
    {
        std::swap(m_macros[index1], m_macros[index2]);
    }
    else
    {
        std::swap(m_shelveMacros[index1], m_shelveMacros[index2]);
    }
}

//////////////////////////////////////////////////////////////////////////
void CToolBoxManager::RemoveMacro(int index, bool bToolbox)
{
    assert(0 <= index && index < GetMacroCount(bToolbox));

    if (bToolbox)
    {
        delete m_macros[index];
        m_macros[index] = nullptr;
        m_macros.erase(m_macros.begin() + index);
    }
    else
    {
        delete m_shelveMacros[index];
        m_shelveMacros[index] = nullptr;
        m_shelveMacros.erase(m_shelveMacros.begin() + index);
    }
}

//////////////////////////////////////////////////////////////////////////
void CToolBoxManager::GetSaveFilePath(QString& outPath) const
{
    outPath = Path::GetResolvedUserSandboxFolder();
    outPath += "Macros.xml";
}

//////////////////////////////////////////////////////////////////////////

const std::vector<AmazonToolbar>& CToolBoxManager::GetToolbars() const
{
    return m_toolbars;
}
