/*
 * This file is part of the Code::Blocks IDE and licensed under the GNU Lesser General Public License, version 3
 * http://www.gnu.org/licenses/lgpl-3.0.html
 *
 * $Revision: 12999 $
 * $Id: cbplugin.cpp 12999 2022-11-01 13:12:28Z wh11204 $
 * $HeadURL: https://svn.code.sf.net/p/codeblocks/code/trunk/src/sdk/cbplugin.cpp $
 */

#include "sdk_precomp.h"

#ifndef CB_PRECOMP
    #include <wx/frame.h> // wxFrame
    #include <wx/menu.h>
    #include <wx/process.h>

    #include "cbeditor.h"
    #include "cbplugin.h"
    #include "cbproject.h"
    #include "compiler.h" // GetSwitches
    #include "configmanager.h"
    #include "debuggermanager.h"
    #include "editorcolourset.h"
    #include "editormanager.h"
    #include "infowindow.h"
    #include "logmanager.h"
    #include "macrosmanager.h"
    #include "manager.h"
    #include "projectbuildtarget.h"
    #include "projectmanager.h"
    #include "sdk_events.h"
#endif

#include <wx/toolbar.h>

#include "annoyingdialog.h"
#include "cbdebugger_interfaces.h"
#include "cbstyledtextctrl.h"
#include "ccmanager.h"
#include "debuggermanager.h"
#include "editor_hooks.h"
#include "loggers.h"

#ifndef __WXMSW__
    #include <errno.h>
    // needed for the kill system call
    #include <signal.h>
    #include <sys/types.h>
#endif

cbPlugin::cbPlugin() :
    m_Type(ptNone),
    m_IsAttached(false)
{
    SetEvtHandlerEnabled(false);
}

cbPlugin::~cbPlugin()
{
}

void cbPlugin::Attach()
{
    if (m_IsAttached)
        return;
    wxWindow* window = Manager::Get()->GetAppWindow();
    if (window)
    {
        // push ourself in the application's event handling chain...
        window->PushEventHandler(this);
    }
    m_IsAttached = true;
    OnAttach();
    SetEvtHandlerEnabled(true);

    CodeBlocksEvent event(cbEVT_PLUGIN_ATTACHED);
    event.SetPlugin(this);
    // post event in the host's event queue
    Manager::Get()->ProcessEvent(event);
}

void cbPlugin::Release(bool appShutDown)
{
    if (!m_IsAttached)
        return;
    m_IsAttached = false;
    SetEvtHandlerEnabled(false);
    OnRelease(appShutDown);

    CodeBlocksEvent event(cbEVT_PLUGIN_RELEASED);
    event.SetPlugin(this);
    // ask the host to process this event immediately
    // it must be done this way, because if the host references
    // us (through event.GetEventObject()), we might not be valid at that time
    // (while, now, we are...)
    Manager::Get()->ProcessEvent(event);

    if (appShutDown)
        return; // nothing more to do, if the app is shutting down

    wxWindow* window = Manager::Get()->GetAppWindow();
    if (window)
    {
        // remove ourself from the application's event handling chain...
        window->RemoveEventHandler(this);
    }
}

void cbPlugin::NotImplemented(const wxString& log) const
{
    Manager::Get()->GetLogManager()->DebugLog(log + _T(" : not implemented"));
}

/////
///// cbCompilerPlugin
/////

cbCompilerPlugin::cbCompilerPlugin()
{
    m_Type = ptCompiler;
}

/////
///// cbDebuggerPlugin
/////

cbDebuggerPlugin::cbDebuggerPlugin(const wxString &guiName, const wxString &settingsName) :
    m_pCompiler(nullptr),
    m_WaitingCompilerToFinish(false),
    m_StartType(StartTypeUnknown),
    m_ActiveConfig(0),
    m_LogPageIndex(-1),
    m_lastLineWasNormal(true),
    m_guiName(guiName),
    m_settingsName(settingsName)
{
    m_Type = ptDebugger;
}


void cbDebuggerPlugin::OnAttach()
{
    Manager::Get()->GetDebuggerManager()->RegisterDebugger(this);

    OnAttachReal();

    typedef cbEventFunctor<cbDebuggerPlugin, CodeBlocksEvent> Event;

    Manager::Get()->RegisterEventSink(cbEVT_EDITOR_OPEN, new Event(this, &cbDebuggerPlugin::OnEditorOpened));
    Manager::Get()->RegisterEventSink(cbEVT_PROJECT_ACTIVATE, new Event(this, &cbDebuggerPlugin::OnProjectActivated));
    Manager::Get()->RegisterEventSink(cbEVT_PROJECT_CLOSE, new Event(this, &cbDebuggerPlugin::OnProjectClosed));

    Manager::Get()->RegisterEventSink(cbEVT_COMPILER_FINISHED, new Event(this, &cbDebuggerPlugin::OnCompilerFinished));

    m_StartType = StartTypeUnknown;

    if (SupportsFeature(cbDebuggerFeature::ValueTooltips))
        RegisterValueTooltip();
}

void cbDebuggerPlugin::OnRelease(bool appShutDown)
{
    Manager::Get()->RemoveAllEventSinksFor(this);

    OnReleaseReal(appShutDown);

    Manager::Get()->GetDebuggerManager()->UnregisterDebugger(this);
}

void cbDebuggerPlugin::BuildMenu(cb_unused wxMenuBar* menuBar)
{
    if (!IsAttached())
        return;
    Manager::Get()->GetDebuggerManager()->GetMenu();
}

wxString cbDebuggerPlugin::GetEditorWordAtCaret(const wxPoint* mousePosition)
{
    cbEditor* ed = Manager::Get()->GetEditorManager()->GetBuiltinActiveEditor();
    if (!ed)
        return wxString();
    cbStyledTextCtrl* stc = ed->GetControl();
    if (!stc)
        return wxString();

    wxString selected_text = stc->GetSelectedText();
    if (!selected_text.empty())
    {
        selected_text.Trim(true);
        selected_text.Trim(false);

        wxString::size_type pos = selected_text.find(wxT('\n'));
        if (pos != wxString::npos)
        {
            selected_text.Remove(pos, selected_text.length() - pos);
            selected_text.Trim(true);
            selected_text.Trim(false);
        }
        // check if the mouse is over the selected text
        if (mousePosition)
        {
            int startPos = stc->GetSelectionStart();
            int endPos = stc->GetSelectionEnd();
            int mousePos = stc->PositionFromPointClose(mousePosition->x, mousePosition->y);
            if (mousePos == wxSCI_INVALID_POSITION)
                return wxString();
            else if (startPos <= mousePos && mousePos <= endPos)
                return selected_text;
            else
                return wxString();
        }
        else
            return selected_text;
    }

    if (mousePosition)
    {
        int pos = stc->PositionFromPoint(*mousePosition);
        int start = stc->WordStartPosition(pos, true);
        int end = stc->WordEndPosition(pos, true);
        selected_text = stc->GetTextRange(start, end);
    }
    else
    {
        int start = stc->WordStartPosition(stc->GetCurrentPos(), true);
        int end = stc->WordEndPosition(stc->GetCurrentPos(), true);
        selected_text = stc->GetTextRange(start, end);
    }
    return selected_text;
}

void cbDebuggerPlugin::BuildModuleMenu(const ModuleType type, wxMenu* menu, cb_unused const FileTreeData* data)
{
    if (!IsAttached())
        return;
    // we 're only interested in editor menus
    // we 'll add a "debug watches" entry only when the debugger is running...
    if (type != mtEditorManager || !menu)
        return;
    cbDebuggerPlugin *active_plugin = Manager::Get()->GetDebuggerManager()->GetActiveDebugger();
    if (active_plugin != this)
        return;

    wxString word;
    if (IsRunning())
    {
        // has to have a word under the caret...
        word = GetEditorWordAtCaret();
    }
    Manager::Get()->GetDebuggerManager()->BuildContextMenu(*menu, word, IsRunning());
}

bool cbDebuggerPlugin::BuildToolBar(cb_unused wxToolBar* toolBar)
{
    return false;
}

bool cbDebuggerPlugin::ToolMenuEnabled() const
{
    cbProject* prj = Manager::Get()->GetProjectManager()->GetActiveProject();

    bool en = (prj && !prj->GetCurrentlyCompilingTarget()) || IsAttachedToProcess();
    return IsRunning() && en;
}

cbDebuggerConfiguration& cbDebuggerPlugin::GetActiveConfig()
{
    DebuggerManager::RegisteredPlugins &allPlugins = Manager::Get()->GetDebuggerManager()->GetAllDebuggers();

    DebuggerManager::RegisteredPlugins::iterator it = allPlugins.find(this);
    if (it == allPlugins.end())
        cbAssert(false);
    cbDebuggerConfiguration *config = it->second.GetConfiguration(m_ActiveConfig);
    if (!config)
        return *it->second.GetConfigurations().front();
    else
        return *config;
}

void cbDebuggerPlugin::SetActiveConfig(int index)
{
    m_ActiveConfig = index;
}

int cbDebuggerPlugin::GetIndexOfActiveConfig() const
{
    return m_ActiveConfig;
}

void cbDebuggerPlugin::ClearActiveMarkFromAllEditors()
{
    EditorManager* edMan = Manager::Get()->GetEditorManager();
    for (int i = 0; i < edMan->GetEditorsCount(); ++i)
    {
        cbEditor* ed = edMan->GetBuiltinEditor(i);
        if (ed)
            ed->SetDebugLine(-1);
    }
}

cbDebuggerPlugin::SyncEditorResult cbDebuggerPlugin::SyncEditor(const wxString& filename, int line, bool setMarker)
{
    if (setMarker)
    {
        EditorManager* edMan = Manager::Get()->GetEditorManager();
        for (int i = 0; i < edMan->GetEditorsCount(); ++i)
        {
            cbEditor* ed = edMan->GetBuiltinEditor(i);
            if (ed)
                ed->SetDebugLine(-1);
        }
    }
    FileType ft = FileTypeOf(filename);
    if (ft != ftSource && ft != ftHeader && ft != ftResource && ft != ftTemplateSource)
    {
        // if the line is >= 0 and ft == ftOther assume, that we are in header without extension
        if (line < 0 || ft != ftOther)
        {
            ShowLog(false);
            Log(_("Unknown file: ") + filename, Logger::error);
            InfoWindow::Display(_("Unknown file"), _("File: ") + filename, 5000);

            return SyncFileUnknown; // don't try to open unknown files
        }
    }

    cbProject* project = Manager::Get()->GetProjectManager()->GetActiveProject();
    ProjectFile* f = project ? project->GetFileByFilename(filename, false, true) : nullptr;

    wxString unixfilename = UnixFilename(filename);
    wxFileName fname(unixfilename);

    if (project && fname.IsRelative())
        fname.MakeAbsolute(project->GetBasePath());

    // gdb can't work with spaces in filenames, so we have passed it the shorthand form (C:\MYDOCU~1 etc)
    // revert this change now so the file can be located and opened...
    // we do this by calling GetLongPath()
    cbEditor* ed = Manager::Get()->GetEditorManager()->Open(fname.GetLongPath());
    if (ed)
    {
        ed->Show(true);
        if (f && !ed->GetProjectFile())
            ed->SetProjectFile(f);
        ed->GotoLine(line - 1, false);
        if (setMarker)
            ed->SetDebugLine(line - 1);
        return SyncOk;
    }
    else
    {
        ShowLog(false);
        Log(_("Cannot open file: ") + filename, Logger::error);
        InfoWindow::Display(_("Cannot open file"), _("File: ") + filename, 5000);

        return SyncFileNotFound;
    }
}

inline bool HasBreakpoint(cbDebuggerPlugin &plugin, wxString const &filename, int line)
{
    int count = plugin.GetBreakpointsCount();
    for (int ii = 0; ii < count; ++ii)
    {
        const cb::shared_ptr<cbBreakpoint> &b = plugin.GetBreakpoint(ii);

        if (b->GetLocation() == filename && b->GetLine() == line)
            return true;
    }
    return false;
}

void cbDebuggerPlugin::EditorLinesAddedOrRemoved(cbEditor* editor, int startline, int lines)
{
    // here we keep the breakpoints in sync with the editors
    // (whenever lines are added or removed)
    if (!editor || lines == 0)
        return;

    const wxString& filename = editor->GetFilename();

    std::vector<int> breakpoints_for_file;
    int count = GetBreakpointsCount();
    for (int ii = 0; ii < count; ++ii)
    {
        const cb::shared_ptr<cbBreakpoint> &b = GetBreakpoint(ii);

        if (b->GetLocation() == filename)
        {
            breakpoints_for_file.push_back(ii);
        }
    }

    if (lines < 0)
    {
        // removed lines
        // make "lines" positive, for easier reading below
        lines = -lines;
        int endline = startline + lines - 1;

        std::vector<cb::shared_ptr<cbBreakpoint> > to_remove;

        for (std::vector<int>::iterator it = breakpoints_for_file.begin(); it != breakpoints_for_file.end(); ++it)
        {
            const cb::shared_ptr<cbBreakpoint> &b = GetBreakpoint(*it);
            if (b->GetLine() > endline)
                ShiftBreakpoint(*it, -lines);
            else if (b->GetLine() >= startline && b->GetLine() <= endline)
                to_remove.push_back(b);
        }

        for (std::vector<cb::shared_ptr<cbBreakpoint> >::iterator it = to_remove.begin(); it != to_remove.end(); ++it)
            DeleteBreakpoint(*it);
    }
    else
    {
        for (std::vector<int>::iterator it = breakpoints_for_file.begin(); it != breakpoints_for_file.end(); ++it)
        {
            const cb::shared_ptr<cbBreakpoint> &b = GetBreakpoint(*it);
            if (b->GetLine() > startline)
                ShiftBreakpoint(*it, lines);
        }
    }
}

void cbDebuggerPlugin::OnEditorOpened(CodeBlocksEvent& event)
{
    // when an editor opens, look if we have breakpoints for it
    // and notify it...
    EditorBase* ed = event.GetEditor();
    if (ed && ed->IsBuiltinEditor())
    {
        cbEditor *editor = static_cast<cbEditor*>(ed);
        editor->RefreshBreakpointMarkers();

        if (IsRunning())
        {
            wxString filename;
            int line;
            GetCurrentPosition(filename, line);

            wxFileName edFileName(ed->GetFilename());
            edFileName.Normalize(wxPATH_NORM_DOTS | wxPATH_NORM_TILDE | wxPATH_NORM_ABSOLUTE | wxPATH_NORM_LONG | wxPATH_NORM_SHORTCUT);

            wxFileName dbgFileName(filename);
            dbgFileName.Normalize(wxPATH_NORM_DOTS | wxPATH_NORM_TILDE | wxPATH_NORM_ABSOLUTE | wxPATH_NORM_LONG | wxPATH_NORM_SHORTCUT);
            if (dbgFileName.GetFullPath().IsSameAs(edFileName.GetFullPath()) && line != -1)
            {
                editor->SetDebugLine(line - 1);
            }
        }
    }
    event.Skip(); // must do
}

void cbDebuggerPlugin::OnProjectActivated(CodeBlocksEvent& event)
{
    // allow others to catch this
    event.Skip();

    if(this != Manager::Get()->GetDebuggerManager()->GetActiveDebugger())
        return;
    // when a project is activated and it's not the actively debugged project,
    // ask the user to end debugging or re-activate the debugged project.

    if (!IsRunning())
        return;

    if (event.GetProject() != GetProject() && GetProject())
    {
        wxString msg = _("You can't change the active project while you're actively debugging another.\n"
                         "Do you want to stop debugging?\n\n"
                         "Click \"Yes\" to stop debugging now or click \"No\" to re-activate the debuggee.");
        if (cbMessageBox(msg, _("Warning"), wxICON_WARNING | wxYES_NO) == wxID_YES)
        {
            Stop();
        }
        else
        {
            Manager::Get()->GetProjectManager()->SetProject(GetProject());
        }
    }
}

void cbDebuggerPlugin::OnProjectClosed(CodeBlocksEvent& event)
{
    // allow others to catch this
    event.Skip();

    if(this != Manager::Get()->GetDebuggerManager()->GetActiveDebugger())
        return;
    CleanupWhenProjectClosed(event.GetProject());

    // when a project closes, make sure it's not the actively debugged project.
    // if so, end debugging immediately!
    if (!IsRunning())
        return;

    if (event.GetProject() == GetProject())
    {
        AnnoyingDialog dlg(_("Project closed while debugging message"),
                           _("The project you were debugging has closed.\n"
                             "(The application most likely just finished.)\n"
                             "The debugging session will terminate immediately."),
                            wxART_WARNING, AnnoyingDialog::OK);
        dlg.ShowModal();
        Stop();
        ResetProject();
    }
}



void cbDebuggerPlugin::ShowLog(bool clear)
{
    TextCtrlLogger *log = Manager::Get()->GetDebuggerManager()->GetLogger();
    if (log)
    {
        // switch to the debugger log
        CodeBlocksLogEvent eventSwitchLog(cbEVT_SWITCH_TO_LOG_WINDOW, log);
        Manager::Get()->ProcessEvent(eventSwitchLog);
        CodeBlocksLogEvent eventShowLog(cbEVT_SHOW_LOG_MANAGER);
        Manager::Get()->ProcessEvent(eventShowLog);

        if (clear)
            log->Clear();
    }
}

void cbDebuggerPlugin::Log(const wxString& msg, Logger::level level)
{
    if (IsAttached())
    {
        Manager::Get()->GetLogManager()->Log((m_lastLineWasNormal ? wxEmptyString : wxT("\n")) + msg, m_LogPageIndex,
                                             level);
        m_lastLineWasNormal = true;
    }
}

void cbDebuggerPlugin::DebugLog(const wxString& msg, Logger::level level)
{
    // gdb debug messages
    if (IsAttached() && HasDebugLog())
    {
        Manager::Get()->GetLogManager()->Log((!m_lastLineWasNormal ? wxT("[debug]") : wxT("\n[debug]")) + msg,
                                             m_LogPageIndex, level);
        m_lastLineWasNormal = false;
    }
}

bool cbDebuggerPlugin::HasDebugLog() const
{
    return cbDebuggerCommonConfig::GetFlag(cbDebuggerCommonConfig::ShowDebuggersLog);
}

void cbDebuggerPlugin::ClearLog()
{
    Manager::Get()->GetDebuggerManager()->GetLogger()->Clear();
}

void cbDebuggerPlugin::SetupLog(int normalIndex)
{
    m_LogPageIndex = normalIndex;
}

void cbDebuggerPlugin::SwitchToDebuggingLayout()
{
    CodeBlocksLayoutEvent queryEvent(cbEVT_QUERY_VIEW_LAYOUT);

    const cbDebuggerConfiguration &config = GetActiveConfig();

    wxString perspectiveName;
    switch (cbDebuggerCommonConfig::GetPerspective())
    {
    case cbDebuggerCommonConfig::OnePerDebugger:
        perspectiveName = GetGUIName();
        break;
    case cbDebuggerCommonConfig::OnePerDebuggerConfig:
        perspectiveName = GetGUIName() + wxT(":") + config.GetName();
        break;
    case cbDebuggerCommonConfig::UseCurrent:
        m_PreviousLayout = wxString();
        return;
    case cbDebuggerCommonConfig::OnlyOne:
    default:
        perspectiveName = _("Debugging");
    }

    CodeBlocksLayoutEvent switchEvent(cbEVT_SWITCH_VIEW_LAYOUT, perspectiveName);

    Manager::Get()->GetLogManager()->DebugLog(wxString::Format(_("Switching layout to \"%s\""), switchEvent.layout));

    // query the current layout
    Manager::Get()->ProcessEvent(queryEvent);
    m_PreviousLayout = queryEvent.layout;

    // switch to debugging layout
    Manager::Get()->ProcessEvent(switchEvent);
}

void cbDebuggerPlugin::SwitchToPreviousLayout()
{
    if (m_PreviousLayout.empty())
        return;

    CodeBlocksLayoutEvent switchEvent(cbEVT_SWITCH_VIEW_LAYOUT, m_PreviousLayout);

    wxString const &name = !switchEvent.layout.IsEmpty() ? switchEvent.layout : wxString(_("Code::Blocks default"));

    Manager::Get()->GetLogManager()->DebugLog(wxString::Format(_("Switching layout to \"%s\""), name));

    // switch to previous layout
    Manager::Get()->ProcessEvent(switchEvent);
}

bool cbDebuggerPlugin::GetDebuggee(wxString &pathToDebuggee, wxString &workingDirectory, ProjectBuildTarget* target)
{
    if (!target)
        return false;

    wxString out;
    switch (target->GetTargetType())
    {
        case ttExecutable:
        case ttConsoleOnly:
        case ttNative:
            {
                out = UnixFilename(target->GetOutputFilename());
                Manager::Get()->GetMacrosManager()->ReplaceEnvVars(out); // apply env vars
                wxFileName f(out);
                f.MakeAbsolute(target->GetParentProject()->GetBasePath());
                out = f.GetFullPath();
                Log(_("Adding file: ") + out);
                ConvertDirectory(out);
            }
            break;

        case ttStaticLib:
        case ttDynamicLib:
            // check for hostapp
            if (target->GetHostApplication().IsEmpty())
            {
                cbMessageBox(_("You must select a host application to \"run\" a library..."));
                return false;
            }
            out = UnixFilename(target->GetHostApplication());
            Manager::Get()->GetMacrosManager()->ReplaceEnvVars(out); // apply env vars
            Log(_("Adding file: ") + out);
            ConvertDirectory(out);
            break;

        case ttCommandsOnly: // fall through:
        default:
            Log(_("Unsupported target type (Project -> Properties -> Build Targets -> Type)"), Logger::error);
            return false;
    }
    if (out.empty())
    {
        Log(_("Couldn't find the path to the debuggee!"), Logger::error);
        return false;
    }

    workingDirectory = target->GetWorkingDir();
    Manager::Get()->GetMacrosManager()->ReplaceEnvVars(workingDirectory);
    wxFileName cd(workingDirectory);
    if (cd.IsRelative())
        cd.MakeAbsolute(target->GetParentProject()->GetBasePath());
    workingDirectory = cd.GetFullPath();

    pathToDebuggee = out;
    return true;
}

bool cbDebuggerPlugin::EnsureBuildUpToDate(StartType startType)
{
    m_StartType = startType;
    m_WaitingCompilerToFinish = false;

    // compile project/target (if not attaching to a PID)
    if (!IsAttachedToProcess())
    {
        // should we build to make sure project is up-to-date?
        if (!cbDebuggerCommonConfig::GetFlag(cbDebuggerCommonConfig::AutoBuild))
        {
            m_WaitingCompilerToFinish = false;
            m_pCompiler = nullptr;
            return true;
        }

        // make sure the target is compiled
        const std::vector<cbCompilerPlugin*> &compilers = Manager::Get()->GetPluginManager()->GetCompilerPlugins();
        if (compilers.empty())
            m_pCompiler = nullptr;
        else
            m_pCompiler = compilers.front();
        if (m_pCompiler)
        {
            // is the compiler already running?
            if (m_pCompiler->IsRunning())
            {
                Log(_("Compiler in use..."));
                Log(_("Aborting debugging session"));
                cbMessageBox(_("The compiler is currently in use. Aborting debugging session..."),
                             _("Compiler running"), wxICON_WARNING);
                return false;
            }

            Log(_("Building to ensure sources are up-to-date"));
            m_WaitingCompilerToFinish = true;
            m_pCompiler->Build();
            // now, when the build is finished, DoDebug will be launched in OnCompilerFinished()
        }
    }
    return true;
}

void cbDebuggerPlugin::OnCompilerFinished(cb_unused CodeBlocksEvent& event)
{
    if (m_WaitingCompilerToFinish)
    {
        m_WaitingCompilerToFinish = false;
        bool compilerFailed = false;
        // only proceed if build succeeded
        if (m_pCompiler && m_pCompiler->GetExitCode() != 0)
        {
            AnnoyingDialog dlg(_("Debug anyway?"),
                               _("Build failed, do you want to debug the program?"),
                               wxART_QUESTION, AnnoyingDialog::YES_NO, AnnoyingDialog::rtNO,
                               _("&Debug anyway"));
            if (dlg.ShowModal() != AnnoyingDialog::rtYES)
            {
                ProjectManager *manager = Manager::Get()->GetProjectManager();
                if (manager->GetIsRunning() && manager->GetIsRunning() == this)
                    manager->SetIsRunning(nullptr);
                compilerFailed = true;
            }
        }
        ShowLog(false);
        if (!CompilerFinished(compilerFailed, m_StartType))
        {
            ProjectManager *manager = Manager::Get()->GetProjectManager();
            if (manager->GetIsRunning() && manager->GetIsRunning() == this)
                manager->SetIsRunning(nullptr);
        }
    }
}

#ifndef __WXMSW__
namespace
{
wxString MakeSleepCommand()
{
    return wxString::Format(wxT("sleep %lu"), 80000000 + ::wxGetProcessId());
}

struct ConsoleInfo
{
    ConsoleInfo(const wxString &path = wxEmptyString, int pid = -1) : ttyPath(path), sleepPID(pid) {}

    bool IsValid() const { return !ttyPath.empty() && sleepPID > 0; }

    wxString ttyPath;
    int sleepPID;
};

ConsoleInfo GetConsoleTty(int consolePID)
{
    // execute the ps x -o command  and read PS output to get the /dev/tty field
    wxArrayString psOutput;
    wxArrayString psErrors;

    int result = wxExecute(wxT("ps x -o tty,pid,command"), psOutput, psErrors, wxEXEC_SYNC);
    if (result != 0)
        return ConsoleInfo();

    // find task with our unique sleep time
    const wxString &uniqueSleepTimeStr = MakeSleepCommand();

    // search the output of "ps pid" command
    int count = psOutput.GetCount();
    for (int i = count - 1; i > -1; --i)
    {
        // find the pts/# or tty/# or whatever it's called
        // by searching the output of "ps x -o tty,pid,command" command.
        // The output of ps looks like:
        // TT       PID   COMMAND
        // pts/0    13342 /bin/sh ./run.sh
        // pts/0    13343 /home/pecanpecan/devel/trunk/src/devel/codeblocks
        // pts/0    13361 /usr/bin/gdb -nx -fullname -quiet -args ./conio
        // pts/0    13362 xterm -font -*-*-*-*-*-*-20-*-*-*-*-*-*-* -T Program Console -e sleep 93343
        // pts/2    13363 sleep 93343
        // ?        13365 /home/pecan/proj/conio/conio
        // pts/1    13370 ps x -o tty,pid,command

        const wxString &psCmd = psOutput.Item(i);
        if (psCmd.Contains(uniqueSleepTimeStr))
        {
            // Extract the pid for the line
            long pidForLine;
            if (psCmd.AfterFirst(' ').Trim(false).BeforeFirst(' ').Trim(true).ToLong(&pidForLine))
            {
                // Check if we are at the correct line. It is possible that there are two lines which contain the
                // "sleep" string. One for the sleep process and one for the terminal process. We want to skip the
                // line for the terminal process.
                if (pidForLine != consolePID)
                    return ConsoleInfo(wxT("/dev/") + psCmd.BeforeFirst(' '), pidForLine);
            }
        }
    }
    return ConsoleInfo();
}

struct ConsoleProcessTerminationInfo
{
    ConsoleProcessTerminationInfo() : status(-1), terminated(false) {}

    bool FailedToStart() const { return terminated && status != 0; }

    int status;
    bool terminated;
};

struct ConsoleProcess : wxProcess
{
    ConsoleProcess(cb::shared_ptr<ConsoleProcessTerminationInfo> cptInfo) : info(cptInfo) {}
    void OnTerminate(int pid, int status) override
    {
        info->terminated = true;
        info->status = status;
    }
    cb::shared_ptr<ConsoleProcessTerminationInfo> info;
};

} // namespace
#endif

int cbDebuggerPlugin::RunNixConsole(wxString &consoleTty)
{
    consoleTty = wxEmptyString;
#ifndef __WXMSW__
    // Start a terminal and put the shell to sleep with -e sleep 80000.
    // Fetch the terminal's tty, so we can tell the debugger what TTY to use,
    // thus redirecting program's stdin/stdout/stderr to the terminal.

    wxString cmd;
    int consolePid = 0;
    // Use the terminal specified by the user in the Settings -> Environment.
    wxString term = Manager::Get()->GetConfigManager(_T("app"))->Read(_T("/console_terminal"), DEFAULT_CONSOLE_TERM);

    term.Replace(_T("$TITLE"), wxString(wxT("'"))+_("Program Console")+wxT("'"));
    cmd << term << _T(" ");

    const wxString &sleepCommand = MakeSleepCommand();
    cmd << sleepCommand;

    Manager::Get()->GetMacrosManager()->ReplaceEnvVars(cmd);

    // The lifetime of wxProcess objects is very uncertain, so we are using a shared pointer to
    // prevent us accessing deleted objects.
    cb::shared_ptr<ConsoleProcessTerminationInfo> processInfo(new ConsoleProcessTerminationInfo);
    ConsoleProcess *process = new ConsoleProcess(processInfo);
    consolePid = wxExecute(cmd, wxEXEC_ASYNC, process);
    if (consolePid <= 0)
        return -1;

    // Try to find the TTY. We're using a loop, because some slow machines might make the check fail due
    // to a slow starting terminal.
    for (int ii = 0; ii < 100; ++ii)
    {
        // First, wait for the terminal to settle down, else PS won't see the sleep task
        Manager::Yield();
        ::wxMilliSleep(200);

        // Try to detect if the terminal command is present or its parameters are valid.
        if (processInfo->FailedToStart() /*&& ii > 0*/)
        {
            Log(wxString::Format("Failed to execute terminal command: '%s' (exit code: %d)",
                                 cmd, processInfo->status), Logger::error);
            break;
        }

        // Try to find tty path and pid for the sleep command we've just executed.
        const ConsoleInfo &info = GetConsoleTty(consolePid);

        // If there is no sleep command yet, do another iteration after a small delay.
        if (!info.IsValid())
            continue;

        // Try to find if the console window is still alive. Newer terminals like gnome-terminal
        // try to be easier on resources and use a shared server process. For these terminals the
        // spawned terminal process exits immediately, but the sleep command is still executed.
        // If we detect such case we will return the PID for the sleep command instead of the PID
        // for the terminal.
        if (kill(consolePid, 0) == -1 && errno == ESRCH) {
            DebugLog(wxString::Format("Using sleep command's PID as console PID %d, TTY %s", info.sleepPID, info.ttyPath));
            consoleTty = info.ttyPath;
            return info.sleepPID;
        }
        else
        {
            DebugLog(wxString::Format("Using terminal's PID as console PID %d, TTY %s", info.sleepPID, info.ttyPath));
            consoleTty = info.ttyPath;
            return consolePid;
        }
    }
    // failed to find the console tty
    if (consolePid != 0)
        ::wxKill(consolePid);
#endif // !__WWXMSW__
    return -1;
}

void cbDebuggerPlugin::MarkAsStopped()
{
    Manager::Get()->GetProjectManager()->SetIsRunning(nullptr);
}

void cbDebuggerPlugin::BringCBToFront()
{
    wxWindow* app = Manager::Get()->GetAppWindow();
    if (app)
        app->Raise();
}

void cbDebuggerPlugin::UpdateWatches(const std::vector<cb::shared_ptr<cbWatch>> &watches)
{
    for (const cb::shared_ptr<cbWatch>& watch : watches)
        UpdateWatch(watch);
}

void cbDebuggerPlugin::RegisterValueTooltip()
{
    typedef cbEventFunctor<cbDebuggerPlugin, CodeBlocksEvent> Event;
    Manager::Get()->RegisterEventSink(cbEVT_EDITOR_TOOLTIP, new Event(this, &cbDebuggerPlugin::ProcessValueTooltip));
    Manager::Get()->RegisterEventSink(cbEVT_EDITOR_TOOLTIP_CANCEL,
                                      new Event(this, &cbDebuggerPlugin::CancelValueTooltip));
}

bool cbDebuggerPlugin::ShowValueTooltip(cb_unused int style)
{
    return false;
}

// Default implementation does nothing
void cbDebuggerPlugin::OnValueTooltip(cb_unused const wxString& token, cb_unused const wxRect& evalRect)
{
}

void cbDebuggerPlugin::ProcessValueTooltip(CodeBlocksEvent& event)
{
    event.Skip();
    if (cbDebuggerCommonConfig::GetFlag(cbDebuggerCommonConfig::RequireCtrlForTooltips))
    {
        if (!wxGetKeyState(WXK_CONTROL))
            return;
    }

    if (Manager::Get()->GetDebuggerManager()->GetInterfaceFactory()->IsValueTooltipShown())
        return;

    if (!ShowValueTooltip(event.GetInt()))
        return;

    EditorBase* base = event.GetEditor();
    cbEditor* ed = base && base->IsBuiltinEditor() ? static_cast<cbEditor*>(base) : nullptr;
    if (!ed)
        return;

    if (ed->IsContextMenuOpened())
        return;

    // get rid of other calltips (if any) [for example the code completion one, at this time we
    // want the debugger value call/tool-tip to win and be shown]
    if (ed->GetControl()->CallTipActive())
        ed->GetControl()->CallTipCancel();

    wxPoint pt;
    pt.x = event.GetX();
    pt.y = event.GetY();

    const wxString &token = GetEditorWordAtCaret(&pt);
    if (!token.empty())
    {
        pt = ed->GetControl()->ClientToScreen(pt);
        OnValueTooltip(token, wxRect(pt.x - 5, pt.y, 10, 10));
    }
}

void cbDebuggerPlugin::CancelValueTooltip(cb_unused CodeBlocksEvent& event)
{
    Manager::Get()->GetDebuggerManager()->GetInterfaceFactory()->HideValueTooltip();
}
/////
///// cbToolPlugin
/////

cbToolPlugin::cbToolPlugin()
{
    m_Type = ptTool;
}

/////
///// cbMimePlugin
/////

cbMimePlugin::cbMimePlugin()
{
    m_Type = ptMime;
}

/////
///// cbCodeCompletionPlugin
/////

cbCodeCompletionPlugin::cbCodeCompletionPlugin()
{
    m_Type = ptCodeCompletion;
}

void cbCodeCompletionPlugin::DoAutocomplete(cb_unused const CCToken& token, cb_unused cbEditor* ed)
{
    // do nothing: allow (wx)Scintilla to handle the insert
}

void cbCodeCompletionPlugin::DoAutocomplete(const wxString& token, cbEditor* ed)
{
    DoAutocomplete(CCToken(-1, token), ed);
}

bool cbCodeCompletionPlugin::IsProviderFor(cbEditor* ed)
{
    return (Manager::Get()->GetCCManager()->GetProviderFor(ed) == this);
}

/////
///// cbWizardPlugin
/////

cbWizardPlugin::cbWizardPlugin()
{
    m_Type = ptWizard;
}

/////
///// cbSmartIndentPlugin
/////

cbSmartIndentPlugin::cbSmartIndentPlugin()
{
    m_Type = ptSmartIndent;
}

void cbSmartIndentPlugin::OnAttach()
{
    m_FunctorId = EditorHooks::RegisterHook( new EditorHooks::cbSmartIndentEditorHookFunctor(this) );
    Manager::Get()->RegisterEventSink(cbEVT_EDITOR_CC_DONE, new cbEventFunctor<cbSmartIndentPlugin, CodeBlocksEvent>(this, &cbSmartIndentPlugin::OnCCDoneEvent));
}

void cbSmartIndentPlugin::OnRelease(cb_unused bool appShutDown)
{
    EditorHooks::UnregisterHook(m_FunctorId);
}

bool cbSmartIndentPlugin::AutoIndentEnabled()const
{
    return Manager::Get()->GetConfigManager(_T("editor"))->ReadBool(_T("/auto_indent"), true);
}

bool cbSmartIndentPlugin::SmartIndentEnabled()const
{
    return Manager::Get()->GetConfigManager(_T("editor"))->ReadBool(_T("/smart_indent"), true);
}

bool cbSmartIndentPlugin::BraceSmartIndentEnabled()const
{
    return Manager::Get()->GetConfigManager(_T("editor"))->ReadBool(_T("/brace_smart_indent"), true);
}

bool cbSmartIndentPlugin::BraceCompletionEnabled()const
{
    return Manager::Get()->GetConfigManager(_T("editor"))->ReadBool(_T("/brace_completion"), true);
}

bool cbSmartIndentPlugin::SelectionBraceCompletionEnabled()const
{
    return Manager::Get()->GetConfigManager(_T("editor"))->ReadBool(_T("/selection_brace_completion"), false);
}

void cbSmartIndentPlugin::Indent(cbStyledTextCtrl* stc, wxString &indent)const
{
    if (stc->GetUseTabs())
        indent << _T('\t'); // 1 tab
    else
        indent << wxString(_T(' '), stc->GetTabWidth()); // n spaces
}

bool cbSmartIndentPlugin::Indent(cbStyledTextCtrl* stc, wxString &indent, int posInLine)const
{
    if (posInLine >= 0)
    {
        if (stc->GetUseTabs())
            indent = wxString(_T('\t'), posInLine/stc->GetTabWidth());
        else
            indent = wxString(_T(' '), posInLine); // n spaces
        return true;
    }
    return false;
}

wxString cbSmartIndentPlugin::GetLastNonCommentWord(cbEditor* ed, int position, unsigned int NumberOfWords)const
{
    cbStyledTextCtrl* stc = ed->GetControl();
    if ( !stc )
        return wxEmptyString;

    if ( position == -1 )
        position = stc->GetCurrentPos();


    wxString str;
    str.Empty();
    int count = 0;
    bool foundlf = false; // For the rare case of CR's without LF's
    while (position)
    {
        wxChar c = stc->GetCharAt(--position);
        int style = stc->GetStyleAt(position);
        bool inComment = stc->IsComment(style);
        if (c == _T('\n'))
        {
            count++;
            foundlf = true;
        }
        else if (c == _T('\r') && !foundlf)
            count++;
        else
            foundlf = false;

        if (count > 1) return str;
        if (!inComment && c != _T(' ') && c != _T('\t') && c != _T('\n') && c != _T('\r') )
        {
            int startpos = stc->WordStartPosition( position, true );
            for ( unsigned int i = 1; i < NumberOfWords ; ++i )
                startpos = stc->WordStartPosition( startpos - 1, true );
            int endpos = stc->WordEndPosition(startpos, true);
            str = stc->GetTextRange(startpos, endpos);
            return str;
        }
    }
    return str;
}

wxChar cbSmartIndentPlugin::GetLastNonWhitespaceChar(cbEditor* ed, int position)const
{
    return GetLastNonWhitespaceChars(ed, position, 1)[0];
}

wxString cbSmartIndentPlugin::GetLastNonWhitespaceChars(cbEditor* ed, int position, unsigned int NumberOfChars)const
{
    cbStyledTextCtrl* stc = ed->GetControl();
    if ( !stc )
        return wxEmptyString;

    if (position == -1)
        position = stc->GetCurrentPos();

    int count = 0; // Used to count the number of blank lines
    bool foundlf = false; // For the rare case of CR's without LF's
    while (position)
    {
        wxChar c = stc->GetCharAt(--position);
        int style = stc->GetStyleAt(position);
        bool inComment = stc->IsComment(style);
        if (c == _T('\n'))
        {
            count++;
            foundlf = true;
        }
        else if (c == _T('\r') && !foundlf)
            count++;
        else
            foundlf = false;
        if (count > 1) return wxEmptyString;
        if (!inComment && c != _T(' ') && c != _T('\t') && c != _T('\n') && c != _T('\r'))
            return stc->GetTextRange(position-NumberOfChars+1, position+1);
    }
    return wxEmptyString;
}

wxChar cbSmartIndentPlugin::GetNextNonWhitespaceCharOnLine(cbStyledTextCtrl* stc, int position, int *pos)const
{
    if (position == -1)
        position = stc->GetCurrentPos();

    while (position < stc->GetLength())
    {
        wxChar c = stc->GetCharAt(position);
        if ( c == _T('\n') || c ==  _T('\r') )
        {
            if ( pos ) *pos = position;
            return 0;
        }
        if ( c !=  _T(' ') && c != _T('\t') )
        {
            if ( pos ) *pos = position;
            return c;
        }
        position++;
    }

    return 0;
}

int cbSmartIndentPlugin::FindBlockStart(cbStyledTextCtrl* stc, int position, wxChar blockStart, wxChar blockEnd, cb_unused bool skipNested)const
{
    int lvl = 0;
    wxChar b = stc->GetCharAt(position);
    while (b)
    {
        if (b == blockEnd)
            ++lvl;
        else if (b == blockStart)
        {
            if (lvl == 0)
                return position;
            --lvl;
        }
        --position;
        b = stc->GetCharAt(position);
    }
    return -1;
}

int cbSmartIndentPlugin::FindBlockStart(cbStyledTextCtrl* stc, int position, wxString blockStart, wxString blockEnd, bool CaseSensitive)const
{
    int pos = position;
    int pb, pe;
    int lvl = 0;

    int flags = wxSCI_FIND_WHOLEWORD;
    if ( CaseSensitive )
        flags |= wxSCI_FIND_MATCHCASE;

    do
    {
        pb =  stc->FindText(pos, 0, blockStart, flags);
        pe =  stc->FindText(pos, 0, blockEnd, flags);
        if ( pe > pb )
        {
            pos = pe;
            ++lvl;
            continue;
        }
        pos = pb;
        if ( lvl == 0 ) return pb;
        --lvl;
    }
    while( pos != -1 );

    return -1;
}

//ToDo: Is this c++ only?
int cbSmartIndentPlugin::GetFirstBraceInLine(cbStyledTextCtrl* stc, int string_style)const
{
    int curr_position = stc->GetCurrentPos();
    int position = curr_position;
    int min_brace_position = position;
    int closing_braces = 0;
    bool found_brace = false;
    bool has_braces = false;

    while (position)
    {
        wxChar c = stc->GetCharAt(--position);

        int style = stc->GetStyleAt(position);
        if (style == string_style)
            continue;

        if (c == _T(';'))
        {
            found_brace = false;
            break;
        }
        else if (c == _T(')'))
        {
            ++closing_braces;
            has_braces = true;
        }
        else if (c == _T('('))
        {
            has_braces = true;
            if (closing_braces > 0)
                --closing_braces;
            else if (!found_brace)
            {
                min_brace_position = position + 1;
                found_brace = true;
                break;
            }
        }
        else if (c == _T('\n') && position + 1 != curr_position && !has_braces)
        {
            break;
        }
    }

    if (!found_brace)
        return -1;

    int tab_characters = 0;

    while (position)
    {
        wxChar c = stc->GetCharAt(--position);
        if (c == _T('\n') && position + 1 != curr_position)
        {
            break;
        }
        else if (c == _T('\t'))
            ++tab_characters;
    }

    if (stc->GetUseTabs())
    {
        position -= tab_characters * stc->GetTabWidth();
    }
    return min_brace_position - position - 1;
}

wxChar cbSmartIndentPlugin::GetNextNonWhitespaceCharOfLine(cbStyledTextCtrl* stc, int position, int *pos)const
{
    if (position == -1)
        position = stc->GetCurrentPos();

    while (position < stc->GetLength())
    {
        wxChar c = stc->GetCharAt(position);
        if ( c == _T('\n') || c ==  _T('\r') )
        {
            if ( pos ) *pos = position;
            return 0;
        }
        if ( c !=  _T(' ') && c != _T('\t') )
        {
            if ( pos ) *pos = position;
            return c;
        }
        position++;
    }

    return 0;
}

void cbSmartIndentPlugin::OnCCDoneEvent(CodeBlocksEvent& event)
{
    EditorBase* eb = event.GetEditor();
    if (eb && eb->IsBuiltinEditor())
    {
        cbEditor* ed = static_cast<cbEditor *>(eb);
        OnCCDone(ed);
    }
}
