/*
 * This file is part of the Code::Blocks IDE and licensed under the GNU General Public License, version 3
 * http://www.gnu.org/licenses/gpl-3.0.html
 *
 * $Revision: 12537 $
 * $Id: envvars.cpp 12537 2021-10-24 22:27:25Z bluehazzard $
 * $HeadURL: https://svn.code.sf.net/p/codeblocks/code/trunk/src/plugins/contrib/envvars/envvars.cpp $
 */

// ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----

#include "sdk.h"
#ifndef CB_PRECOMP
    #include <wx/arrstr.h>
    #include <wx/menu.h>
    #include <wx/toolbar.h>

    #include <tinyxml.h>

    #include "cbproject.h"
    #include "globals.h"
    #include "manager.h"
    #include "configmanager.h"
    #include "logmanager.h"
    #include "scriptingmanager.h"
#endif

#include "projectloader_hooks.h"

#include "scripting/bindings/sc_utils.h"
#include "scripting/bindings/sc_typeinfo_all.h"

#include "envvars_common.h"
#include "envvars_cfgdlg.h"
#include "envvars_prjoptdlg.h"
#include "envvars.h"

// Uncomment this for tracing of method calls in C::B's DebugLog:
//#define TRACE_ENVVARS

// ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----

// Register the plugin
namespace
{
    PluginRegistrant<EnvVars> reg("EnvVars");
};

BEGIN_EVENT_TABLE(EnvVars, cbPlugin)
END_EVENT_TABLE()

// ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----

wxString EnvVars::ParseProjectEnvvarSet(const cbProject* project)
{
    if (!project)
        return wxString();

    const TiXmlNode *extNode = project->GetExtensionsNode();
    if (!extNode)
        return wxString();

    const TiXmlElement* elem = extNode->ToElement();
    if (!elem)
        return wxString();

    const TiXmlElement* node = elem->FirstChildElement("envvars");
    if (!node)
        return wxString();

    wxString result = cbC2U(node->Attribute("set"));
    if (result.empty()) // no envvar set to apply setup
        return wxString();

    if (!nsEnvVars::EnvvarSetExists(result))
        EnvvarSetWarning(result);

    return result;
}

void EnvVars::SaveProjectEnvvarSet(cbProject* project, const wxString& envvar_set)
{
    TiXmlNode *extNode = project->GetExtensionsNode();
    if (!extNode)
        return;

    TiXmlElement* elem = extNode->ToElement();
    if (!elem)
        return;

    TiXmlElement* node = elem->FirstChildElement("envvars");

    // If the set is empty we want to remove the node, else we set it.
    if (envvar_set.empty())
    {
        if (node)
            elem->RemoveChild(node);
    }
    else
    {
        if (!node)
            node = elem->InsertEndChild(TiXmlElement("envvars"))->ToElement();

        node->SetAttribute("set", cbU2C(envvar_set));
    }
}

void EnvVars::DoProjectActivate(cbProject* project)
{
    const wxString prj_envvar_set(ParseProjectEnvvarSet(project));
    if (prj_envvar_set.empty())  // There is no envvar set to apply...
    {
        // Apply default envvar set (but only, if not already active)
        EV_DBGLOG("Setting up '%s' envvars set.", nsEnvVars::EnvVarsDefault);
        nsEnvVars::EnvvarSetApply(wxEmptyString, false);
    }
    else                         // ...there is an envvar set setup to apply.
    {
        if (nsEnvVars::EnvvarSetExists(prj_envvar_set))
        {
            EV_DBGLOG("Discarding envvars set '%s'.", nsEnvVars::GetActiveSetName());

            nsEnvVars::EnvvarSetDiscard(wxEmptyString); // Remove currently active envvars
            EV_DBGLOG("Setting up envvars set '%s' for activated project.", prj_envvar_set);

            // Apply envvar set always (as the old one has been discarded above)
            nsEnvVars::EnvvarSetApply(prj_envvar_set, true);
        }
        else
        {
            EnvvarSetWarning(prj_envvar_set);
        }
    }
}

// ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----

void EnvVars::OnProjectActivated(CodeBlocksEvent& event)
{
#if defined(TRACE_ENVVARS)
    Manager::Get()->GetLogManager()->DebugLog("OnProjectActivated");
#endif

    if (IsAttached())
       DoProjectActivate(event.GetProject());

    event.Skip(); // propagate the event to other listeners
}// OnProjectActivated

// ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----

void EnvVars::OnProjectClosed(CodeBlocksEvent& event)
{
#if defined(TRACE_ENVVARS)
    Manager::Get()->GetLogManager()->DebugLog("OnProjectClosed");
#endif

    wxString prj_envvar_set;
    if (IsAttached())
    {
        prj_envvar_set = ParseProjectEnvvarSet(event.GetProject());

        // If there is an envvar set connected to this project...
        if (!prj_envvar_set.empty()) // ...make sure it's being discarded
            nsEnvVars::EnvvarSetDiscard(prj_envvar_set);
    }

    // Apply default envvar set (but only, if not already active)
    nsEnvVars::EnvvarSetApply(wxString(), !prj_envvar_set.empty());

    event.Skip(); // Propagate the event to other listeners
}// OnProjectClosed

// ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----

namespace ScriptBindings
{
SQInteger GetEnvvarSetNames(HSQUIRRELVM v)
{
    // env table
    ExtractParams1<SkipParam> extractor(v);
    if (!extractor.Process("EnvvarGetEnvvarSetNames"))
        return extractor.ErrorMessage();

    return ConstructAndReturnInstance(v, nsEnvVars::GetEnvvarSetNames());
}

SQInteger GetActiveSetName(HSQUIRRELVM v)
{
    // env table
    ExtractParams1<SkipParam> extractor(v);
    if (!extractor.Process("EnvvarGetActiveSetName"))
        return extractor.ErrorMessage();

    return ConstructAndReturnInstance(v, nsEnvVars::GetActiveSetName());
}

SQInteger GetEnvvarsBySetPath(HSQUIRRELVM v)
{
    // env table, set_path
    ExtractParams2<SkipParam, const wxString *> extractor(v);
    if (!extractor.Process("EnvvarGetEnvvarsBySetPath"))
        return extractor.ErrorMessage();

    return ConstructAndReturnInstance(v, nsEnvVars::GetEnvvarsBySetPath(*extractor.p1));
}

SQInteger EnvvarSetExists(HSQUIRRELVM v)
{
    // env table, set_path
    ExtractParams2<SkipParam, const wxString *> extractor(v);
    if (!extractor.Process("EnvvarSetExists"))
        return extractor.ErrorMessage();

    sq_pushbool(v, nsEnvVars::EnvvarSetExists(*extractor.p1));
    return 1;
}

SQInteger EnvvarSetApply(HSQUIRRELVM v)
{
    // env table, set_name, even_if_active
    ExtractParams3<SkipParam, const wxString *, bool> extractor(v);
    if (!extractor.Process("EnvvarSetApply"))
        return extractor.ErrorMessage();

    nsEnvVars::EnvvarSetApply(*extractor.p1, extractor.p2);
    return 0;
}

SQInteger EnvvarSetDiscard(HSQUIRRELVM v)
{
    // env table, set_name
    ExtractParams2<SkipParam, const wxString *> extractor(v);
    if (!extractor.Process("EnvvarSetDiscard"))
        return extractor.ErrorMessage();
    nsEnvVars::EnvvarSetDiscard(*extractor.p1);
    return 0;
}

SQInteger EnvvarApply(HSQUIRRELVM v)
{
    // env table, key, value
    ExtractParams3<SkipParam, const wxString *, const wxString *> extractor(v);
    if (!extractor.Process("EnvvarApply"))
        return extractor.ErrorMessage();

    sq_pushbool(v, nsEnvVars::EnvvarApply(*extractor.p1, *extractor.p2));
    return 1;
}

SQInteger EnvvarDiscard(HSQUIRRELVM v)
{
    // env table, key
    ExtractParams2<SkipParam, const wxString *> extractor(v);
    if (!extractor.Process("EnvvarDiscard"))
        return extractor.ErrorMessage();

    sq_pushbool(v, nsEnvVars::EnvvarDiscard(*extractor.p1));
    return 1;
}
} // namespace ScriptBindings

void EnvVars::OnAttach()
{
#if defined(TRACE_ENVVARS)
    Manager::Get()->GetLogManager()->DebugLog("OnAttach");
#endif

    if (!Manager::LoadResource("envvars.zip"))
        NotifyMissingFile("envvars.zip");

    // load and apply configuration (to application only)
    ConfigManager *cfg = Manager::Get()->GetConfigManager("envvars");
    if (!cfg)
        return;

    // will apply the currently active envvar set
    nsEnvVars::EnvvarSetApply(wxEmptyString, true);

    // register event sink
    Manager::Get()->RegisterEventSink(cbEVT_PROJECT_ACTIVATE, new cbEventFunctor<EnvVars, CodeBlocksEvent>(this, &EnvVars::OnProjectActivated));
    Manager::Get()->RegisterEventSink(cbEVT_PROJECT_CLOSE,    new cbEventFunctor<EnvVars, CodeBlocksEvent>(this, &EnvVars::OnProjectClosed));

    {
        // Register scripting
        ScriptingManager *scriptMgr = Manager::Get()->GetScriptingManager();
        HSQUIRRELVM vm = scriptMgr->GetVM();
        ScriptBindings::PreserveTop preserveTop(vm);

        sq_pushroottable(vm);

        ScriptBindings::BindMethod(vm, _SC("EnvvarGetEnvvarSetNames"), ScriptBindings::GetEnvvarSetNames, nullptr);
        ScriptBindings::BindMethod(vm, _SC("EnvvarGetActiveSetName"), ScriptBindings::GetActiveSetName, nullptr);
        ScriptBindings::BindMethod(vm, _SC("EnvvarGetEnvvarsBySetPath"), ScriptBindings::GetEnvvarsBySetPath, nullptr);
        ScriptBindings::BindMethod(vm, _SC("EnvvarSetExists"), ScriptBindings::EnvvarSetExists, nullptr);
        ScriptBindings::BindMethod(vm, _SC("EnvvarSetApply"), ScriptBindings::EnvvarSetApply, nullptr);
        ScriptBindings::BindMethod(vm, _SC("EnvvarSetDiscard"), ScriptBindings::EnvvarSetDiscard, nullptr);
        ScriptBindings::BindMethod(vm, _SC("EnvvarApply"), ScriptBindings::EnvvarApply, nullptr);
        ScriptBindings::BindMethod(vm, _SC("EnvvarDiscard"), ScriptBindings::EnvvarDiscard, nullptr);

        sq_poptop(vm); // Pop root table.
    }
}// OnAttach

// ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----

void EnvVars::OnRelease(bool /*appShutDown*/)
{
    // Unregister scripting
    HSQUIRRELVM v = Manager::Get()->GetScriptingManager()->GetVM();
    if (v)
    {
      // TODO (Morten#5#): Is that the correct way of un-registering? (Seems so weird...)
      sq_pushroottable(v);
      sq_pushstring(v, _SC("EnvvarDiscard"), -1);
      sq_deleteslot(v, -2, false);
      sq_poptop(v);

      sq_pushroottable(v);
      sq_pushstring(v, _SC("EnvvarApply"), -1);
      sq_deleteslot(v, -2, false);
      sq_poptop(v);

      sq_pushroottable(v);
      sq_pushstring(v, _SC("EnvvarSetDiscard"), -1);
      sq_deleteslot(v, -2, false);
      sq_poptop(v);

      sq_pushroottable(v);
      sq_pushstring(v, _SC("EnvvarSetApply"), -1);
      sq_deleteslot(v, -2, false);
      sq_poptop(v);

      sq_pushroottable(v);
      sq_pushstring(v, _SC("EnvvarSetExists"), -1);
      sq_deleteslot(v, -2, false);
      sq_poptop(v);

      sq_pushroottable(v);
      sq_pushstring(v, _SC("EnvVarGetEnvvarsBySetPath"), -1);
      sq_deleteslot(v, -2, false);
      sq_poptop(v);

      sq_pushroottable(v);
      sq_pushstring(v, _SC("EnvvarGetActiveSetName"), -1);
      sq_deleteslot(v, -2, false);
      sq_poptop(v);

      sq_pushroottable(v);
      sq_pushstring(v, _SC("EnvvarGetEnvvarSetNames"), -1);
      sq_deleteslot(v, -2, false);
      sq_poptop(v);
    }
}// OnRelease

// ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----

cbConfigurationPanel* EnvVars::GetConfigurationPanel(wxWindow* parent)
{
    EnvVarsConfigDlg* dlg = new EnvVarsConfigDlg(parent, this);  // deleted by the caller
    return dlg;
}// GetConfigurationPanel

// ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----

cbConfigurationPanel* EnvVars::GetProjectConfigurationPanel(wxWindow* parent, cbProject* project)
{
    EnvVarsProjectOptionsDlg* dlg = new EnvVarsProjectOptionsDlg(parent, project);  // deleted by the caller
    return dlg;
}// GetProjectConfigurationPanel

// ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----

void EnvVars::EnvvarSetWarning(const wxString& envvar_set)
{
#if defined(TRACE_ENVVARS)
    Manager::Get()->GetLogManager()->DebugLog("EnvvarSetWarning");
#endif

    wxString warning_msg;
    warning_msg.Printf(_("Warning: The project contained a reference to an envvar set\n"
                         "('%s') that could not be found."), envvar_set);
    cbMessageBox(warning_msg, _("EnvVars Plugin Warning"), wxICON_WARNING);
}// EnvvarSetWarning
