#include <sdk.h>
#include "todolistview.h"
#include <wx/intl.h>
#include <manager.h>
#include <configmanager.h>
#include <editormanager.h>
#include <messagemanager.h>
#include <projectmanager.h>
#include <cbeditor.h>
#include <cbproject.h>
#include <wx/sizer.h>
#include <wx/stattext.h>
#include <wx/file.h>
#include <wx/utils.h>

int idSource = wxNewId();
int idUser = wxNewId();
int idRefresh = wxNewId();

BEGIN_EVENT_TABLE(ToDoListView, SimpleListLog)
	EVT_COMBOBOX(idSource, ToDoListView::OnComboChange)
	EVT_COMBOBOX(idUser, ToDoListView::OnComboChange)
	EVT_BUTTON(idRefresh, ToDoListView::OnRefresh)
END_EVENT_TABLE()

ToDoListView::ToDoListView(wxNotebook* parent, const wxString& title, int numCols, int widths[], const wxArrayString& titles, const wxArrayString& m_Types)
    : SimpleListLog(parent, title, numCols, widths, titles),
    m_pSource(0L),
    m_pUser(0L),
    m_Types(m_Types)
{
	//ctor
    int id = m_pList->GetId();
    Connect(id, -1, wxEVT_COMMAND_LIST_ITEM_SELECTED,
            (wxObjectEventFunction) (wxEventFunction) (wxCommandEventFunction)
            &ToDoListView::OnListItemSelected);

	wxSizer* bs = m_pList->GetContainingSizer();
	if (bs)
	{
        wxArrayString choices;
        choices.Add(_("Current file"));
        choices.Add(_("Open files"));
        choices.Add(_("All project files"));
        wxBoxSizer* hbs = new wxBoxSizer(wxHORIZONTAL);

        hbs->Add(new wxStaticText(this, wxID_ANY, _("Scope:")), 0, wxTOP, 4);

        m_pSource = new wxComboBox(this, idSource, wxEmptyString, wxDefaultPosition, wxDefaultSize, 3, &choices[0], wxCB_READONLY);
        m_pSource->SetSelection(0);
        hbs->Add(m_pSource, 0, wxLEFT | wxRIGHT, 8);

        hbs->Add(new wxStaticText(this, wxID_ANY, _("User:")), 0, wxTOP, 4);

        m_pUser = new wxComboBox(this, idUser, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0, 0L, wxCB_READONLY);
        m_pUser->Append(_("<All users>"));
        m_pUser->SetSelection(0);
        hbs->Add(m_pUser, 0, wxLEFT, 8);

        m_pRefresh = new wxButton(this, idRefresh, _("Refresh list"));
        hbs->Add(m_pRefresh, 0, wxLEFT, 8);

        bs->Add(hbs, 0, wxGROW | wxALL, 8);
	}
}

ToDoListView::~ToDoListView()
{
	//dtor
}

void ToDoListView::LoadUsers()
{
	wxString oldStr = m_pUser->GetStringSelection();
	m_pUser->Clear();
	m_pUser->Append(_("<All users>"));

	// loop through all todos and add distinct users
	for (unsigned int i = 0; i < m_Items.GetCount(); ++i)
	{
		wxString user = m_Items[i].user;
		if (!user.IsEmpty())
		{
			if (m_pUser->FindString(user) == wxNOT_FOUND)
				m_pUser->Append(user);
		}
	}
	int old = m_pUser->FindString(oldStr);
	if (old != wxNOT_FOUND)
		m_pUser->SetSelection(old);
	else
		m_pUser->SetSelection(0); // all users
}

void ToDoListView::FillList()
{
	LoadUsers();
	GetListControl()->Freeze();
	Clear();

	for (unsigned int i = 0; i < m_Items.GetCount(); ++i)
	{
		const ToDoItem& item = m_Items[i];
		if (m_pUser->GetSelection() == 0 || // all users
			m_pUser->GetStringSelection().Matches(item.user)) // or matches user
		{
			int idx = GetListControl()->InsertItem(GetListControl()->GetItemCount(), item.type);
			GetListControl()->SetItem(idx, 1, item.text);
			GetListControl()->SetItem(idx, 2, item.user);
			GetListControl()->SetItem(idx, 3, item.priorityStr);
			GetListControl()->SetItem(idx, 4, item.lineStr);
			GetListControl()->SetItem(idx, 5, item.filename);
		}
	}
	GetListControl()->Thaw();
}

void ToDoListView::Parse()
{
//    wxBusyCursor busy;

	// based on user prefs, parse files for todo items
	m_Items.Clear();

	switch (m_pSource->GetSelection())
	{
		case 0: // current file only
		{
			// this is the easiest selection ;)
            cbEditor* ed = Manager::Get()->GetEditorManager()->GetBuiltinEditor(Manager::Get()->GetEditorManager()->GetActiveEditor());
			ParseEditor(ed);
			break;
		}
		case 1: // open files
		{
			// easy too; parse all open editor files...
			for (int i = 0; i < Manager::Get()->GetEditorManager()->GetEditorsCount(); ++i)
			{
                cbEditor* ed = Manager::Get()->GetEditorManager()->GetBuiltinEditor(Manager::Get()->GetEditorManager()->GetEditor(i));
				ParseEditor(ed);
			}
			break;
		}
		case 2: // all project files
		{
			// loop all project files
			// but be aware: if a file is opened, use the open file because
			// it might not be the same on the disk...
			cbProject* prj = Manager::Get()->GetProjectManager()->GetActiveProject();
			if (!prj)
				return;
			for (int i = 0; i < prj->GetFilesCount(); ++i)
			{
				ProjectFile* pf = prj->GetFile(i);
				wxString filename = pf->file.GetFullPath();
                cbEditor* ed = Manager::Get()->GetEditorManager()->IsBuiltinOpen(filename);
				if (ed)
					ParseEditor(ed);
				else
					ParseFile(filename);
			}
			break;
		}
	}
	FillList();
}

int ToDoListView::CalculateLineNumber(const wxString& buffer, int upTo)
{
	int line = 0;
	for (int i = 0; i < upTo; ++i)
	{
		if (buffer.GetChar(i) == _T('\r') && buffer.GetChar(i + 1) == _T('\n')) // CR+LF
			continue; // we 'll count on \n (next loop)
		else if (buffer.GetChar(i) == _T('\r') || // CR only
                buffer.GetChar(i) == _T('\n')) // lf only
			++line;
	}
	return line;
}

void ToDoListView::ParseEditor(cbEditor* pEditor)
{
	if (pEditor)
		ParseBuffer(pEditor->GetControl()->GetText(), pEditor->GetFilename());
}

void ToDoListView::ParseFile(const wxString& filename)
{
    wxLogNull ln;

	if (!wxFileExists(filename))
		return;

	// open file
	wxString st;
	wxFile file(filename);
    if(!cbRead(file,st))
        return;
	ParseBuffer(st, filename);
}

void ToDoListView::ParseBuffer(const wxString& buffer, const wxString& filename)
{
	// this is the actual workhorse...

	// ok, we look for two basic kinds of todo entries in the text
	// our version...
    // TODO (mandrav#0#): Implement code to do this and the other...
	// and a generic version...
    // TODO: Implement code to do this and the other...

	for (unsigned int i = 0; i < m_Types.GetCount(); ++i)
	{
//Manager::Get()->GetMessageManager()->DebugLog("Looking for %s", m_Types[i].c_str());
		int pos = buffer.find(m_Types[i], 0);

		while (pos > 0)
		{
			// ok, start parsing now...
			// keep a temp copy of pos to work with
			int idx = pos;
			bool isValid = false; // found it in a comment?
			bool isC = false; // C or C++ style comment?

//#warning TODO (mandrav#1#): Make viewtododlg understand and display todo notes that are compiler warnings/errors...

			// first check what type of comment we have
			wxString allowedChars = _T(" \t/*");
			wxChar lastChar = _T('\0');
			while (idx >= 0)
			{
				wxChar c = buffer.GetChar(--idx);
				if ((int)allowedChars.Index(c) != wxNOT_FOUND)
				{
					if (c == _T('/') && (lastChar == _T('/') || lastChar == _T('*')))
					{
						isValid = true;
						isC = lastChar == _T('*');
						break;
					}
				}
				else
					break;
				lastChar = c;
			}

//Manager::Get()->GetMessageManager()->DebugLog("Found %s %s style %s at %d", isValid ? "valid" : "invalid", isC ? "C" : "C++", m_Types[i].c_str(), pos);
			if (isValid)
			{
				ToDoItem item;
				item.type = m_Types[i];
				item.filename = filename;

				idx = pos + m_Types[i].Length();
				wxChar c = _T('\0');

//Manager::Get()->GetMessageManager()->DebugLog("1");
				// skip to next non-blank char
				while (idx < (int)buffer.Length())
				{
					c = buffer.GetChar(idx);
					if (c != _T(' ') && c != _T('\t'))
						break;
					++idx;
				}
//Manager::Get()->GetMessageManager()->DebugLog("2");
				// is it ours or generic todo?
				if (c == _T('('))
				{
					// it's ours, find user and/or priority
					++idx; // skip (
					while (idx < (int)buffer.Length())
					{
						wxChar c1 = buffer.GetChar(idx);
						if (c1 != _T('#') && c1 != _T(')'))
						{
							// a little logic doesn't hurt ;)

							if (c1 == _T(' ') || c1 == _T('\t') || c1 == _T('\r') || c1 == _T('\n'))
							{
								// allow one consecutive space
								if (item.user.Last() != _T(' '))
									item.user << _T(' ');
							}
							else
								item.user << c1;
						}
						else if (c1 == _T('#'))
						{
							// look for priority
							c1 = buffer.GetChar(++idx);
							allowedChars = _T("0123456789");
							if ((int)allowedChars.Index(c1) != wxNOT_FOUND)
								item.priorityStr << c1;
							// skip to start of text
							while (idx < (int)buffer.Length())
							{
								wxChar c2 = buffer.GetChar(idx++);
								if (c2 == _T(')') || c2 == _T('\r') || c2 == _T('\n'))
									break;
							}
							break;
						}
						else
							break;
						++idx;
					}
				}
//Manager::Get()->GetMessageManager()->DebugLog("3");
				// ok, we 've reached the actual todo text :)
				// take everything up to the end of line or end of comment (if isC)
				wxChar lastChar = _T('\0');
				if (buffer.GetChar(idx) == _T(':'))
					++idx;
				while (idx < (int)buffer.Length())
				{
					wxChar c1 = buffer.GetChar(idx++);
					if (c1 == _T('\r') || c1 == _T('\n'))
						break;
					if (isC && c1 == _T('/') && lastChar == _T('*'))
					{
                        // remove last char '*'
                        item.text.RemoveLast();
						break;
                    }
					if (c1 == _T(' ') || c1 == _T('\t'))
					{
						// allow one consecutive space
						if (item.text.Last() != _T(' '))
							item.text << _T(' ');
					}
					else
						item.text << c1;
					lastChar = c1;
				}
//Manager::Get()->GetMessageManager()->DebugLog("4");
				// do some clean-up
				item.text.Trim();
				item.text.Trim(false);
				item.user.Trim();
				item.user.Trim(false);
				item.line = CalculateLineNumber(buffer, pos);
				item.lineStr << item.line + 1; // 1-based line number for list
				m_Items.Add(item);
			}
			else
				break; // invalid style...

			pos = buffer.find(m_Types[i], idx);
		}
//		Manager::Get()->GetMessageManager()->DebugLog("Found it at %d", pos);
	}
}

void ToDoListView::OnComboChange(wxCommandEvent& event)
{
    Parse();
}

void ToDoListView::OnListItemSelected(wxListEvent& event)
{
    if (event.GetIndex() == -1)
        return;
    wxString file = m_Items[event.GetIndex()].filename;
    long int line = m_Items[event.GetIndex()].line;

    if (file.IsEmpty() || line <= 0)
        return;

	// jump to file/line selected
	cbEditor* ed = Manager::Get()->GetEditorManager()->Open(file);
	if (ed)
	{
		ed->GetControl()->GotoLine(line);
		ed->Activate();
    }
}

void ToDoListView::OnRefresh(wxCommandEvent& event)
{
    Parse();
}


syntax highlighted by Code2HTML, v. 0.9.1