////////////////////////////////////////////////////////////////////////////////
// Gadu-Gadu Plugin for Miranda IM
//
// Copyright (c) 2003-2009 Adam Strzelecki <ono+miranda@java.pl>
// Copyright (c) 2009-2012 Bartosz Bia�ek
//
// 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.
////////////////////////////////////////////////////////////////////////////////

#include "gg.h"
#include <io.h>

#define WM_ADDIMAGE  WM_USER + 1
#define WM_SENDIMG	 WM_USER + 2
#define WM_CHOOSEIMG WM_USER + 3
#define TIMERID_FLASHWND WM_USER + 4

////////////////////////////////////////////////////////////////////////////
// Image Window : Data

// tablica zawiera uin kolesia i uchwyt do okna, okno zawiera GGIMAGEDLGDATA
// ktore przechowuje handle kontaktu, i wskaznik na pierwszy  obrazek
// obrazki sa poukladane jako lista jednokierunkowa.
// przy tworzeniu okna podaje sie handle do kontaktu
// dodajac obrazek tworzy sie element listy i wysyla do okna
// wyswietlajac obrazek idzie po liscie jednokierunkowej

typedef struct _GGIMAGEENTRY
{
	HBITMAP hBitmap;
	wchar_t *lpszFileName;
	char *lpData;
	unsigned long nSize;
	struct _GGIMAGEENTRY *lpNext;
	uint32_t crc32;
} GGIMAGEENTRY;

typedef struct
{
	MCONTACT hContact;
	HANDLE hEvent;
	HWND hWnd;
	uin_t uin;
	int nImg, nImgTotal;
	GGIMAGEENTRY *lpImages;
	SIZE minSize;
	BOOL bReceiving;
	GGPROTO *gg;
} GGIMAGEDLGDATA;

// Prototypes
int gg_img_remove(GGIMAGEDLGDATA *dat);

////////////////////////////////////////////////////////////////////////////
// Image Module : Adding item to contact menu, creating sync objects
//
int GGPROTO::img_init()
{
	// Send image contact menu item
	CMenuItem mi;
	SET_UID(mi,0xab238938, 0xed85, 0x4cfe, 0x93, 0xb5, 0xb8, 0x83, 0xf4, 0x32, 0xa0, 0xec);
	mi.position = -2000010000;
	mi.hIcolibItem = iconList[11].hIcolib;
	mi.name.a = LPGEN("&Image");
	mi.pszService = GGS_SENDIMAGE;
	hImageMenuItem = Menu_AddContactMenuItem(&mi, m_szModuleName);

	// Receive image
	CreateProtoService(GGS_RECVIMAGE, &GGPROTO::img_recvimage);
	return FALSE;
}

////////////////////////////////////////////////////////////////////////////
// Image Module : closing dialogs, sync objects
//
int GGPROTO::img_shutdown()
{
	list_t l;
#ifdef DEBUGMODE
	debugLogA("img_shutdown(): Closing all dialogs...");
#endif
	// Rather destroy window instead of just removing structures
	for (l = imagedlgs; l;)
	{
		GGIMAGEDLGDATA *dat = (GGIMAGEDLGDATA *)l->data;
		l = l->next;

		if (dat && dat->hWnd)
		{
			if (IsWindow(dat->hWnd))
			{
				// Post message async, since it maybe be different thread
				if (!PostMessage(dat->hWnd, WM_CLOSE, 0, 0))
					debugLogA("img_shutdown(): Image dlg %x cannot be released !!", dat->hWnd);
			}
			else
				debugLogA("img_shutdown(): Image dlg %x not exists, but structure does !!", dat->hWnd);
		}
	}

	return FALSE;
}

////////////////////////////////////////////////////////////////////////////
// Image Module : destroying list
//
int GGPROTO::img_destroy()
{
	// Release all dialogs
	while (imagedlgs && gg_img_remove((GGIMAGEDLGDATA *)imagedlgs->data));

	// Destroy list
	list_destroy(imagedlgs, 1);
	Menu_RemoveItem(hImageMenuItem);
	return FALSE;
}

////////////////////////////////////////////////////////////////////////////
// Image Window : Frees image entry structure
//
static int gg_img_releasepicture(void *img)
{
	if (!img)
		return FALSE;
	free(((GGIMAGEENTRY *)img)->lpszFileName);
	if (((GGIMAGEENTRY *)img)->hBitmap)
		DeleteObject(((GGIMAGEENTRY *)img)->hBitmap);
	free(((GGIMAGEENTRY *)img)->lpData);
	free(img);

	return TRUE;
}

////////////////////////////////////////////////////////////////////////////
// Painting image
//
int gg_img_paint(HWND hwnd, GGIMAGEENTRY *dat)
{
	PAINTSTRUCT paintStruct;
	HDC hdc = BeginPaint(hwnd, &paintStruct);
	RECT rc;

	GetWindowRect(GetDlgItem(hwnd, IDC_IMG_IMAGE), &rc);
	ScreenToClient(hwnd, (POINT *)&rc.left);
	ScreenToClient(hwnd, (POINT *)&rc.right);
	FillRect(hdc, &rc, (HBRUSH)GetSysColorBrush(COLOR_WINDOW));

	if (dat->hBitmap)
	{
		HDC hdcBmp = NULL;
		int nWidth, nHeight;
		BITMAP bmp;

		GetObject(dat->hBitmap, sizeof(bmp), &bmp);
		nWidth = bmp.bmWidth; nHeight = bmp.bmHeight;

		hdcBmp = CreateCompatibleDC(hdc);
		SelectObject(hdcBmp, dat->hBitmap);
		if (hdcBmp)
		{
			SetStretchBltMode(hdc, HALFTONE);
			// Draw bitmap
			if (nWidth > (rc.right-rc.left) || nHeight > (rc.bottom-rc.top))
			{
				if ((double)nWidth / (double)nHeight > (double) (rc.right-rc.left) / (double)(rc.bottom-rc.top))
				{
					StretchBlt(hdc,
						rc.left,
						((rc.top + rc.bottom) - (rc.right - rc.left) * nHeight / nWidth) / 2,
						(rc.right - rc.left),
						(rc.right - rc.left) * nHeight / nWidth,
						hdcBmp, 0, 0, nWidth, nHeight, SRCCOPY);
				}
				else
				{
					StretchBlt(hdc,
						((rc.left + rc.right) - (rc.bottom - rc.top) * nWidth / nHeight) / 2,
						rc.top,
						(rc.bottom - rc.top) * nWidth / nHeight,
						(rc.bottom - rc.top),
						hdcBmp, 0, 0, nWidth, nHeight, SRCCOPY);
				}
			}
			else
			{
				BitBlt(hdc,
					(rc.left + rc.right - nWidth) / 2,
					(rc.top + rc.bottom - nHeight) / 2,
					nWidth, nHeight,
					hdcBmp, 0, 0, SRCCOPY);
			}
			DeleteDC(hdcBmp);
		}
	}
	EndPaint(hwnd, &paintStruct);

	return FALSE;
}

////////////////////////////////////////////////////////////////////////////////
// Returns supported image filters
//
wchar_t *gg_img_getfilter(wchar_t *szFilter, int nSize)
{
	wchar_t *szFilterName, *szFilterMask;
	wchar_t *pFilter = szFilter;

	// Match relative to ImgDecoder presence
	szFilterName = TranslateT("Image files (*.bmp,*.gif,*.jpeg,*.jpg,*.png)");
	szFilterMask = L"*.bmp;*.gif;*.jpeg;*.jpg;*.png";

	// Make up filter
	wcsncpy(pFilter, szFilterName, nSize);
	pFilter += mir_tstrlen(pFilter) + 1;
	if (pFilter >= szFilter + nSize) return NULL;
	wcsncpy(pFilter, szFilterMask, nSize - (pFilter - szFilter));
	pFilter += mir_tstrlen(pFilter) + 1;
	if (pFilter >= szFilter + nSize) return NULL;
	*pFilter = 0;

	return szFilter;
}

////////////////////////////////////////////////////////////////////////////////
// Save specified image entry
//
int gg_img_saveimage(HWND hwnd, GGIMAGEENTRY *dat)
{
	if (!dat)
		return FALSE;

	GGPROTO* gg = ((GGIMAGEDLGDATA *)GetWindowLongPtr(hwnd, GWLP_USERDATA))->gg;

	wchar_t szFilter[128];
	gg_img_getfilter(szFilter, _countof(szFilter));

	wchar_t szFileName[MAX_PATH];
	wcsncpy(szFileName, dat->lpszFileName, _countof(szFileName));

	OPENFILENAME ofn = {0};
	ofn.lStructSize = OPENFILENAME_SIZE_VERSION_400;
	ofn.hwndOwner = hwnd;
	ofn.hInstance = hInstance;
	ofn.lpstrFile = szFileName;
	ofn.lpstrFilter = szFilter;
	ofn.nMaxFile = MAX_PATH;
	ofn.Flags = OFN_PATHMUSTEXIST | OFN_NOCHANGEDIR | OFN_OVERWRITEPROMPT;
	if (GetSaveFileName(&ofn))
	{
		FILE *fp = _wfopen(szFileName, L"w+b");
		if (fp)
		{
			fwrite(dat->lpData, dat->nSize, 1, fp);
			fclose(fp);
			gg->debugLog(L"gg_img_saveimage(): Image saved to %s.", szFileName);
		}
		else
		{
			gg->debugLog(L"gg_img_saveimage(): Cannot save image to %s.", szFileName);
			MessageBox(hwnd, TranslateT("Image cannot be written to disk."), gg->m_tszUserName, MB_OK | MB_ICONERROR);
		}
	}

	return 0;
}

////////////////////////////////////////////////////////////////////////////
// Fit window size to image size
//
BOOL gg_img_fit(HWND hwndDlg)
{
	GGIMAGEDLGDATA *dat = (GGIMAGEDLGDATA *)GetWindowLongPtr(hwndDlg, GWLP_USERDATA);
	RECT dlgRect, imgRect, wrkRect;
	int nWidth, nHeight;
	int rWidth = 0, rHeight = 0;
	int oWidth = 0, oHeight = 0;
	BITMAP bmp;
	GGIMAGEENTRY *img = NULL;
	HDC hdc;

	// Check if image is loaded
	if (!dat || !dat->lpImages || !dat->lpImages->hBitmap)
		return FALSE;

	img = dat->lpImages;

	// Go to last image
	while (img->lpNext && dat->lpImages->hBitmap)
		img = img->lpNext;

	// Get rects of display
	GetWindowRect(hwndDlg, &dlgRect);
	GetClientRect(GetDlgItem(hwndDlg, IDC_IMG_IMAGE), &imgRect);

	hdc = GetDC(hwndDlg);

	GetObject(img->hBitmap, sizeof(bmp), &bmp);
	nWidth = bmp.bmWidth; nHeight = bmp.bmHeight;
	SystemParametersInfo(SPI_GETWORKAREA, 0, &wrkRect, 0);

	ReleaseDC(hwndDlg, hdc);

	if ((imgRect.right - imgRect.left) < nWidth)
		rWidth = nWidth - imgRect.right + imgRect.left;
	if ((imgRect.bottom - imgRect.top) < nHeight)
		rHeight = nHeight - imgRect.bottom + imgRect.top;

	// Check if anything needs resize
	if (!rWidth && !rHeight)
		return FALSE;

	oWidth = dlgRect.right - dlgRect.left + rWidth;
	oHeight = dlgRect.bottom - dlgRect.top + rHeight;

	if (oHeight > wrkRect.bottom - wrkRect.top)
	{
		oWidth = (int)((double)(wrkRect.bottom - wrkRect.top + imgRect.bottom - imgRect.top - dlgRect.bottom + dlgRect.top) * nWidth / nHeight)
			- imgRect.right + imgRect.left + dlgRect.right - dlgRect.left;
		if (oWidth < dlgRect.right - dlgRect.left)
			oWidth = dlgRect.right - dlgRect.left;
		oHeight = wrkRect.bottom - wrkRect.top;
	}
	if (oWidth > wrkRect.right - wrkRect.left)
	{
		oHeight = (int)((double)(wrkRect.right - wrkRect.left + imgRect.right - imgRect.left - dlgRect.right + dlgRect.left) * nHeight / nWidth)
			- imgRect.bottom + imgRect.top + dlgRect.bottom - dlgRect.top;
		if (oHeight < dlgRect.bottom - dlgRect.top)
			oHeight = dlgRect.bottom - dlgRect.top;
		oWidth = wrkRect.right - wrkRect.left;
	}
	SetWindowPos(hwndDlg, NULL,
		(wrkRect.left + wrkRect.right - oWidth) / 2,
		(wrkRect.top + wrkRect.bottom - oHeight) / 2,
		oWidth, oHeight,
		SWP_SHOWWINDOW | SWP_NOZORDER /* | SWP_NOACTIVATE */);

	return TRUE;
}

////////////////////////////////////////////////////////////////////////////
// Dialog resizer procedure
//
static int sttImageDlgResizer(HWND, LPARAM, UTILRESIZECONTROL *urc)
{
	switch (urc->wId)
	{
		case IDC_IMG_PREV:
		case IDC_IMG_NEXT:
		case IDC_IMG_DELETE:
		case IDC_IMG_SAVE:
			return RD_ANCHORX_RIGHT | RD_ANCHORY_TOP;
		case IDC_IMG_IMAGE:
			return RD_ANCHORX_LEFT | RD_ANCHORY_TOP | RD_ANCHORY_HEIGHT | RD_ANCHORX_WIDTH;
		case IDC_IMG_SEND:
		case IDC_IMG_CANCEL:
			return RD_ANCHORX_RIGHT | RD_ANCHORY_BOTTOM;
	}
	return RD_ANCHORX_LEFT | RD_ANCHORY_TOP;
}

////////////////////////////////////////////////////////////////////////////
// Send / Recv main dialog procedure
//
static INT_PTR CALLBACK gg_img_dlgproc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam)
{
	GGIMAGEDLGDATA *dat = (GGIMAGEDLGDATA *)GetWindowLongPtr(hwndDlg, GWLP_USERDATA);

	switch (msg) {
	case WM_INITDIALOG:
		{
			RECT rect;

			TranslateDialogDefault(hwndDlg);
			// This should be already initialized
			// InitCommonControls();

			// Get dialog data
			dat = (GGIMAGEDLGDATA *)lParam;
			SetWindowLongPtr(hwndDlg, GWLP_USERDATA, (LONG_PTR)dat);

			// Save dialog handle
			dat->hWnd = hwndDlg;

			// Send event if someone's waiting
			if (dat->hEvent) SetEvent(dat->hEvent);
			else dat->gg->debugLogA("gg_img_dlgproc(): WM_INITDIALOG Creation event not found, but someone might be waiting.");

			// Making buttons flat
			SendDlgItemMessage(hwndDlg, IDC_IMG_PREV,	BUTTONSETASFLATBTN, TRUE, 0);
			SendDlgItemMessage(hwndDlg, IDC_IMG_NEXT,	BUTTONSETASFLATBTN, TRUE, 0);
			SendDlgItemMessage(hwndDlg, IDC_IMG_DELETE,	BUTTONSETASFLATBTN, TRUE, 0);
			SendDlgItemMessage(hwndDlg, IDC_IMG_SAVE,	BUTTONSETASFLATBTN, TRUE, 0);

			// Setting images for buttons
			SendDlgItemMessage(hwndDlg, IDC_IMG_PREV,	BM_SETIMAGE, IMAGE_ICON, (LPARAM)LoadIconEx("previous", FALSE));
			SendDlgItemMessage(hwndDlg, IDC_IMG_NEXT,	BM_SETIMAGE, IMAGE_ICON, (LPARAM)LoadIconEx("next", FALSE));
			SendDlgItemMessage(hwndDlg, IDC_IMG_DELETE,	BM_SETIMAGE, IMAGE_ICON, (LPARAM)LoadIconEx("delete", FALSE));
			SendDlgItemMessage(hwndDlg, IDC_IMG_SAVE,	BM_SETIMAGE, IMAGE_ICON, (LPARAM)LoadIconEx("save", FALSE));

			// Setting tooltips for buttons
			SendDlgItemMessage(hwndDlg, IDC_IMG_PREV,	BUTTONADDTOOLTIP, (WPARAM)TranslateT("Previous image"), BATF_TCHAR);
			SendDlgItemMessage(hwndDlg, IDC_IMG_NEXT,	BUTTONADDTOOLTIP, (WPARAM)TranslateT("Next image"), BATF_TCHAR);
			SendDlgItemMessage(hwndDlg, IDC_IMG_DELETE,	BUTTONADDTOOLTIP, (WPARAM)TranslateT("Delete image from the list"), BATF_TCHAR);
			SendDlgItemMessage(hwndDlg, IDC_IMG_SAVE,	BUTTONADDTOOLTIP, (WPARAM)TranslateT("Save image to disk"), BATF_TCHAR);

			// Set main window image
			Window_SetIcon_IcoLib(hwndDlg, GetIconHandle(IDI_IMAGE));

			wchar_t *szName = pcli->pfnGetContactDisplayName(dat->hContact, 0), szTitle[128];
			if (dat->bReceiving)
				mir_sntprintf(szTitle, TranslateT("Image from %s"), szName);
			else
				mir_sntprintf(szTitle, TranslateT("Image for %s"), szName);
			SetWindowText(hwndDlg, szTitle);

			// Store client extents
			GetClientRect(hwndDlg, &rect);
			dat->minSize.cx = rect.right - rect.left;
			dat->minSize.cy = rect.bottom - rect.top;
		}
		return TRUE;

	case WM_SIZE:
		Utils_ResizeDialog(hwndDlg, hInstance, dat->bReceiving ? MAKEINTRESOURCEA(IDD_IMAGE_RECV) : MAKEINTRESOURCEA(IDD_IMAGE_SEND), sttImageDlgResizer);
		if (wParam == SIZE_RESTORED || wParam == SIZE_MAXIMIZED)
			InvalidateRect(hwndDlg, NULL, FALSE);
		return 0;

	case WM_SIZING:
		{
			RECT *pRect = (RECT *)lParam;
			if (pRect->right - pRect->left < dat->minSize.cx)
			{
				if (wParam == WMSZ_BOTTOMLEFT || wParam == WMSZ_LEFT || wParam == WMSZ_TOPLEFT)
					pRect->left = pRect->right - dat->minSize.cx;
				else
					pRect->right = pRect->left + dat->minSize.cx;
			}
			if (pRect->bottom - pRect->top < dat->minSize.cy)
			{
				if (wParam == WMSZ_TOPLEFT || wParam == WMSZ_TOP || wParam == WMSZ_TOPRIGHT)
					pRect->top = pRect->bottom - dat->minSize.cy;
				else
					pRect->bottom = pRect->top + dat->minSize.cy;
			}
		}
		return TRUE;

	case WM_CLOSE:
		EndDialog(hwndDlg, 0);
		break;

		// Flash the window
	case WM_TIMER:
		if (wParam == TIMERID_FLASHWND)
			FlashWindow(hwndDlg, TRUE);
		break;

		// Kill the timer
	case WM_ACTIVATE:
		if (LOWORD(wParam) != WA_ACTIVE)
			break;
	case WM_MOUSEACTIVATE:
		if (KillTimer(hwndDlg, TIMERID_FLASHWND))
			FlashWindow(hwndDlg, FALSE);
		break;

	case WM_PAINT:
		if (dat->lpImages)
		{
			GGIMAGEENTRY *img = dat->lpImages;
			int i;

			for (i = 1; img && (i < dat->nImg); i++)
				img = img->lpNext;

			if (!img)
			{
				dat->gg->debugLogA("gg_img_dlgproc(): WM_PAINT Image was not found on the list. Cannot paint the window.");
				return FALSE;
			}

			if (dat->bReceiving)
			{
				wchar_t szTitle[128];
				mir_sntprintf(szTitle, L"%s (%d / %d)", img->lpszFileName, dat->nImg, dat->nImgTotal);
				SetDlgItemText(hwndDlg, IDC_IMG_NAME, szTitle);
			}
			else
				SetDlgItemText(hwndDlg, IDC_IMG_NAME, img->lpszFileName);
			gg_img_paint(hwndDlg, img);
		}
		break;

	case WM_DESTROY:
		if (dat)
		{
			// Deleting all image entries
			GGIMAGEENTRY *temp, *img = dat->lpImages;
			GGPROTO *gg = dat->gg;
			while (temp = img)
			{
				img = img->lpNext;
				gg_img_releasepicture(temp);
			}
			ReleaseIconEx("previous", FALSE);
			ReleaseIconEx("next", FALSE);
			ReleaseIconEx("delete", FALSE);
			ReleaseIconEx("save", FALSE);
			Window_FreeIcon_IcoLib(hwndDlg);
			gg->gg_EnterCriticalSection(&gg->img_mutex, "gg_img_dlgproc", 58, "img_mutex", 1);
			list_remove(&gg->imagedlgs, dat, 1);
			gg->gg_LeaveCriticalSection(&gg->img_mutex, "gg_img_dlgproc", 58, 1, "img_mutex", 1);
		}
		return TRUE;

	case WM_COMMAND:
		switch (LOWORD(wParam))
		{
		case IDC_IMG_CANCEL:
			EndDialog(hwndDlg, 0);
			return TRUE;

		case IDC_IMG_PREV:
			if (dat->nImg > 1)
			{
				dat->nImg--;
				InvalidateRect(hwndDlg, NULL, FALSE);
			}
			return TRUE;

		case IDC_IMG_NEXT:
			if (dat->nImg < dat->nImgTotal)
			{
				dat->nImg++;
				InvalidateRect(hwndDlg, NULL, FALSE);
			}
			return TRUE;

		case IDC_IMG_DELETE:
			{
				GGIMAGEENTRY *del, *img = dat->lpImages;
				if (dat->nImg == 1)
				{
					del = dat->lpImages;
					dat->lpImages = img->lpNext;
				}
				else
				{
					for (int i = 1; img && (i < dat->nImg - 1); i++)
						img = img->lpNext;
					if (!img)
					{
						dat->gg->debugLogA("gg_img_dlgproc(): IDC_IMG_DELETE Image was not found on the list. Cannot delete it from the list.");
						return FALSE;
					}
					del = img->lpNext;
					img->lpNext = del->lpNext;
					dat->nImg --;
				}

				if ((-- dat->nImgTotal) == 0)
					EndDialog(hwndDlg, 0);
				else
					InvalidateRect(hwndDlg, NULL, FALSE);

				gg_img_releasepicture(del);
			}
			return TRUE;

		case IDC_IMG_SAVE:
			{
				GGIMAGEENTRY *img = dat->lpImages;

				for (int i = 1; img && (i < dat->nImg); i++)
					img = img->lpNext;
				if (!img)
				{
					dat->gg->debugLogA("gg_img_dlgproc(): IDC_IMG_SAVE Image was not found on the list. Cannot launch saving.");
					return FALSE;
				}
				gg_img_saveimage(hwndDlg, img);
			}
			return TRUE;

		case IDC_IMG_SEND:
			{
				unsigned char format[20];
				char *msg = "\xA0\0";
				GGPROTO *gg = dat->gg;

				if (dat->lpImages && gg->isonline())
				{
					uin_t uin = (uin_t)gg->getDword(dat->hContact, GG_KEY_UIN, 0);
					struct gg_msg_richtext_format *r = NULL;
					struct gg_msg_richtext_image *p = NULL;
					int len;

					((struct gg_msg_richtext*)format)->flag = 2;

					r = (struct gg_msg_richtext_format *)(format + sizeof(struct gg_msg_richtext));
					r->position = 0;
					r->font = GG_FONT_IMAGE;

					p = (struct gg_msg_richtext_image *)(format + sizeof(struct gg_msg_richtext) + sizeof(struct gg_msg_richtext_format));
					p->unknown1 = 0x109;
					p->size = dat->lpImages->nSize;

					dat->lpImages->crc32 = p->crc32 = gg_fix32(gg_crc32(0, (BYTE*)dat->lpImages->lpData, dat->lpImages->nSize));

					len = sizeof(struct gg_msg_richtext_format) + sizeof(struct gg_msg_richtext_image);
					((struct gg_msg_richtext*)format)->length = len;

					gg->gg_EnterCriticalSection(&gg->sess_mutex, "gg_img_dlgproc", 59, "sess_mutex", 1);
					gg_send_message_richtext(gg->sess, GG_CLASS_CHAT, (uin_t)uin, (unsigned char*)msg, format, len + sizeof(struct gg_msg_richtext));
					gg->gg_LeaveCriticalSection(&gg->sess_mutex, "gg_img_dlgproc", 59, 1, "sess_mutex", 1);

					// Protect dat from releasing
					SetWindowLongPtr(hwndDlg, GWLP_USERDATA, 0);

					EndDialog(hwndDlg, 0);
				}
				return TRUE;
			}
			break;
		}
		break;

	case WM_ADDIMAGE: // lParam == GGIMAGEENTRY *dat
		{
			GGIMAGEENTRY *lpImage = (GGIMAGEENTRY *)lParam;
			GGIMAGEENTRY *lpImages = dat->lpImages;

			if (!dat->lpImages) // first image entry
				dat->lpImages = lpImage;
			else // adding at the end of the list
			{
				while (lpImages->lpNext)
					lpImages = lpImages->lpNext;
				lpImages->lpNext = lpImage;
			}
			dat->nImg = ++ dat->nImgTotal;
		}
		// Fit window to image
		if (!gg_img_fit(hwndDlg))
			InvalidateRect(hwndDlg, NULL, FALSE);
		return TRUE;

	case WM_CHOOSEIMG:
		{
			wchar_t szFilter[128];
			wchar_t szFileName[MAX_PATH];
			OPENFILENAME ofn = {0};

			gg_img_getfilter(szFilter, _countof(szFilter));
			*szFileName = 0;
			ofn.lStructSize = OPENFILENAME_SIZE_VERSION_400;
			ofn.hwndOwner = hwndDlg;
			ofn.hInstance = hInstance;
			ofn.lpstrFilter = szFilter;
			ofn.lpstrFile = szFileName;
			ofn.nMaxFile = MAX_PATH;
			ofn.lpstrTitle = TranslateT("Select picture to send");
			ofn.Flags = OFN_FILEMUSTEXIST | OFN_NOCHANGEDIR | OFN_HIDEREADONLY;
			if (GetOpenFileName(&ofn))
			{
				if (dat->lpImages)
					gg_img_releasepicture(dat->lpImages);
				if (!(dat->lpImages = (GGIMAGEENTRY *)dat->gg->img_loadpicture(0, szFileName)))
				{
					EndDialog(hwndDlg, 0);
					return FALSE;
				}
				if (!gg_img_fit(hwndDlg))
					InvalidateRect(hwndDlg, NULL, FALSE);
			}
			else
			{
				EndDialog(hwndDlg, 0);
				return FALSE;
			}
			return TRUE;
		}
	}

	return FALSE;
}

////////////////////////////////////////////////////////////////////////////
// Image dialog call thread
//
void __cdecl GGPROTO::img_dlgcallthread(void *param)
{
	HWND hMIWnd = 0;

	debugLogA("img_dlgcallthread(): started.");
	GGIMAGEDLGDATA *dat = (GGIMAGEDLGDATA *)param;
	DialogBoxParam(hInstance, dat->bReceiving ? MAKEINTRESOURCE(IDD_IMAGE_RECV) : MAKEINTRESOURCE(IDD_IMAGE_SEND),
		hMIWnd, gg_img_dlgproc, (LPARAM) dat);
#ifdef DEBUGMODE
	debugLogA("img_dlgcallthread(): end.");
#endif

}

////////////////////////////////////////////////////////////////////////////
// Open dialog receive for specified contact
//
GGIMAGEDLGDATA *gg_img_recvdlg(GGPROTO *gg, MCONTACT hContact)
{
	// Create dialog data
	GGIMAGEDLGDATA *dat = (GGIMAGEDLGDATA *)calloc(1, sizeof(GGIMAGEDLGDATA));
	dat->hContact = hContact;
	dat->hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
	dat->bReceiving = TRUE;
	dat->gg = gg;
	ResetEvent(dat->hEvent);
#ifdef DEBUGMODE
	gg->debugLogA("gg_img_recvdlg(): ForkThread 18 GGPROTO::img_dlgcallthread");
#endif
	gg->ForkThread(&GGPROTO::img_dlgcallthread, dat);
	return dat;
}

////////////////////////////////////////////////////////////////////////////
// Checks if an image is already saved to the specified path
// Returns 1 if yes, 0 if no or -1 if different image on this path is found
//
int gg_img_isexists(wchar_t *szPath, GGIMAGEENTRY *dat)
{
	struct _stat st;

	if (_wstat(szPath, &st) != 0)
		return 0;

	if (st.st_size == dat->nSize)
	{
		FILE *fp = _wfopen(szPath, L"rb");
		if (!fp) return 0;
		char *lpData = (char*)mir_alloc(dat->nSize);
		if (fread(lpData, 1, dat->nSize, fp) == dat->nSize)
		{
			if (dat->crc32 == gg_fix32(gg_crc32(0, (BYTE*)lpData, dat->nSize)) ||
				memcmp(lpData, dat->lpData, dat->nSize) == 0)
			{
				mir_free(lpData);
				fclose(fp);
				return 1;
			}
		}
		mir_free(lpData);
		fclose(fp);
	}

	return -1;
}

////////////////////////////////////////////////////////////////////////////
// Determine if image's file name has the proper extension
//
wchar_t *gg_img_hasextension(wchar_t *filename)
{
	if (filename != NULL && *filename != '\0')
	{
		wchar_t *imgtype = wcsrchr(filename, '.');
		if (imgtype != NULL)
		{
			size_t len = mir_tstrlen(imgtype);
			imgtype++;
			if (len == 4 && (mir_tstrcmpi(imgtype, L"bmp") == 0 ||
							 mir_tstrcmpi(imgtype, L"gif") == 0 ||
							 mir_tstrcmpi(imgtype, L"jpg") == 0 ||
							 mir_tstrcmpi(imgtype, L"png") == 0))
				return --imgtype;
			if (len == 5 &&  mir_tstrcmpi(imgtype, L"jpeg") == 0)
				return --imgtype;
		}
	}
	return NULL;
}

////////////////////////////////////////////////////////////////////////////////
// Display received image using message with [img] BBCode
//
int GGPROTO::img_displayasmsg(MCONTACT hContact, void *img)
{
	GGIMAGEENTRY *dat = (GGIMAGEENTRY *)img;
	wchar_t szPath[MAX_PATH], path[MAX_PATH], *pImgext, imgext[6];
	size_t tPathLen;
	int i, res;

	if (hImagesFolder == NULL || FoldersGetCustomPathT(hImagesFolder, path, MAX_PATH, L"")) {
		wchar_t *tmpPath = Utils_ReplaceVarsT( L"%miranda_userdata%");
		tPathLen = mir_sntprintf(szPath, L"%s\\%s\\ImageCache", tmpPath, m_tszUserName);
		mir_free(tmpPath);
	}
	else {
		mir_tstrcpy(szPath, path);
		tPathLen = mir_tstrlen(szPath);
	}

	if ( _waccess(szPath, 0)){
		int ret = CreateDirectoryTreeT(szPath);
		if (ret == 0){
			debugLog(L"img_displayasmsg(): Created new directory for image cache: %s.", szPath);
		} else {
			debugLog(L"img_displayasmsg(): Can not create directory for image cache: %s. errno=%d: %s", szPath, errno, strerror(errno));
			wchar_t error[512];
			mir_sntprintf(error, TranslateT("Cannot create image cache directory. ERROR: %d: %s\n%s"), errno, _tcserror(errno), szPath);
			showpopup(m_tszUserName, error, GG_POPUP_ERROR | GG_POPUP_ALLOW_MSGBOX | GG_POPUP_ONCE);
		}
	}

	mir_sntprintf(szPath + tPathLen, MAX_PATH - tPathLen, L"\\%s", dat->lpszFileName);
	if ((pImgext = gg_img_hasextension(szPath)) == NULL)
		pImgext = szPath + mir_tstrlen(szPath);
	wcsncpy_s(imgext, pImgext, _TRUNCATE);
	for (i = 1; ; ++i)
	{
		if ((res = gg_img_isexists(szPath, dat)) != -1) break;
		mir_sntprintf(szPath, L"%.*s (%u)%s", pImgext - szPath, szPath, i, imgext);
	}

	if (res == 0) {
		// Image file not found, thus create it
		FILE *fp = _wfopen(szPath, L"w+b");
		if (fp) {
			res = fwrite(dat->lpData, dat->nSize, 1, fp) > 0;
			fclose(fp);
		} else {
			debugLog(L"img_displayasmsg(): Cannot open file %s for write image. errno=%d: %s", szPath, errno, strerror(errno));
			wchar_t error[512];
			mir_sntprintf(error, TranslateT("Cannot save received image to file. ERROR: %d: %s\n%s"), errno, _tcserror(errno), szPath);
			showpopup(m_tszUserName, error, GG_POPUP_ERROR);
			return 0;
		}
	}

	if (res != 0) {
		wchar_t image_msg[MAX_PATH + 11];
		mir_sntprintf(image_msg, L"[img]%s[/img]", szPath);

		T2Utf szMessage(image_msg);
		PROTORECVEVENT pre = {0};
		pre.timestamp = time(NULL);
		pre.szMessage = szMessage;
		ProtoChainRecvMsg(hContact, &pre);
		debugLog(L"img_displayasmsg(): Image saved to %s.", szPath);
	}
	else debugLog(L"img_displayasmsg(): Cannot save image to %s.", szPath);

	return 0;
}

////////////////////////////////////////////////////////////////////////////
// Return if uin has it's window already opened
//
BOOL GGPROTO::img_opened(uin_t uin)
{
	list_t l = imagedlgs;
	while (l)
	{
		GGIMAGEDLGDATA *dat = (GGIMAGEDLGDATA *)l->data;
		if (dat->uin == uin)
			return TRUE;
		l = l->next;
	}
	return FALSE;
}

////////////////////////////////////////////////////////////////////////////
// Image Module : Looking for window entry, create if not found
//
int GGPROTO::img_display(MCONTACT hContact, void *img)
{
	list_t l = imagedlgs;
	GGIMAGEDLGDATA *dat = NULL;

	if (!img) return FALSE;

	// Look for already open dialog
	gg_EnterCriticalSection(&img_mutex, "img_display", 60, "img_mutex", 1);
	while (l)
	{
		dat = (GGIMAGEDLGDATA *)l->data;
		if (dat->bReceiving && dat->hContact == hContact)
			break;
		l = l->next;
	}

	if (!l) dat = NULL;

	if (!dat)
	{
		dat = gg_img_recvdlg(this, hContact);
		dat->uin = getDword(hContact, GG_KEY_UIN, 0);

		while (WaitForSingleObjectEx(dat->hEvent, INFINITE, TRUE) != WAIT_OBJECT_0);
		CloseHandle(dat->hEvent);
		dat->hEvent = NULL;

		list_add(&imagedlgs, dat, 0);
	}
	gg_LeaveCriticalSection(&img_mutex, "img_display", 60, 1, "img_mutex", 1);

	SendMessage(dat->hWnd, WM_ADDIMAGE, 0, (LPARAM)img);
	if (/*db_get_b(NULL, "Chat", "bFlashWindowHighlight", 0) != 0 && */
		GetActiveWindow() != dat->hWnd && GetForegroundWindow() != dat->hWnd)
		SetTimer(dat->hWnd, TIMERID_FLASHWND, 900, NULL);

	return TRUE;
}

////////////////////////////////////////////////////////////////////////////
// Helper function to determine image file format and the right extension
//
const wchar_t *gg_img_guessfileextension(const char *lpData)
{
	if (lpData != NULL)
	{
		if (memcmp(lpData, "BM", 2) == 0)
			return L".bmp";
		if (memcmp(lpData, "GIF8", 4) == 0)
			return L".gif";
		if (memcmp(lpData, "\xFF\xD8", 2) == 0)
			return L".jpg";
		if (memcmp(lpData, "\x89PNG", 4) == 0)
			return L".png";
	}
	return L"";
}

////////////////////////////////////////////////////////////////////////////
// Image Window : Loading picture and sending for display
//
void* GGPROTO::img_loadpicture(gg_event* e, wchar_t *szFileName)
{
	if (!szFileName &&
		(!e || !e->event.image_reply.size || !e->event.image_reply.image || !e->event.image_reply.filename))
		return NULL;

	GGIMAGEENTRY *dat = (GGIMAGEENTRY *)calloc(1, sizeof(GGIMAGEENTRY));
	if (dat == NULL)
		return NULL;

	// Copy the file name
	if (szFileName)
	{
		FILE *fp = _wfopen(szFileName, L"rb");
		if (!fp) {
			free(dat);
			debugLog(L"img_loadpicture(): fopen(\"%s\", \"rb\" failed. errno=%d: %s)", szFileName, errno, strerror(errno));
			wchar_t error[512];
			mir_sntprintf(error, TranslateT("Cannot open image file. ERROR: %d: %s\n%s"), errno, _tcserror(errno), szFileName);
			showpopup(m_tszUserName, error, GG_POPUP_ERROR);
			return NULL;
		}
		fseek(fp, 0, SEEK_END);
		dat->nSize = ftell(fp);
		if (dat->nSize <= 0)
		{
			fclose(fp);
			free(dat);
			debugLog(L"img_loadpicture(): Zero file size \"%s\" failed.", szFileName);
			return NULL;
		}
		// Maximum acceptable image size
		if (dat->nSize > 255 * 1024)
		{
			fclose(fp);
			free(dat);
			debugLog(L"img_loadpicture(): Image size of \"%s\" exceeds 255 KB.", szFileName);
			MessageBox(NULL, TranslateT("Image exceeds maximum allowed size of 255 KB."), m_tszUserName, MB_OK | MB_ICONEXCLAMATION);
			return NULL;
		}
		fseek(fp, 0, SEEK_SET);
		dat->lpData = (char*)malloc(dat->nSize);
		if (fread(dat->lpData, 1, dat->nSize, fp) < dat->nSize)
		{
			free(dat->lpData);
			fclose(fp);
			free(dat);
			debugLog(L"img_loadpicture(): Reading file \"%s\" failed.", szFileName);
			return NULL;
		}
		fclose(fp);
		dat->lpszFileName = wcsdup(szFileName);
	}
	// Copy picture from packet
	else if (e && e->event.image_reply.filename)
	{
		dat->nSize = e->event.image_reply.size;
		dat->lpData = (char*)malloc(dat->nSize);
		memcpy(dat->lpData, e->event.image_reply.image, dat->nSize);

		ptrT tmpFileName( mir_a2t(e->event.image_reply.filename));
		if (!gg_img_hasextension(tmpFileName)) {
			// Add missing file extension
			const wchar_t *szImgType = gg_img_guessfileextension(dat->lpData);
			if (*szImgType) {
				dat->lpszFileName = (wchar_t*)calloc(sizeof(wchar_t), mir_tstrlen(tmpFileName) + mir_tstrlen(szImgType) + 1);
				if (dat->lpszFileName != NULL) {
					mir_tstrcpy(dat->lpszFileName, tmpFileName);
					mir_tstrcat(dat->lpszFileName, szImgType);
				}
			}
		}

		if (dat->lpszFileName == NULL)
			dat->lpszFileName = wcsdup( _A2T( e->event.image_reply.filename));
	}

	////////////////////////////////////////////////////////////////////
	// Loading picture using Miranda Image services

	// Load image from memory
	if (!szFileName)
	{
		IMGSRVC_MEMIO memio;
		memio.iLen = dat->nSize;
		memio.pBuf = (void *)dat->lpData;
		memio.fif = FIF_UNKNOWN; /* detect */
		memio.flags = 0;
		dat->hBitmap = (HBITMAP) CallService(MS_IMG_LOADFROMMEM, (WPARAM) &memio, 0);
	}
	// Load image from file
	else
		dat->hBitmap = (HBITMAP) CallService(MS_IMG_LOAD, (WPARAM) szFileName, IMGL_TCHAR);

	// If everything is fine return the handle
	if (dat->hBitmap) return dat;

	debugLogA("img_loadpicture(): MS_IMG_LOAD(MEM) failed.");
	if (dat)
	{
		free(dat->lpData);
		free(dat->lpszFileName);
		free(dat);
	}
	return NULL;
}

////////////////////////////////////////////////////////////////////////////
// Image Recv : AddEvent proc
//
INT_PTR GGPROTO::img_recvimage(WPARAM wParam, LPARAM lParam)
{
	CLISTEVENT *cle = (CLISTEVENT *)lParam;
	GGIMAGEENTRY *img = (GGIMAGEENTRY *)cle->lParam;

	debugLogA("img_recvimage(%x, %x): Popup new image.", wParam, lParam);
	if (!img) return FALSE;

	img_display(cle->hContact, img);

	return FALSE;
}

////////////////////////////////////////////////////////////////////////////
// Windows queue management
////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////
// Removes dat structure
//
int gg_img_remove(GGIMAGEDLGDATA *dat)
{
	GGIMAGEENTRY *temp = NULL, *img = NULL;
	GGPROTO *gg;

	if (!dat) return FALSE;
	gg = dat->gg;

	gg->gg_EnterCriticalSection(&gg->img_mutex, "gg_img_remove", 61, "img_mutex", 1);

	// Remove the structure
	img = dat->lpImages;

	// Destroy picture handle
	while (temp = img)
	{
		img = img->lpNext;
		gg_img_releasepicture(temp);
	}

	// Remove from list
	list_remove(&gg->imagedlgs, dat, 1);
	gg->gg_LeaveCriticalSection(&gg->img_mutex, "gg_img_remove", 61, 1, "img_mutex", 1);

	return TRUE;
}

////////////////////////////////////////////////////////////////////////////
//
GGIMAGEDLGDATA* gg_img_find(GGPROTO *gg, uin_t uin, uint32_t crc32)
{
	list_t l = gg->imagedlgs;
	GGIMAGEDLGDATA *dat;

	gg->gg_EnterCriticalSection(&gg->img_mutex, "gg_img_find", 62, "img_mutex", 1);
	while (l)
	{
		uin_t c_uin;

		dat = (GGIMAGEDLGDATA *)l->data;
		if (!dat) break;

		c_uin = dat->gg->getDword(dat->hContact, GG_KEY_UIN, 0);

		if (!dat->bReceiving && dat->lpImages && dat->lpImages->crc32 == crc32 && c_uin == uin)
		{
			gg->gg_LeaveCriticalSection(&gg->img_mutex, "gg_img_find", 62, 1, "img_mutex", 1);
			return dat;
		}

		l = l->next;
	}
	gg->gg_LeaveCriticalSection(&gg->img_mutex, "gg_img_find", 62, 2, "img_mutex", 1);

	gg->debugLogA("gg_img_find(): Image not found on the list. It might be released before calling this function.");
	return NULL;
}


////////////////////////////////////////////////////////////////////////////
// Image Module : Send on Request
//
BOOL GGPROTO::img_sendonrequest(gg_event* e)
{
	GGIMAGEDLGDATA *dat = gg_img_find(this, e->event.image_request.sender, e->event.image_request.crc32);
	if (!dat || !isonline())
		return FALSE;

	char* lpszFileNameA = mir_t2a(dat->lpImages->lpszFileName);
	gg_EnterCriticalSection(&sess_mutex, "img_sendonrequest", 63, "sess_mutex", 1);
	gg_image_reply(sess, e->event.image_request.sender, lpszFileNameA, dat->lpImages->lpData, dat->lpImages->nSize);
	gg_LeaveCriticalSection(&sess_mutex, "img_sendonrequest", 63, 1, "sess_mutex", 1);
	mir_free(lpszFileNameA);

	gg_img_remove(dat);

	return TRUE;
}

////////////////////////////////////////////////////////////////////////////
// Send Image : Run (Thread and main)
//
INT_PTR GGPROTO::img_sendimg(WPARAM hContact, LPARAM)
{
	GGIMAGEDLGDATA *dat = NULL;

	gg_EnterCriticalSection(&img_mutex, "img_sendimg", 64, "img_mutex", 1);
	if (!dat)
	{
		dat = (GGIMAGEDLGDATA *)calloc(1, sizeof(GGIMAGEDLGDATA));
		dat->hContact = hContact;
		dat->hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
		dat->gg = this;
		ResetEvent(dat->hEvent);

		// Create new dialog
#ifdef DEBUGMODE
		debugLogA("img_sendimg(): ForkThread 19 GGPROTO::img_dlgcallthread");
#endif
		ForkThread(&GGPROTO::img_dlgcallthread, dat);

		while (WaitForSingleObjectEx(dat->hEvent, INFINITE, TRUE) != WAIT_OBJECT_0);
		CloseHandle(dat->hEvent);
		dat->hEvent = NULL;

		list_add(&imagedlgs, dat, 0);
	}

	// Request choose dialog
	SendMessage(dat->hWnd, WM_CHOOSEIMG, 0, 0);
	gg_LeaveCriticalSection(&img_mutex, "img_sendimg", 64, 1, "img_mutex", 1);

	return 0;
}