#include <precomp.h>
#include "editwnd.h"

#include <tataki/canvas/canvas.h>
#include <api/wnd/notifmsg.h>

#include <bfc/assert.h>

#define ID_EDITCHILD 12

enum { IDLETIMER = 8, DELETETIMER = 10 };
#define IDLETIME 350 // comprimises suck ;)


#if UTF8
#ifdef WANT_UTF8_WARNINGS
#pragma CHAT("mig", "all", "UTF8 is enabled in editwnd.cpp -- Things might be screwy till it's all debugged?")
#endif
# include <bfc/string/encodedstr.h>
#endif

#ifdef WIN32
#include <commctrl.h>
#endif


#ifdef WIN32
static LRESULT CALLBACK static_editWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	EditWnd *editwnd = (EditWnd *)GetWindowLongPtrW(hWnd, GWLP_USERDATA);
	if (editwnd == NULL) return DefWindowProcW(hWnd, uMsg, wParam, lParam);
	return editwnd->editWndProc(hWnd, uMsg, wParam, lParam);
}
#endif

EditWnd::EditWnd(wchar_t *buffer, int buflen)
{
	wantfocus = 1;
	nextenterfaked = 0;
	idleenabled = 1;
	beforefirstresize = 1;
	editWnd = NULL;
	prevWndProc = NULL;
	setBuffer(buffer, buflen);
	maxlen = 0;
	retcode = EDITWND_RETURN_NOTHING;
	modal = 0;
	autoenter = 0;
	autoselect = 0;
	outbuf = NULL;
	//  bordered = 0;
	idletimelen = IDLETIME;
	multiline = 0;
	readonly = 0;
	password = 0;
	autohscroll = 1;
	autovscroll = 1;
	vscroll = 0;
#ifdef WIN32
	oldbrush = NULL;
#endif
#ifdef LINUX
	selstart = selend = 0;
	cursorpos = 0;
	selectmode = 0;
	viewstart = 0;
#endif
#ifdef WASABI_EDITWND_LISTCOLORS
	if (WASABI_API_SKIN->skin_getColorElementRef(L"wasabi.list.text"))
		textcolor = L"wasabi.list.text";
	else
		textcolor = L"wasabi.edit.text";
	if (WASABI_API_SKIN->skin_getColorElementRef(L"wasabi.list.background"))
		backgroundcolor = L"wasabi.list.background";
	else
		textcolor.setColor(WASABI_API_SKIN->skin_getBitmapColor(L"wasabi.list.background"));
#else
	backgroundcolor = "wasabi.edit.background";
	textcolor = "wasabi.edit.text";
#endif
	selectioncolor = L"wasabi.edit.selection";
	setVirtual(0);
}

EditWnd::~EditWnd()
{
	killTimer(IDLETIMER);
#ifdef WIN32
	if (oldbrush != NULL)
		DeleteObject(oldbrush);
	oldbrush = NULL;
	if (editWnd != NULL)
	{
		SetWindowLong(editWnd, GWLP_USERDATA, (LONG_PTR)0);
		SetWindowLongPtrW(editWnd, GWLP_WNDPROC, (LONG_PTR)prevWndProc);
		DestroyWindow(editWnd);
	}
#endif
	notifyParent(ChildNotify::RETURN_CODE, retcode);
}

int EditWnd::onInit()
{
	EDITWND_PARENT::onInit();

#ifdef WIN32
	RECT r = clientRect();

		editWnd = CreateWindowW(L"EDIT", NULL,
		                       WS_CHILD
		                       | (autohscroll ? ES_AUTOHSCROLL : 0)
		                       | (readonly ? ES_READONLY : 0)
		                       | (multiline ? ES_MULTILINE : 0)
		                       | (password ? ES_PASSWORD : 0)
		                       | (autovscroll ? ES_AUTOVSCROLL : 0)
		                       | (vscroll ? WS_VSCROLL : 0),
		                       r.left, r.top, r.right - r.left, r.bottom - r.top,
		                       gethWnd(), (HMENU)ID_EDITCHILD,
		                       getOsModuleHandle(), NULL);
		ASSERT(editWnd != NULL);

	if ((maxlen != 0) && (outbuf != NULL))
	{
		setBuffer(outbuf, maxlen);
	}

	// stash a pointer to us
	SetWindowLongPtrW(editWnd, GWLP_USERDATA, (LONG_PTR)this);
	// subclass the edit control -- either by 8 or by 16

		prevWndProc = (WNDPROC)SetWindowLongPtrW(editWnd, GWLP_WNDPROC, (LONG_PTR)static_editWndProc);


	SendMessageW(editWnd, WM_SETFONT, (WPARAM)GetStockObject(ANSI_VAR_FONT), FALSE);
	ShowWindow(editWnd, !getStartHidden() ? SW_NORMAL : SW_HIDE);
#endif

	return 1;
}

void EditWnd::onSetVisible(int show)
{
	EDITWND_PARENT::onSetVisible(show);
	if (editWnd == NULL) return ;
#ifdef WIN32
	ShowWindow(editWnd, show ? SW_NORMAL : SW_HIDE);
#endif
}

int EditWnd::onPaint(Canvas *canvas)
{
	//  if (!bordered) return EDITWND_PARENT::onPaint(canvas);

	PaintCanvas paintcanvas;
	if (canvas == NULL)
	{
		if (!paintcanvas.beginPaint(this)) return 0;
		canvas = &paintcanvas;
	}
	EDITWND_PARENT::onPaint(canvas);

	RECT r;
	getClientRect(&r);
	canvas->fillRect(&r, backgroundcolor);	//SKIN

#ifdef LINUX
	char *str = STRDUP((const char *)inbuf + viewstart);
	canvas->setTextColor(textcolor);
	canvas->setTextSize(r.bottom - r.top);
	canvas->setTextOpaque(FALSE);
	char save;
	if (selstart != selend)
	{
		RECT selrect = r;
		int start = MAX(MIN(selstart, selend) - viewstart, 0);
		int end = MAX(MAX(selstart, selend) - viewstart, 0);

		save = str[ start ];
		str[start] = '\0';
		selrect.left = r.left + canvas->getTextWidth(str);
		str[start] = save;

		save = str[ end ];
		str[end] = '\0';
		selrect.right = r.left + canvas->getTextWidth(str);
		str[end] = save;

		canvas->fillRect(&selrect, selectioncolor);
	}

	save = str[cursorpos - viewstart];
	str[cursorpos - viewstart] = '\0';
	RECT cursor = r;
	cursor.left = cursor.right = r.left + canvas->getTextWidth(str);
	str[cursorpos - viewstart] = save;
	canvas->drawRect(&cursor, TRUE, 0xffffff);

	canvas->textOut(r.left, r.top, r.right - r.left, r.bottom - r.top, str);

	FREE(str);
#endif

	return 1;
}

int EditWnd::onResize()
{
	EDITWND_PARENT::onResize();
#ifdef WIN32
	RECT r = clientRect();
	if (1 /*bordered*/)
	{
		r.top++;
		r.bottom--;
		r.left++;
		r.right--;
	}
	MoveWindow(editWnd, r.left, r.top, r.right - r.left, r.bottom - r.top, TRUE);

	if (beforefirstresize)
	{
		ShowWindow(editWnd, SW_NORMAL); beforefirstresize = 0;
		if (modal)
		{
			SetFocus(editWnd);
			if (getAutoSelect())
				SendMessageW(editWnd, EM_SETSEL, 0, -1);
		}
	}
#endif

	return TRUE;
}

#ifdef WIN32
LRESULT EditWnd::wndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	switch (uMsg)
	{
	case WM_CTLCOLOREDIT:
		{
			HDC hdc = (HDC)wParam;
			SetTextColor(hdc, textcolor);
			SetBkColor(hdc, backgroundcolor);
			if (oldbrush != NULL)
			{
				DeleteObject(oldbrush);
				oldbrush = NULL;
			}
			oldbrush = CreateSolidBrush(backgroundcolor);
			return (LRESULT)oldbrush;
		}

	case WM_MOUSEACTIVATE:
		WASABI_API_WND->popupexit_check(this);
		break;

	case WM_COMMAND:
		{
			switch (HIWORD(wParam))
			{
			case EN_CHANGE:
				{
					if (maxlen > 0)
					{
						GetWindowTextW(editWnd, outbuf, maxlen);
						onEditUpdate();
					}
				}
				break;
			case EN_SETFOCUS:
				if (getAutoSelect())
					SendMessageW(editWnd, EM_SETSEL, (WPARAM)0, (LPARAM) - 1);
				break;
			case EN_KILLFOCUS:
				onLoseFocus();
				break;
			}
		}
		break;
	}

	return EDITWND_PARENT::wndProc(hWnd, uMsg, wParam, lParam);
}
#endif

void EditWnd::setBuffer(wchar_t *buffer, int len)
{
#ifdef LINUX
	if (buffer == NULL || len <= 1)
	{
		inbuf = "";
		return ;
	}
#endif
	if (buffer == NULL || len <= 1) return ;
	ASSERT(len > 1);
	ASSERT(len < 0x7ffe);
	ASSERT((int)wcslen(buffer) <= len);

#ifdef WIN32
#define USE_INTERNAL_BUFFER 0

#if USE_INTERNAL_BUFFER
	buffer8.setSize(len + 1);
	outbuf = buffer8.getMemory();
	if (len)
	{
		STRNCPY(outbuf, buffer, len);
	}
	outbuf[len] = 0;
#else
	outbuf = buffer;
#endif

	if (editWnd != NULL)
	{
				SetWindowTextW(editWnd, buffer);

		// This is going to be problematic.  This is where utf8 sucks.
		// Just how many characters CAN we save in our buffer, eh?
		// (shrug) Oh well.  Can't be helped.   At most this many.
		SendMessageW(editWnd, EM_LIMITTEXT, (WPARAM)len - 1, (LPARAM)0);
		// hooray for halcyon7

		/*    if (getAutoSelect()) {
		      SetFocus(editWnd);
		      SendMessageW(editWnd, EM_SETSEL, (WPARAM)0, (LPARAM)-1);
		    }*/
	}

	maxlen = len;
#else
	outbuf = buffer;
	maxlen = len;
	inbuf = buffer;
	cursorpos = len;
	invalidate();
#endif
}

void EditWnd::selectAll()
{
#ifdef WIN32
	PostMessage(editWnd, EM_SETSEL, 0, -1);
#else
	selstart = 0; selend = inbuf.len();
#endif
}

void EditWnd::enter()
{
	onEnter();
}

void EditWnd::getBuffer(wchar_t *buf, int _len)
{
	if (_len > maxlen) _len = maxlen;
	//  SendMessageW(editWnd, WM_GETTEXT, (WPARAM)_len, (LPARAM)buf);
	WCSCPYN(buf, outbuf, _len);
}

void EditWnd::setModal(int _modal)
{
	modal = _modal;
}

void setBorder(int border)
{
	//  bordered = border;
}

int EditWnd::isEditorKey(int vk)
{
	if (vk >= VK_F1) return 0;
	if ((vk == VK_UP || vk == VK_DOWN) || ((Std::keyDown(VK_CONTROL) || Std::keyDown(VK_MENU)) && (vk == VK_LEFT || vk == VK_RIGHT)))
		return 0;
	if (vk == VK_RETURN && Std::keyDown(VK_CONTROL)) return 0;
	if (vk == VK_CONTROL || vk == VK_MENU) return 0;
	return 1;
}

LRESULT EditWnd::editWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	switch (uMsg)
	{
	case WM_KEYDOWN:
		if (!isEditorKey((int)wParam) && !onKeyDown((int)wParam))
		{
#ifdef WASABI_COMPILE_WND
			WASABI_API_WND->forwardOnKeyDown(this, (int) wParam, (int)lParam);
#endif

		}
		break;
	case WM_KEYUP:
		if (!isEditorKey((int)wParam) && !onKeyUp((int)wParam))
		{
#ifdef WASABI_COMPILE_WND
			WASABI_API_WND->forwardOnKeyUp(this, (int) wParam, (int)lParam);
#endif

		}
		break;
	case WM_CHAR:
		if (!(wParam == VK_RETURN && nextenterfaked && !autoenter))
		{
			notifyParent(ChildNotify::EDITWND_KEY_PRESSED, wParam);
			onChar((TCHAR)wParam);
		}
		if (wParam == VK_RETURN)
		{
			if (!(nextenterfaked && !autoenter))
				if (onEnter()) return 0;
			nextenterfaked = 0;
			return 0;
		}
		else if (wParam == VK_ESCAPE)
		{
			if (onAbort()) return 0;
		}
		else if (wParam == VK_TAB && multiline)
		{
			return 0;
		}
		break;
	case WM_SETFOCUS:
		onSetRootFocus(this);
		// fall thru
	case WM_KILLFOCUS:
		invalidate();
		break;
	}
#ifdef WIN32
	return CallWindowProc(prevWndProc, hWnd, uMsg, wParam, lParam);
#else
	DebugString("portme -- EditWnd::editWndProc\n");
	return 0;
#endif
}

void EditWnd::timerCallback(int id)
{
	switch (id)
	{
	case IDLETIMER:
		killTimer(IDLETIMER);
		if (idleenabled) onIdleEditUpdate();
		break;
	case DELETETIMER:
		killTimer(DELETETIMER);
		delete this;
		break;
	default:
		EDITWND_PARENT::timerCallback(id);
	}
}

void EditWnd::onEditUpdate()
{
#ifdef LINUX
	STRNCPY(outbuf, inbuf, maxlen);
	outbuf[maxlen] = '\0';

	RECT r;
	getClientRect(&r);

	SysCanvas sysc;
	sysc.setTextSize(r.bottom - r.top);
	sysc.getTextWidth(inbuf);

	char *str = STRDUP(inbuf);

	if (cursorpos < viewstart)
		viewstart = cursorpos;

	char save = str[cursorpos];
	str[cursorpos] = '\0';
	while (sysc.getTextWidth(str + viewstart) > r.right - r.left)
	{
		viewstart++;
	}
	str[cursorpos] = save;

	invalidate();
#endif
	killTimer(IDLETIMER);
	setTimer(IDLETIMER, idletimelen);
	notifyParent(ChildNotify::EDITWND_DATA_MODIFIED);
}

void EditWnd::onIdleEditUpdate()
{
	notifyParent(ChildNotify::EDITWND_DATA_MODIFIED_ONIDLE);
}

int EditWnd::onEnter()
{
	notifyParent(ChildNotify::EDITWND_ENTER_PRESSED);
	if (modal)
	{
		retcode = EDITWND_RETURN_OK;
		delete this;
		//CUT    setTimer(DELETETIMER, 1);
		return 1;
	}
	return 0;
}

int EditWnd::onAbort()
{
	notifyParent(ChildNotify::EDITWND_CANCEL_PRESSED);
	if (modal)
	{
		retcode = EDITWND_RETURN_CANCEL;
		delete this;
		//CUT    setTimer(DELETETIMER, 1);
		return 1;
	}
	return 0;
}

int EditWnd::onLoseFocus()
{	// fake an onEnter()
#ifdef WIN32
	if (autoenter)
	{
		nextenterfaked = 1;
		PostMessage(editWnd, WM_CHAR, VK_RETURN, 0);
	}
#else
	invalidate();
	selstart = selend = 0;
#endif
	return 0;
}

void EditWnd::setAutoEnter(int a)
{
	autoenter = a;
}

void EditWnd::setAutoSelect(int a)
{
	autoselect = a;
};

void EditWnd::setIdleTimerLen(int ms)
{
	if (ms < 0) ms = 0;
	idletimelen = ms;
}

int EditWnd::getTextLength()
{ // TOTALLY NONPORTABLE AND TOTALLY DIRTY
#ifdef WIN32
	HFONT font = (HFONT)SendMessageW(editWnd, WM_GETFONT, 0, 0);

	HDC sdc = GetDC(NULL);
	HDC dc = CreateCompatibleDC(sdc);
	ReleaseDC(NULL, sdc);

	HFONT oldfont = (HFONT)SelectObject(dc, font);

	SIZE s;
	GetTextExtentPoint32W(dc, outbuf, wcslen(outbuf), &s);
	SelectObject(dc, oldfont);
	DeleteDC(dc);
	return s.cx + SendMessageW(editWnd, EM_GETMARGINS, 0, 0)*2 + 2;
#else
	if (inbuf.isempty())
		return 0;

	RECT r;
	getClientRect(&r);

	SysCanvas sysc;
	sysc.setTextSize(r.bottom - r.top);
	return sysc.getTextWidth(inbuf);
#endif
}

HWND EditWnd::getEditWnd()
{
	return editWnd;
}

#ifndef WIN32
enum {
    ES_MULTILINE,
    ES_WANTRETURN,
    ES_AUTOHSCROLL,
    ES_AUTOVSCROLL,
    WS_VSCROLL,
};
#endif

void EditWnd::setMultiline(int ml)
{
	multiline = ml;
	setStyle(ES_MULTILINE | ES_WANTRETURN, ml);
}

void EditWnd::setReadOnly(int ro)
{
	readonly = ro;
	setStyle(ES_READONLY, ro);
}

void EditWnd::setPassword(int pw)
{
	password = pw;
	setStyle(ES_PASSWORD, pw);
}

void EditWnd::setAutoHScroll(int hs)
{
	autohscroll = hs;
	setStyle(ES_AUTOHSCROLL, hs);
}

void EditWnd::setAutoVScroll(int vs)
{
	autovscroll = vs;
	setStyle(ES_AUTOVSCROLL, vs);
}

void EditWnd::setVScroll(int vs)
{
	vscroll = vs;
	setStyle(WS_VSCROLL, vs);
}

void EditWnd::setStyle(LONG style, int set)
{
#ifdef WIN32
	if (editWnd)
	{
		LONG s = GetWindowLong(editWnd, GWL_STYLE);
		if (set) s |= style;
		else s &= ~style;
		SetWindowLong(editWnd, GWL_STYLE, s);
	}
#else
	DebugString("portme -- EditWnd::setStyle\n");
#endif
}

int EditWnd::onGetFocus()
{
	int r = EDITWND_PARENT::onGetFocus();
#ifdef WIN32
	if (editWnd != NULL)
		SetFocus(editWnd);
#endif
	return r;
}

int EditWnd::wantFocus()
{
	return wantfocus;
}

int EditWnd::gotFocus()
{
	return (GetFocus() == editWnd);
}

void EditWnd::setBackgroundColor(COLORREF c)
{
	backgroundcolor.setColor(c);
}

void EditWnd::setTextColor(COLORREF c)
{
	textcolor.setColor(c);
}

void EditWnd::invalidate()
{
	EDITWND_PARENT::invalidate();
	InvalidateRect(editWnd, NULL, TRUE);
}

#ifdef LINUX
int EditWnd::textposFromCoord(int x, int y)
{
	RECT r;
	getClientRect(&r);

	SysCanvas canvas;

	canvas.setTextColor(textcolor);
	canvas.setTextSize(r.bottom - r.top);
	canvas.setTextOpaque(FALSE);

	x -= r.left;

	int i;

	char *str = STRDUP(inbuf);

	if (x > canvas.getTextWidth(str + viewstart))
		return inbuf.len();

	for (i = viewstart + 1; str[i]; i++)
	{
		char save = str[i];
		str[i] = '\0';
		if (x < canvas.getTextWidth(str + viewstart))
		{
			str[i] = save;
			break;
		}
		str[i] = save;
	}

	FREE(str);

	return i - 1;
}

int EditWnd::onLeftButtonDown(int x, int y)
{
	EDITWND_PARENT::onLeftButtonDown(x, y);

	// Add check for double/triple click...

	cursorpos = textposFromCoord(x, y);
	selstart = selend = cursorpos;

	selectmode = 1;

	return 1;
}

int EditWnd::onLeftButtonUp(int x, int y)
{
	EDITWND_PARENT::onLeftButtonUp(x, y);

	selectmode = 0;

	return 1;
}

int EditWnd::onMouseMove(int x, int y)
{
	EDITWND_PARENT::onMouseMove(x, y);

	switch (selectmode)
	{
	case 0:
		// Do nothing
		break;
	case 1:
		selend = textposFromCoord(x, y);
		cursorpos = selend;
		onEditUpdate();
		break;
	default:
		DebugString("selectmode %d not available\n", selectmode);
		break;
	}

	return selectmode;
}

int EditWnd::onKeyDown(int key)
{
	EDITWND_PARENT::onKeyDown(key);

	if (Std::keyDown(VK_CONTROL))
	{
		switch (key)
		{
		case 'a':
		case 'A':
			selectAll();
			break;

		default:
			return 0;
		}
	}
	else
	{
		switch (key)
		{
		case XK_Home:
			if (Std::keyDown(VK_SHIFT))
			{
				if (selstart == selend)
				{
					selstart = selend = cursorpos;
				}
				selend = 0;
			}
			else
			{
				selstart = selend = 0;
			}
			cursorpos = 0;
			break;

		case XK_End:
			if (Std::keyDown(VK_SHIFT))
			{
				if (selstart == selend)
				{
					selstart = selend = cursorpos;
				}
				selend = inbuf.len();
			}
			else
			{
				selstart = selend = 0;
			}
			cursorpos = inbuf.len();
			break;

		case XK_Right:
			if (Std::keyDown(VK_SHIFT))
			{
				if (selstart == selend)
				{
					selstart = selend = cursorpos;
				}
				selend++;
				if (selend > inbuf.len()) selend = inbuf.len();
			}
			else
			{
				selstart = selend = 0;
			}
			cursorpos++;
			if (cursorpos > inbuf.len()) cursorpos = inbuf.len();
			break;

		case XK_Left:
			if (Std::keyDown(VK_SHIFT))
			{
				if (selstart == selend)
				{
					selstart = selend = cursorpos;
				}
				selend--;
				if (selend < 0) selend = 0;
			}
			else
			{
				selstart = selend = 0;
			}
			cursorpos--;
			if (cursorpos < 0) cursorpos = 0;
			break;

		case XK_Escape:
			onAbort();
			break;

		case XK_Return:
			onEnter();
			break;

		case XK_Delete:
			if (selstart != selend)
			{
				int start = MIN(selstart, selend);
				int end = MAX(selstart, selend);

				String add;

				if (end < inbuf.len())
				{
					add = (const char *)inbuf + end;
				}
				else
				{
					add = "";
				}

				inbuf.trunc(start);
				inbuf += add;

				cursorpos = start;
				selstart = selend = 0;

			}
			else
			{
				if (cursorpos >= 0)
				{
					if (cursorpos < inbuf.len() - 1)
					{
						String tmp = inbuf;
						tmp.trunc(cursorpos);
						inbuf = tmp + ((const char *)inbuf + cursorpos + 1);
					}
					else if (cursorpos == inbuf.len() - 1)
					{
						inbuf.trunc(cursorpos);
					}
				}
			}
			break;

		case VK_BACK:
			if (selstart != selend)
			{
				int start = MIN(selstart, selend);
				int end = MAX(selstart, selend);

				String add;

				if (end < inbuf.len())
				{
					add = (const char *)inbuf + end;
				}
				else
				{
					add = "";
				}

				inbuf.trunc(start);
				inbuf += add;

				cursorpos = start;
				selstart = selend = 0;
			}
			else
			{
				if (cursorpos > 0)
				{
					if (cursorpos >= inbuf.len())
					{
						inbuf.trunc(cursorpos - 1);
						cursorpos--;
					}
					else
					{
						String tmp = inbuf;
						tmp.trunc(cursorpos - 1);
						inbuf = tmp + ((const char *)inbuf + cursorpos);
						cursorpos--;
					}
				}
			}
			break;

		default:
			if (key < 0x20 || key > 0x7e)
				return 0;

			if (selstart != selend)
			{
				int start = MIN(selstart, selend);
				int end = MAX(selstart, selend);

				String add;

				if (end < inbuf.len())
				{
					add = (const char *)inbuf + end;
				}
				else
				{
					add = "";
				}

				inbuf.trunc(start);
				inbuf += add;

				cursorpos = start;
				selstart = selend = 0;
			}

			String tmp;

			if (cursorpos >= inbuf.len())
			{
				tmp = "";
			}
			else
			{
				tmp = (const char *)inbuf + cursorpos;
			}

			inbuf.trunc(cursorpos);

			inbuf += (char)key;
			inbuf += tmp;

			cursorpos++;
		}
	}

	onEditUpdate();

	return 1;
}
#endif