/*
 * 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: 12643 $
 * $Id: backtracedlg.cpp 12643 2022-01-12 19:40:38Z wh11204 $
 * $HeadURL: https://svn.code.sf.net/p/codeblocks/code/trunk/src/src/backtracedlg.cpp $
 */
#include "sdk.h"

#ifndef CB_PRECOMP
    #include <wx/filedlg.h>
    #include <wx/listctrl.h>
    #include <wx/menu.h>
    #include <wx/sizer.h>
    #include <wx/txtstrm.h>
    #include <wx/wfstream.h>
    #include <wx/dataobj.h>
    #include "cbplugin.h"
    #include "configmanager.h"
#endif

#include <wx/clipbrd.h>
#include "filefilters.h"

#include "backtracedlg.h"
#include "cbcolourmanager.h"
#include "debuggermanager.h"

namespace
{
    const int idList = wxNewId();
    const int idSwitch = wxNewId();
    const int idSave = wxNewId();
    const int idJump = wxNewId();
    const int idCopyToClipboard = wxNewId();
    const int idSettingJumpDefault = wxNewId();
    const int idSettingSwitchDefault = wxNewId();
}

BEGIN_EVENT_TABLE(BacktraceDlg, wxPanel)
    EVT_LIST_ITEM_RIGHT_CLICK(idList, BacktraceDlg::OnListRightClick)
    EVT_LIST_ITEM_ACTIVATED(idList, BacktraceDlg::OnDoubleClick)
    EVT_MENU(idSwitch, BacktraceDlg::OnSwitchFrame)
    EVT_MENU(idSave, BacktraceDlg::OnSave)
    EVT_MENU(idJump, BacktraceDlg::OnJump)
    EVT_MENU(idCopyToClipboard, BacktraceDlg::OnCopyToClipboard)

    EVT_MENU(idSettingJumpDefault, BacktraceDlg::OnSettingJumpDefault)
    EVT_MENU(idSettingSwitchDefault, BacktraceDlg::OnSettingSwitchDefault)

    EVT_UPDATE_UI(idSwitch, BacktraceDlg::OnUpdateUI)
END_EVENT_TABLE()

BacktraceDlg::BacktraceDlg(wxWindow* parent) :
    wxPanel(parent)
{
    m_list = new wxListCtrl(this, idList, wxDefaultPosition, wxDefaultSize,
                            wxLC_REPORT | wxLC_SINGLE_SEL | wxLC_HRULES | wxLC_VRULES);
    wxBoxSizer* bs = new wxBoxSizer(wxVERTICAL);
    bs->Add(m_list, 1, wxEXPAND | wxALL);
    SetAutoLayout(true);
    SetSizer(bs);

    m_list->InsertColumn(0, _("Nr"), wxLIST_FORMAT_RIGHT);
    m_list->InsertColumn(1, _("Address"), wxLIST_FORMAT_LEFT);
    m_list->InsertColumn(2, _("Function"), wxLIST_FORMAT_LEFT);
    m_list->InsertColumn(3, _("File"), wxLIST_FORMAT_LEFT, 128);
    m_list->InsertColumn(4, _("Line"), wxLIST_FORMAT_RIGHT, 64);

    Manager::Get()->GetColourManager()->RegisterColour(_("Debugger"), _("Backtrace active frame background"),
                                                       wxT("dbg_backtrace_active_background"), *wxRED);
    Manager::Get()->GetColourManager()->RegisterColour(_("Debugger"), _("Backtrace active frame foreground"),
                                                       wxT("dbg_backtrace_active_foreground"), *wxWHITE);
}

void BacktraceDlg::Reload()
{
    cbDebuggerPlugin *plugin = Manager::Get()->GetDebuggerManager()->GetActiveDebugger();
    if (!plugin)
        return;

    ColourManager &colours = *Manager::Get()->GetColourManager();
    const wxColour &activeBackground = colours.GetColour(wxT("dbg_backtrace_active_background"));
    const wxColour &activeForeground = colours.GetColour(wxT("dbg_backtrace_active_foreground"));

    m_list->Freeze();
    m_list->DeleteAllItems();

    int active_frame = plugin->GetActiveStackFrame();
    int active_frame_index = 0;
    for (int ii = 0; ii < plugin->GetStackFrameCount(); ++ii)
    {
        cb::shared_ptr<const cbStackFrame> frame = plugin->GetStackFrame(ii);
        wxString addr = frame->GetAddressAsString();
        wxString num;
        num.Printf(wxT("%d"), frame->GetNumber());
        int idx = m_list->InsertItem(m_list->GetItemCount(), num);
        m_list->SetItem(idx, 1, addr);
        m_list->SetItem(idx, 2, frame->GetSymbol());
        m_list->SetItem(idx, 3, frame->GetFilename());
        m_list->SetItem(idx, 4, frame->GetLine());

        if (active_frame == frame->GetNumber())
        {
            active_frame_index = ii;
            m_list->SetItemBackgroundColour(ii, activeBackground);
            m_list->SetItemTextColour(ii, activeForeground);
        }
    }

    if (active_frame_index < m_list->GetItemCount())
        m_list->EnsureVisible(active_frame_index);
    m_list->Thaw();
    m_list->SetColumnWidth(0, 32);

    for (int i = 1; i < 4; ++i)
        m_list->SetColumnWidth(i, wxLIST_AUTOSIZE);
}

void BacktraceDlg::EnableWindow(bool enable)
{
    Enable(enable);
    m_list->Enable(enable);
}


void BacktraceDlg::OnListRightClick(cb_unused wxListEvent& event)
{
    wxMenu m;
    m.Append(idJump, _("Jump to this file/line"));
    m.Append(idSwitch, _("Switch to this frame"));
    m.AppendSeparator();
    m.Append(idSave, _("Save to file..."));
    m.Append(idCopyToClipboard, _("Copy to clipboard"));
    m.AppendSeparator();
    m.AppendRadioItem(idSettingJumpDefault, _("Jump on double-click"));
    m.AppendRadioItem(idSettingSwitchDefault, _("Switch on double-click"));

    bool jump_on_double_click = cbDebuggerCommonConfig::GetFlag(cbDebuggerCommonConfig::JumpOnDoubleClick);

    m.Check(idSettingJumpDefault, jump_on_double_click);
    m.Check(idSettingSwitchDefault, !jump_on_double_click);

    m_list->PopupMenu(&m);
}

void BacktraceDlg::OnJump(cb_unused wxCommandEvent& event)
{
    if (m_list->GetSelectedItemCount() == 0)
        return;

    // find selected item index
    const int index = m_list->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);

    wxListItem info;
    info.SetId(index);
    info.SetMask(wxLIST_MASK_TEXT);
    info.SetColumn(3);
    const wxString file(m_list->GetItem(info) ? info.GetText() : wxString());
    info.SetColumn(4);
    const wxString line(m_list->GetItem(info) ? info.GetText() : wxString());
    if (!file.empty() && !line.empty())
    {
        long line_number;
        line.ToLong(&line_number, 10);

        cbDebuggerPlugin *plugin = Manager::Get()->GetDebuggerManager()->GetActiveDebugger();
        if (plugin)
            plugin->SyncEditor(file, line_number, false);
    }
}

void BacktraceDlg::OnSwitchFrame(cb_unused wxCommandEvent& event)
{
    if (!IsSwitchFrameEnabled())
        return;

    if (m_list->GetSelectedItemCount() == 0)
        return;

    // find selected item index
    int index = m_list->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
    // read the frame number from the first column
    long realFrameNr;
    if (m_list->GetItemText(index).ToLong(&realFrameNr))
    {
        // switch to this frame
        cbDebuggerPlugin *plugin = Manager::Get()->GetDebuggerManager()->GetActiveDebugger();
        if (plugin)
            plugin->SwitchToFrame(realFrameNr);
    }
    else
        cbMessageBox(_("Couldn't find out the frame number!"), _("Error"), wxICON_ERROR);
}

void BacktraceDlg::OnDoubleClick(cb_unused wxListEvent& event)
{
    bool jump = cbDebuggerCommonConfig::GetFlag(cbDebuggerCommonConfig::JumpOnDoubleClick);
    wxCommandEvent evt;
    if (jump || !IsSwitchFrameEnabled())
        OnJump(evt);
    else
        OnSwitchFrame(evt);
}

void BacktraceDlg::OnSave(cb_unused wxCommandEvent& event)
{
    wxFileDialog dlg(this, _("Save as text file"), wxEmptyString, wxEmptyString,
                     FileFilters::GetFilterAll(), wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
    PlaceWindow(&dlg);
    if (dlg.ShowModal() != wxID_OK)
        return;

    wxFFileOutputStream output(dlg.GetPath());
    wxTextOutputStream text(output);

    for (int ii = 0; ii < m_list->GetItemCount(); ++ii)
    {
        wxListItem info;
        info.SetId(ii);
        info.SetColumn(1);
        info.SetMask(wxLIST_MASK_TEXT);
        const wxString addr(m_list->GetItem(info) && !info.GetText().empty() ? info.GetText() : _T("??"));
        info.SetColumn(2);
        const wxString func(m_list->GetItem(info) && !info.GetText().empty() ? info.GetText() : _T("??"));
        info.SetColumn(3);
        const wxString file(m_list->GetItem(info) && !info.GetText().empty() ? info.GetText() : _T("??"));
        info.SetColumn(4);
        const wxString line(m_list->GetItem(info) && !info.GetText().empty() ? info.GetText() : _T("??"));

        text << '#' << m_list->GetItemText(ii) << ' '
             << addr << '\t'
             << func << ' '
             << '(' << file << ':' << line << ')'
             << '\n';
    }

    cbMessageBox(_("File saved"), _("Result"), wxICON_INFORMATION);
}

void BacktraceDlg::OnCopyToClipboard(cb_unused wxCommandEvent& event)
{
    wxString text;
    for (int ii = 0; ii < m_list->GetItemCount(); ++ii)
    {
        wxListItem info;
        info.SetId(ii);
        info.SetColumn(1);
        info.SetMask(wxLIST_MASK_TEXT);
        const wxString addr(m_list->GetItem(info) && !info.GetText().empty() ? info.GetText() : _T("??"));
        info.SetColumn(2);
        const wxString func(m_list->GetItem(info) && !info.GetText().empty() ? info.GetText() : _T("??"));
        info.SetColumn(3);
        const wxString file(m_list->GetItem(info) && !info.GetText().empty() ? info.GetText() : _T("??"));
        info.SetColumn(4);
        const wxString line(m_list->GetItem(info) && !info.GetText().empty() ? info.GetText() : _T("??"));

        text << '#' << m_list->GetItemText(ii) << ' '
             << addr << '\t'
             << func << ' '
             << '(' << file << ':' << line << ')'
             << '\n';
    }

    wxTextDataObject *object = new wxTextDataObject(text);
    if(wxTheClipboard->Open())
    {
        wxTheClipboard->SetData(object);
        wxTheClipboard->Close();
    }
}

void BacktraceDlg::OnSettingJumpDefault(wxCommandEvent& event)
{
    bool checked = event.IsChecked();
    cbDebuggerCommonConfig::SetFlag(cbDebuggerCommonConfig::JumpOnDoubleClick, checked);
}

void BacktraceDlg::OnSettingSwitchDefault(wxCommandEvent& event)
{
    bool checked = event.IsChecked();
    cbDebuggerCommonConfig::SetFlag(cbDebuggerCommonConfig::JumpOnDoubleClick, !checked);
}

void BacktraceDlg::OnUpdateUI(wxUpdateUIEvent &event)
{
    event.Enable(IsSwitchFrameEnabled());
}

bool BacktraceDlg::IsSwitchFrameEnabled() const
{
    cbDebuggerPlugin *plugin = Manager::Get()->GetDebuggerManager()->GetActiveDebugger();
    return plugin && plugin->IsRunning() && plugin->IsStopped();
}
