#include <sdk.h>
#include <wx/intl.h>
#include <wx/sizer.h>
#include <wx/menu.h>
#include <wx/textdlg.h>
#include <wx/msgdlg.h>
#include <wx/app.h>
#include <globals.h>
#include "debuggertree.h"
#include <manager.h>
#include <messagemanager.h>

int cbCustom_WATCHES_CHANGED = wxNewId();
int idTree = wxNewId();
int idAddWatch = wxNewId();
int idEditWatch = wxNewId();
int idDeleteWatch = wxNewId();

#ifndef __WXMSW__
/*
	Under wxGTK, I have noticed that wxTreeCtrl is not sending a EVT_COMMAND_RIGHT_CLICK
	event when right-clicking on the client area.
	This is a "proxy" wxTreeCtrl descendant that handles this for us...
*/
class WatchTree : public wxTreeCtrl
{
	public:
		WatchTree(wxWindow* parent, int id) : wxTreeCtrl(parent, id) {}
	protected:
		void OnRightClick(wxMouseEvent& event)
		{
		    //Manager::Get()->GetMessageManager()->DebugLog("OnRightClick");
		    int flags;
		    HitTest(wxPoint(event.GetX(), event.GetY()), flags);
		    if (flags & (wxTREE_HITTEST_ABOVE | wxTREE_HITTEST_BELOW | wxTREE_HITTEST_NOWHERE))
		    {
		    	// "proxy" the call
			    wxCommandEvent e(wxEVT_COMMAND_RIGHT_CLICK, idTree);
				wxPostEvent(GetParent(), e);
			}
			else
		    	event.Skip();
		}
		DECLARE_EVENT_TABLE();
};

BEGIN_EVENT_TABLE(WatchTree, wxTreeCtrl)
	EVT_RIGHT_DOWN(WatchTree::OnRightClick)
END_EVENT_TABLE()
#endif // !__WXMSW__

BEGIN_EVENT_TABLE(DebuggerTree, wxPanel)
    EVT_TREE_ITEM_RIGHT_CLICK(idTree, DebuggerTree::OnTreeRightClick)
    EVT_COMMAND_RIGHT_CLICK(idTree, DebuggerTree::OnRightClick)
	EVT_MENU(idAddWatch, DebuggerTree::OnAddWatch)
	EVT_MENU(idEditWatch, DebuggerTree::OnEditWatch)
	EVT_MENU(idDeleteWatch, DebuggerTree::OnDeleteWatch)
END_EVENT_TABLE()

DebuggerTree::DebuggerTree(wxEvtHandler* debugger, wxNotebook* parent)
    : wxPanel(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL | wxCLIP_CHILDREN),
    m_pParent(parent),
	m_pDebugger(debugger)
{
    wxBoxSizer* bs = new wxBoxSizer(wxVERTICAL);
#ifndef __WXMSW__
	m_pTree = new WatchTree(this, idTree);
#else
	m_pTree = new wxTreeCtrl(this, idTree);
#endif
    bs->Add(m_pTree, 1, wxEXPAND | wxALL);
    SetAutoLayout(TRUE);
    SetSizer(bs);

    m_pParent->AddPage(this, _("Watches"));
    m_PageIndex = m_pParent->GetPageCount() - 1;

    BuildTree(wxEmptyString);
}

DebuggerTree::~DebuggerTree()
{
	//dtor
	m_pParent->RemovePage(m_PageIndex);
}

void DebuggerTree::BuildTree(const wxString& infoText)
{
	wxArrayString treeState;
	::SaveTreeState(m_pTree, m_pTree->GetRootItem(), treeState);
	m_pTree->Freeze();

	m_pTree->DeleteAllItems();
	wxTreeItemId root = m_pTree->AddRoot(_("Watches"));

//    Manager::Get()->GetMessageManager()->DebugLog("DebuggerTree::BuildTree(): Parsing '%s'", infoText.c_str());
	wxString buffer = infoText;
	wxTreeItemId parent = root;
	// remove CRLFs (except if inside quotes)
	int len = buffer.Length();
	bool inQuotes = false;
	for (int i = 0; i < len; ++i)
	{
        if (buffer.GetChar(i) == _T('"') && (i == 0 || (i > 0 && buffer.GetChar(i - 1) != _T('\\'))))
            inQuotes = !inQuotes;
        if (!inQuotes)
        {
            if (buffer.GetChar(i) == _T('\r'))
                buffer.SetChar(i, _T(' '));
            else if (buffer.GetChar(i) == _T('\n'))
                buffer.SetChar(i, _T(','));
        }
	}
	ParseEntry(parent, buffer);

	m_pTree->Expand(root);
	::RestoreTreeState(m_pTree, root, treeState);
	m_pTree->Thaw();
}

int DebuggerTree::FindCharOutsideQuotes(const wxString& str, wxChar ch)
{
    int len = str.Length();
    int i = 0;
    bool inQuotes = false;
    while (i < len)
    {
        if (!inQuotes && str.GetChar(i) == ch)
            return i;
        else if (str.GetChar(i) == _T('"') && (i == 0 || (i > 0 && str.GetChar(i - 1) != _T('\\'))))
            inQuotes = !inQuotes;
        ++i;
    }
    return -1;
}

int DebuggerTree::FindCommaPos(const wxString& str)
{
    // comma is a special case because it separates the fields
    // but it can also appear in a function signature, where
    // we shouldn't treat it as a field separator

    // what we 'll do now, is decide if the comma is inside
    // a function signature.
    // we 'll do it by counting the opening and closing parenthesis
    // *up to* the comma.
    // if they 're equal, it's a field separator.
    // if they 're not, it's in a function signature
    // ;)

    int len = str.Length();
    int i = 0;
    int parCount = 0;
    bool inQuotes = false;
    while (i < len)
    {
        if (str.GetChar(i) == _T('(') && (i == 0 || (i > 0 && str.GetChar(i - 1) != '\\')))
            ++parCount; // increment on opening parenthesis
        else if (str.GetChar(i) == ')' && (i == 0 || (i > 0 && str.GetChar(i - 1) != '\\')))
            --parCount; // decrement on opening parenthesis

        // if it's not inside quotes *and* we have parCount == 0, it's a field separator
        if (!inQuotes && parCount == 0 && str.GetChar(i) == _T(','))
            return i;
        else if (str.GetChar(i) == _T('"') && (i == 0 || (i > 0 && str.GetChar(i - 1) != _T('\\'))))
            inQuotes = !inQuotes;
        ++i;
    }
    return -1;
}

void DebuggerTree::ParseEntry(const wxTreeItemId& parent, wxString& text)
{
#define MIN(a,b) (a < b ? a : b)
    if (text.IsEmpty())
        return;
//    Manager::Get()->GetMessageManager()->DebugLog("DebuggerTree::ParseEntry(): Parsing '%s' (itemId=%p)", text.c_str(), &parent);
	while (1)
	{
		// trim the string from left and right
		text.Trim(true);
		text.Trim(false);

		// find position of '{', '}' and ',' ***outside*** of any quotes.
		// decide which is nearer to the start
		int braceOpenPos = FindCharOutsideQuotes(text, _T('{'));
		if (braceOpenPos == -1)	braceOpenPos = 0xFFFFFE;
		int braceClosePos = FindCharOutsideQuotes(text, _T('}'));
		if (braceClosePos == -1) braceClosePos = 0xFFFFFE;
        int commaPos = FindCommaPos(text);
		if (commaPos == -1) commaPos = 0xFFFFFE;
		int pos = MIN(commaPos, MIN(braceOpenPos, braceClosePos));

		if (pos == 0xFFFFFE)
		{
			if (text.Right(3).Matches(_T(" = ")))
				text.Truncate(text.Length() - 3);
			if (!text.IsEmpty())
			{
				m_pTree->AppendItem(parent, text);
				text.Clear();
            }
			break;
		}
		else
		{
			wxTreeItemId newParent = parent;
			wxString tmp = text.Left(pos);

			if (tmp.Right(3).Matches(_T(" = ")))
				tmp.Truncate(tmp.Length() - 3); // remove " = " if last in string
			if (!tmp.IsEmpty())
				newParent = m_pTree->AppendItem(parent, tmp); // add entry
			text.Remove(0, pos + 1);

			if (pos == braceOpenPos)
				ParseEntry(newParent, text); // proceed one level deeper
			else if (pos == braceClosePos)
				break; // return one level up
		}
	}
#undef MIN
}

void DebuggerTree::ClearWatches()
{
	m_Watches.Clear();
	wxCommandEvent event(cbCustom_WATCHES_CHANGED);
	wxPostEvent(m_pDebugger, event);
}

void DebuggerTree::AddWatch(const wxString& watch)
{
	m_Watches.Add(watch);
	m_Watches.Sort();
	wxCommandEvent event(cbCustom_WATCHES_CHANGED);
	wxPostEvent(m_pDebugger, event);
}

void DebuggerTree::SetWatches(const wxArrayString& watches)
{
	m_Watches = watches;
	wxCommandEvent event(cbCustom_WATCHES_CHANGED);
	wxPostEvent(m_pDebugger, event);
}

const wxArrayString& DebuggerTree::GetWatches()
{
	return m_Watches;
}

void DebuggerTree::DeleteWatch(const wxString& watch)
{
	m_Watches.Remove(watch);
	wxCommandEvent event(cbCustom_WATCHES_CHANGED);
	wxPostEvent(m_pDebugger, event);
}

void DebuggerTree::ShowMenu(wxTreeItemId id, const wxPoint& pt)
{
	wxString caption;
    wxMenu menu(wxEmptyString);

	// add watch always visible
	menu.Append(idAddWatch, _("&Add watch"));

	// we have to have a valid id for the following to be enabled
    if (id.IsOk()  && m_pTree->GetItemParent(id) == m_pTree->GetRootItem())
    {
        menu.Append(idEditWatch, _("&Edit watch"));
        menu.Append(idDeleteWatch, _("&Delete watch"));
	}

	PopupMenu(&menu, pt);
}

// events

void DebuggerTree::OnTreeRightClick(wxTreeEvent& event)
{
	m_pTree->SelectItem(event.GetItem());
    ShowMenu(event.GetItem(), event.GetPoint());
}

void DebuggerTree::OnRightClick(wxCommandEvent& event)
{
    wxTreeItemId tmp; // dummy var for next call
    // get right-click point
    wxPoint pt = wxGetMousePosition();
    pt = m_pTree->ScreenToClient(pt);

    ShowMenu(tmp, pt);
}

void DebuggerTree::OnAddWatch(wxCommandEvent& event)
{
	wxString w = wxGetTextFromUser(_("Add watch"), _("Enter the variable name to watch:"));
	if (!w.IsEmpty())
	{
		AddWatch(w);
	}
}

void DebuggerTree::OnEditWatch(wxCommandEvent& event)
{
	wxString item = m_pTree->GetItemText(m_pTree->GetSelection());
	wxString w = wxGetTextFromUser(_("Edit watch"), _("Edit the variable name:"), item);
	if (!w.IsEmpty())
	{
		DeleteWatch(item);
		AddWatch(w);
	}
}

void DebuggerTree::OnDeleteWatch(wxCommandEvent& event)
{
	wxString item = m_pTree->GetItemText(m_pTree->GetSelection());
	if (wxMessageBox(_("Delete this watched variable?"), _("Confirm"), wxYES_NO) == wxYES)
	{
		DeleteWatch(item);
	}
}


syntax highlighted by Code2HTML, v. 0.9.1