/*
* This file is part of Code::Blocks Studio, an open-source cross-platform IDE
* Copyright (C) 2003  Yiannis An. Mandravellos
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*
* Contact e-mail: Yiannis An. Mandravellos <mandrav@codeblocks.org>
* Program URL   : http://www.codeblocks.org
*
* $Id: cclist.cpp,v 1.2.2.1 2005/10/25 07:59:01 mandrav Exp $
* $Date: 2005/10/25 07:59:01 $
*/

#include <sdk.h>
#include "cclist.h"
#include <wx/sizer.h>
#include <configmanager.h>
#include <manager.h>

const wxEventType csdEVT_CCLIST_CODECOMPLETE = wxNewEventType();

static CCList* g_CCList = 0L;

int idList = wxNewId();

BEGIN_EVENT_TABLE(CCList, wxFrame)
	EVT_ACTIVATE(CCList::OnActivate)
	EVT_SIZE(CCList::OnSize)
	EVT_KEY_DOWN(CCList::OnKeyDown)
	EVT_GRID_CELL_LEFT_CLICK(CCList::OnLeftClick)
	EVT_GRID_CELL_LEFT_DCLICK(CCList::OnLeftDClick)
	EVT_GRID_SELECT_CELL(CCList::OnCellChanged)
END_EVENT_TABLE()

CCList* CCList::Get(wxEvtHandler* parent, cbStyledTextCtrl* editor, Parser* parser)
{
	if (!g_CCList)
		g_CCList = new CCList(parent, editor, parser);
	return g_CCList;
}

void CCList::Free()
{
	if (g_CCList)
	{
		delete g_CCList;
		g_CCList = 0L;
	}
}

CCList::CCList(wxEvtHandler* parent, cbStyledTextCtrl* editor, Parser* parser)
	: wxFrame(editor, -1, _T("CC"), wxDefaultPosition, wxDefaultSize,
			wxFRAME_NO_TASKBAR | wxRESIZE_BORDER | wxNO_FULL_REPAINT_ON_RESIZE),
	m_pParent(parent),
	m_pEditor(editor),
	m_pParser(parser),
	m_pList(0L),
	m_IsCtrlPressed(false)
{
	m_StartPos = m_pEditor->GetCurrentPos();
	PositionMe();
	int start = m_pEditor->WordStartPosition(m_StartPos, true);
	wxString prefix = m_pEditor->GetTextRange(start, m_StartPos);

	m_pList = new CCListCtrl(this, idList, m_pParser, prefix);
	SendSizeEvent();
	m_pList->SetFocus();
}

CCList::~CCList()
{
	ConfigManager::Get()->Write(_T("/code_completion/size/width"), GetSize().GetWidth());
	ConfigManager::Get()->Write(_T("/code_completion/size/height"), GetSize().GetHeight());
	m_pEditor->SetFocus();
	delete m_pList;
	g_CCList = 0L;
}

void CCList::PositionMe()
{
	wxPoint pt = m_pEditor->PointFromPosition(m_StartPos);
	pt = m_pEditor->ClientToScreen(pt);
	int lineHeight = m_pEditor->TextHeight(m_pEditor->GetCurrentLine());
	pt.y += lineHeight;

	int w = ConfigManager::Get()->Read(_T("/code_completion/size/width"), 320);
	int h = ConfigManager::Get()->Read(_T("/code_completion/size/height"), 160);
	int screenW = wxSystemSettings::GetMetric(wxSYS_SCREEN_X);
	int screenH = wxSystemSettings::GetMetric(wxSYS_SCREEN_Y);
	// sanity check
	if (w > screenW)
		w = screenW;
	if (h > screenH)
		h = screenH;

	// now we 're where we want to be, but check that the whole window is visible...
	// the main goal here is that the caret *should* be visible...

	// for the horizontal axis, easy stuff
	if (pt.x + w > screenW)
		pt.x = screenW - w;

	// for the vertical axis, more work has to be done...
	if (pt.y + h > screenH)
	{
		// it doesn't fit to the bottom of the screen
		// check if it fits to the top
		if (h < pt.y)
			pt.y -= h + lineHeight; // fits
		else
		{
			// we have to shrink the height...
			// determine if pt.y is closer to top or bottom
			if (pt.y <= screenH / 2)
				h = screenH = pt.y; // to top
			else
			{
				h = pt.y - lineHeight; // to bottom
				pt.y = 0;
			}
		}
	}
	// we should be OK now
	SetSize(pt.x, pt.y, w, h);
}

void CCList::SelectCurrent(wxChar ch)
{
	Token* token = m_pList->GetSelectedToken();
	if (token)
	{
		int start = m_pEditor->WordStartPosition(m_pEditor->GetCurrentPos(), true);
		int end = m_pEditor->WordEndPosition(m_pEditor->GetCurrentPos(), true);
		m_pEditor->SetTargetStart(start);
		m_pEditor->SetTargetEnd(end);

		// "smart" support for functions...
		int offset = 0;
		bool funcHasArgs = false;
		bool codeCompleteAgain = false;
		wxString replace = token->m_Name;
		if (token->m_TokenKind == tkFunction)
		{
			// if token is a function, add ()
			replace << _T("()");
			funcHasArgs = !token->m_Args.Matches(_T("()")) && !token->m_Args.Matches(_T("(void)"));
			if (funcHasArgs)
				offset = 1; // adjust cursor position inside parentheses, only if func takes args
		}

		if (ch == '-' || ch == '>')
		{
			replace << _T("->");
			codeCompleteAgain = true;
			if (funcHasArgs)
				offset += 2; // adjust cursor position inside parentheses, only if func takes args
		}
		else if (ch == '.')
		{
			replace << ch;
			codeCompleteAgain = true;
			if (funcHasArgs)
				offset += 1; // adjust cursor position inside parentheses, only if func takes args
		}
		else if (ch == ';')
		{
			replace << ch;
			if (funcHasArgs)
				offset += 1; // adjust cursor position inside parentheses, only if func takes args
		}

		int len = m_pEditor->ReplaceTarget(replace);
		m_pEditor->GotoPos(m_pEditor->GetCurrentPos() + len - offset);
		if (codeCompleteAgain)
		{
			wxNotifyEvent event(csdEVT_CCLIST_CODECOMPLETE);
			wxPostEvent(m_pParent, event);
		}
	}
	Destroy();
}

void CCList::OnActivate(wxActivateEvent& event)
{
	if (!event.GetActive())
		Destroy();
}

void CCList::OnSize(wxSizeEvent& event)
{
	if (m_pList)
	{
		m_pList->SetSize(GetClientSize());
		m_pList->SetColSize(0, GetClientSize().GetWidth() - 20);
	}
}

void CCList::OnLeftClick(wxGridEvent& event)
{
	event.Skip(); // let the event proceed, anyway
	if (!m_IsCtrlPressed)
		return;
	Token* token = m_pList->GetTokenAt(event.GetRow());
	if (token)
	{
		wxString msg;
		msg << _T("\"") << token->m_Name << _T("\" breaks down to:\n\n");
		msg << _T("Kind: ") << token->GetTokenKindString() << _T('\n');
		msg << _T("Namespace: ") << token->GetNamespace() << _T('\n');
		msg << _T("Name: ") << token->m_Name << _T('\n');
		msg << _T("Arguments: ") << token->m_Args << _T('\n');
		msg << _T("Return value: ") << token->m_Type << _T('\n');
		msg << _T("Actual return value: ") << token->m_ActualType << _T('\n');
		msg << _T("Scope: ") << token->GetTokenScopeString() << _T("\n\n");
		msg << _T("and is met in ") << token->m_Filename << _T(", at line ") << token->m_Line;
		wxMessageBox(msg);
	}
}

void CCList::OnLeftDClick(wxGridEvent& event)
{
	SelectCurrent();
	Destroy();
}

void CCList::OnCellChanged(wxGridEvent& event)
{
	//Manager::Get()->GetMessageManager()->DebugLog("OnCellChanged");
	event.Skip(); // let the event proceed, anyway
#if 0
	if (!m_pList)
		return;
	Token* token = m_pList->GetTokenAt(event.GetRow());
	if (token)
	{
		wxString msg;
		msg << token->m_Filename << " : " << token->m_Line;
		SetStatusText(msg);
	}
#endif
}

void CCList::OnKeyDown(wxKeyEvent& event)
{
	// unfortunately, for some odd reason, we never get wxGrid's (m_pList)
	// OnChar event.
	// So we have to process all key handling here (with raw keycodes...)
	char c = (char)event.GetKeyCode();
	m_IsCtrlPressed = event.ControlDown();
	//Manager::Get()->GetMessageManager()->DebugLog("OnKeyDown: %c (%d)", c, c);
	switch (event.GetKeyCode())
	{
		case WXK_ESCAPE:
		case WXK_LEFT:
		case WXK_RIGHT:
		{
			event.Skip();
			Destroy();
			break;
		}

		case ';':
		{
			if (!event.ShiftDown())
				SelectCurrent(c);
			break;
		}

		case '.':
		{
			if (event.ShiftDown())
				SelectCurrent('>');
			else
				SelectCurrent('.');
			break;
		}

		case WXK_RETURN:
		{
			SelectCurrent();
			break;
		}

		case WXK_BACK:
		{
			if (m_pEditor->GetCurrentPos() <= m_StartPos)
				Destroy();
			else
				m_pList->RemoveLastChar();
			break;
		}

		case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':	case 'G':
		case 'H': case 'I':	case 'J': case 'K':	case 'L': case 'M':	case 'N':
		case 'O': case 'P':	case 'Q': case 'R':	case 'S': case 'T':	case 'U':
		case 'V': case 'W':	case 'X': case 'Y':	case 'Z':
		{
			if (!event.ShiftDown())
				c += 32;
			m_pList->AddChar(c);
			break;
		}

		case '~':
		{
			if (event.ShiftDown())
				m_pList->AddChar(c);
			break;
		}

		case '-':
		{
			if (event.ShiftDown())
				m_pList->AddChar('_');
			else
				SelectCurrent(c);
			break;
		}

		case '0': case '1': case '2': case '3':
		case '4': case '5': case '6': case '7':
		case '8':
		{
			if (!event.ShiftDown())
				m_pList->AddChar(c);
			break;
		}

		case '9':
		{
			if (!event.ShiftDown())
				m_pList->AddChar(c);
			else
				SelectCurrent('(');
			break;
		}

		default:
			event.Skip();
			break;
	}
}


syntax highlighted by Code2HTML, v. 0.9.1