// ---------------------------------------------------------------------------80
//                ICQ plugin for Miranda Instant Messenger
//                ________________________________________
// 
// Copyright © 2001-2004 Richard Hughes, Martin Öberg
// Copyright © 2004-2009 Joe Kucera, Bio
// Copyright © 2012-2018 Miranda NG team
// 
// 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.
// -----------------------------------------------------------------------------
//  DESCRIPTION:
//
//  ChangeInfo Plugin stuff
// -----------------------------------------------------------------------------

#include "stdafx.h"

static ChangeInfoData *dataStringEdit = nullptr;
static HWND hwndEdit = nullptr, hwndExpandButton = nullptr, hwndUpDown = nullptr;

static const char escapes[]={'a','\a',
'b','\b',
'e',27,
'f','\f',
'r','\r',
't','\t',
'v','\v',
'\\','\\'};

static void EscapesToMultiline(WCHAR *str,PDWORD selStart,PDWORD selEnd)
{ //converts "\\n" and "\\t" to "\r\n" and "\t" because a multi-line edit box can show them properly
	DWORD i;

	for(i=0; *str; str++, i++) 
	{
		if (*str != '\\') continue;
		if (str[1] == 'n') 
		{
			*str++ = '\r'; 
			i++; 
			*str = '\n';
		}
		else if (str[1] == 't') 
		{
			*str = '\t';
			memmove(str+1, str+2, sizeof(WCHAR)*(mir_wstrlen(str)-1));

			if (*selStart>i) --*selStart;
			if (*selEnd>i) --*selEnd;
		}
	}
}



static void EscapesToBinary(char *str)
{
	for (;*str;str++) 
	{
		if (*str!='\\') continue;
		if(str[1]=='n') {*str++='\r'; *str='\n'; continue;}
		if(str[1]=='0') 
		{
			char *codeend;
			*str=(char)strtol(str+1,&codeend,8);
			if (*str==0) {*str='\\'; continue;}
			memmove(str+1,codeend,mir_strlen(codeend)+1);
			continue;
		}
		for(int i=0;i<_countof(escapes);i+=2)
			if(str[1]==escapes[i]) 
			{
				*str=escapes[i+1];
				memmove(str+1,str+2,mir_strlen(str)-1);
				break;
			}
	}
}



char *BinaryToEscapes(char *str)
{
	int extra = 10, len = (int)mir_strlen(str) + 11, i;
	char *out, *pout;

	out=pout=(char*)SAFE_MALLOC(len);
	for (;*str;str++) 
	{
		if ((unsigned char)*str>=' ') 
		{
			*pout++=*str; 
			continue;
		}
		if(str[0]=='\r' && str[1]=='\n') 
		{
			*pout++='\\'; 
			*pout++='n';
			str++;
			continue;
		}
		if(extra<3) 
		{
			extra+=8; len+=8;
			pout=out=(char*)SAFE_REALLOC(out,len);
		}
		*pout++='\\';
		for(i = 0; i < _countof(escapes); i += 2)
			if (*str==escapes[i+1]) 
			{
				*pout++=escapes[i];
				extra--;
				break;
			}
			if(i < _countof(escapes)) continue;
			*pout++='0'; extra--;
			if (*str>=8) 
			{
				*pout++=(*str>>3)+'0';
				extra--;
			}
			*pout++=(*str&7)+'0'; extra--;
	}
	*pout='\0';
	return out;
}



static LRESULT CALLBACK StringEditSubclassProc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam)
{
	switch(msg) {
	case WM_KEYDOWN:
		if (wParam==VK_ESCAPE) 
		{
			if (dataStringEdit)
				dataStringEdit->EndStringEdit(0); 
			return 0;
		}
		if (wParam==VK_RETURN) 
		{
			if (GetWindowLongPtr(hwnd, GWL_STYLE) & ES_MULTILINE && !(GetKeyState(VK_CONTROL) & 0x8000)) break;
			if (dataStringEdit)
				dataStringEdit->EndStringEdit(1);
			return 0;
		}
		break;

	case WM_GETDLGCODE:
		return DLGC_WANTALLKEYS | mir_callNextSubclass(hwnd, StringEditSubclassProc, msg, wParam, lParam);

	case WM_KILLFOCUS:
		if ((HWND)wParam == hwndExpandButton) break;
		if (dataStringEdit)
			dataStringEdit->EndStringEdit(1);
		return 0;
	}
	return mir_callNextSubclass(hwnd, StringEditSubclassProc, msg, wParam, lParam);
}

static LRESULT CALLBACK ExpandButtonSubclassProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
	switch (msg) {
	case WM_LBUTTONUP:
		if (GetCapture() == hwnd) {
			//do expand
			RECT rcStart, rcEnd;
			DWORD selStart, selEnd;
			WCHAR *text;
			BOOL animEnabled = TRUE;

			GetWindowRect(hwndEdit, &rcStart);
			InflateRect(&rcStart, 2, 2);

			text = GetWindowTextUcs(hwndEdit);
			SendMessage(hwndEdit, EM_GETSEL, (WPARAM)&selStart, (LPARAM)&selEnd);
			DestroyWindow(hwndEdit);
			EscapesToMultiline(text, &selStart, &selEnd);
			hwndEdit = CreateWindowExA(WS_EX_TOOLWINDOW, "EDIT", "", WS_POPUP | WS_BORDER | WS_VISIBLE | ES_WANTRETURN | ES_AUTOVSCROLL | WS_VSCROLL | ES_MULTILINE, rcStart.left, rcStart.top, rcStart.right - rcStart.left, rcStart.bottom - rcStart.top, nullptr, nullptr, hInst, nullptr);
			SetWindowTextUcs(hwndEdit, text);
			mir_subclassWindow(hwndEdit, StringEditSubclassProc);
			SendMessage(hwndEdit, WM_SETFONT, (WPARAM)dataStringEdit->hListFont, 0);
			SendMessage(hwndEdit, EM_SETSEL, selStart, selEnd);
			SetFocus(hwndEdit);

			rcEnd.left = rcStart.left; rcEnd.top = rcStart.top;
			rcEnd.right = rcEnd.left + 350;
			rcEnd.bottom = rcEnd.top + 150;
			if (LOBYTE(LOWORD(GetVersion())) > 4 || HIBYTE(LOWORD(GetVersion())) > 0)
				SystemParametersInfo(SPI_GETCOMBOBOXANIMATION, 0, &animEnabled, FALSE);
			if (animEnabled) {
				DWORD startTime, timeNow;
				startTime = GetTickCount();
				for (;;) {
					UpdateWindow(hwndEdit);
					timeNow = GetTickCount();
					if (timeNow > startTime + 200) break;
					SetWindowPos(hwndEdit, nullptr, rcEnd.left, rcEnd.top, (rcEnd.right - rcStart.right)*(timeNow - startTime) / 200 + rcStart.right - rcEnd.left, (rcEnd.bottom - rcStart.bottom)*(timeNow - startTime) / 200 + rcStart.bottom - rcEnd.top, SWP_NOZORDER);
				}
			}
			SetWindowPos(hwndEdit, nullptr, rcEnd.left, rcEnd.top, rcEnd.right - rcEnd.left, rcEnd.bottom - rcEnd.top, SWP_NOZORDER);

			DestroyWindow(hwnd);
			hwndExpandButton = nullptr;

			SAFE_FREE((void**)&text);
		}
		break;
	}
	return mir_callNextSubclass(hwnd, ExpandButtonSubclassProc, msg, wParam, lParam);
}

void ChangeInfoData::BeginStringEdit(int iItem, RECT *rc, int i, WORD wVKey)
{
	char *szValue;
	char str[80];
	int alloced = 0;

	EndStringEdit(0);
	InflateRect(rc, -2, -2);
	rc->left -= 2;

	const SettingItem &si = setting[i];
	SettingItemData &sid = settingData[i];

	if (si.displayType & LIF_PASSWORD && !sid.changed)
		szValue = "                ";
	else if ((si.displayType & LIM_TYPE) == LI_NUMBER) {
		szValue = str;
		mir_snprintf(str, "%d", sid.value);
	}
	else if (sid.value) {
		szValue = BinaryToEscapes((char*)sid.value);
		alloced = 1;
	}
	else szValue = "";

	iEditItem = iItem;

	if ((si.displayType & LIM_TYPE) == LI_LONGSTRING) {
		rc->right -= rc->bottom - rc->top;
		hwndExpandButton = CreateWindowA("BUTTON", "", WS_VISIBLE | WS_CHILD | BS_PUSHBUTTON | BS_ICON, rc->right, rc->top, rc->bottom - rc->top, rc->bottom - rc->top, hwndList, nullptr, hInst, nullptr);
		SendMessage(hwndExpandButton, BM_SETIMAGE, IMAGE_ICON, (LPARAM)IcoLib_GetIconByHandle(iconList[0].hIcolib));
		mir_subclassWindow(hwndExpandButton, ExpandButtonSubclassProc);
	}

	dataStringEdit = this;
	hwndEdit = CreateWindow(L"EDIT", L"", WS_VISIBLE | WS_CHILD | ES_AUTOHSCROLL | ((si.displayType&LIM_TYPE) == LI_NUMBER ? ES_NUMBER : 0) | (si.displayType&LIF_PASSWORD ? ES_PASSWORD : 0), rc->left, rc->top, rc->right - rc->left, rc->bottom - rc->top, hwndList, nullptr, hInst, nullptr);
	SetWindowTextUtf(hwndEdit, szValue);
	if (alloced) SAFE_FREE(&szValue);
	mir_subclassWindow(hwndEdit, StringEditSubclassProc);
	SendMessage(hwndEdit, WM_SETFONT, (WPARAM)hListFont, 0);
	if ((si.displayType & LIM_TYPE) == LI_NUMBER) {
		int *range = (int*)si.pList;
		RECT rcUpDown;
		hwndUpDown = CreateWindow(UPDOWN_CLASS, L"", WS_VISIBLE | WS_CHILD | UDS_AUTOBUDDY | UDS_ALIGNRIGHT | UDS_HOTTRACK | UDS_NOTHOUSANDS | UDS_SETBUDDYINT, 0, 0, 0, 0, hwndList, nullptr, hInst, nullptr);
		SendMessage(hwndUpDown, UDM_SETRANGE32, range[0], range[1]);
		SendMessage(hwndUpDown, UDM_SETPOS32, 0, sid.value);
		if (!(si.displayType & LIF_ZEROISVALID) && sid.value == 0)
			SetWindowTextA(hwndEdit, "");
		GetClientRect(hwndUpDown, &rcUpDown);
		rc->right -= rcUpDown.right;
		SetWindowPos(hwndEdit, nullptr, 0, 0, rc->right - rc->left, rc->bottom - rc->top, SWP_NOZORDER | SWP_NOMOVE);
	}
	SendMessage(hwndEdit, EM_SETSEL, 0, (LPARAM)-1);
	SetFocus(hwndEdit);
	PostMessage(hwndEdit, WM_KEYDOWN, wVKey, 0);
}

void ChangeInfoData::EndStringEdit(int save)
{
	if (hwndEdit == nullptr || iEditItem == -1 || this != dataStringEdit)
		return;

	if (save) {
		char *text = (char*)SAFE_MALLOC(GetWindowTextLength(hwndEdit) + 1);
		const SettingItem &si = setting[iEditItem];
		SettingItemData &sid = settingData[iEditItem];

		GetWindowTextA(hwndEdit, (char*)text, GetWindowTextLength(hwndEdit) + 1);
		EscapesToBinary(text);
		if ((si.displayType & LIM_TYPE) == LI_NUMBER) {
			LPARAM newValue;
			int *range = (int*)si.pList;
			newValue = atoi(text);
			if (newValue) {
				if (newValue<range[0]) newValue = range[0];
				if (newValue>range[1]) newValue = range[1];
			}
			sid.changed = sid.value != newValue;
			sid.value = newValue;
			SAFE_FREE(&text);
		}
		else {
			if (!(si.displayType & LIF_PASSWORD)) {
				SAFE_FREE(&text);
				text = GetWindowTextUtf(hwndEdit);
				EscapesToBinary(text);
			}
			if ((si.displayType & LIF_PASSWORD && mir_strcmp(text, "                ")) ||
				 (!(si.displayType & LIF_PASSWORD) && mir_strcmp(text, (char*)sid.value) && (mir_strlen(text) + mir_strlen((char*)sid.value)))) {
				SAFE_FREE((void**)&sid.value);
				if (mir_strlen(text))
					sid.value = (LPARAM)text;
				else {
					sid.value = 0;
					SAFE_FREE(&text);
				}
				sid.changed = 1;
			}
		}

		if (sid.changed) {
			wchar_t tbuf[MAX_PATH];

			GetWindowText(hwndEdit, tbuf, _countof(tbuf));
			ListView_SetItemText(hwndList, iEditItem, 1, tbuf);

			EnableDlgItem(hwndDlg, IDC_SAVE, TRUE);
		}
	}

	ListView_RedrawItems(hwndList, iEditItem, iEditItem);
	iEditItem = -1;
	dataStringEdit = nullptr;
	DestroyWindow(hwndEdit);
	hwndEdit = nullptr;
	if (hwndExpandButton) DestroyWindow(hwndExpandButton);
	hwndExpandButton = nullptr;
	if (hwndUpDown) DestroyWindow(hwndUpDown);
	hwndUpDown = nullptr;
}

int IsStringEditWindow(HWND hwnd)
{
	if (hwnd == hwndEdit) return 1;
	if (hwnd == hwndExpandButton) return 1;
	if (hwnd == hwndUpDown) return 1;
	return 0;
}