/*

Miranda IM: the free IM client for Microsoft* Windows*

Copyright 2000-2009 Miranda ICQ/IM project, 
all portions of this codebase are copyrighted to the people 
listed in contributors.txt.

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 "commonheaders.h"
#include "clc.h"
#include <shlobj.h>

struct CDropTarget : IDropTarget
{
	LONG refCount;
	IDropTargetHelper *pDropTargetHelper;

	ULONG STDMETHODCALLTYPE AddRef(void); 
	ULONG STDMETHODCALLTYPE Release(void);
	HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid, void ** ppvObject);

    HRESULT STDMETHODCALLTYPE DragEnter(IDataObject *pDataObj, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect);
    HRESULT STDMETHODCALLTYPE DragOver(DWORD grfKeyState, POINTL pt, DWORD *pdwEffect);
    HRESULT STDMETHODCALLTYPE DragLeave(void);
    HRESULT STDMETHODCALLTYPE Drop(IDataObject *pDataObj, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect);
}
static dropTarget;

static HWND hwndCurrentDrag = NULL;
static int originalSelection;

HRESULT CDropTarget::QueryInterface(REFIID riid, LPVOID * ppvObj)
{
	if (riid == IID_IDropTarget) {
		*ppvObj = this;
		AddRef();
		return S_OK;
	}
	*ppvObj = NULL;
	return E_NOINTERFACE;
}

ULONG CDropTarget::AddRef(void)
{
	return InterlockedIncrement(&refCount);
}

ULONG CDropTarget::Release(void)
{
	if (refCount == 1) {
		if (pDropTargetHelper)
			pDropTargetHelper->Release();
	}
	return InterlockedDecrement(&refCount);
}

static HANDLE HContactFromPoint(HWND hwnd, struct ClcData *dat, int x, int y, int *hitLine)
{
	int hit;
	struct ClcContact *contact;
	DWORD hitFlags;
	char *szProto;
	DWORD protoCaps;

	hit = cli.pfnHitTest(hwnd, dat, x, y, &contact, NULL, &hitFlags);
	if (hit == -1 || !(hitFlags & (CLCHT_ONITEMLABEL | CLCHT_ONITEMICON)) || contact->type != CLCIT_CONTACT)
		return NULL;
	szProto = (char *) CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM) contact->hContact, 0);
	if (szProto == NULL)
		return NULL;
	protoCaps = CallProtoService(szProto, PS_GETCAPS, PFLAGNUM_1, 0);
	if (!(protoCaps & PF1_FILESEND))
		return NULL;
	if (ID_STATUS_OFFLINE == DBGetContactSettingWord(contact->hContact, szProto, "Status", ID_STATUS_OFFLINE))
		return NULL;
	if (hitLine)
		*hitLine = hit;
	return contact->hContact;
}

HRESULT CDropTarget::DragOver(DWORD /*grfKeyState*/, POINTL pt, DWORD * pdwEffect)
{
	POINT shortPt;
	struct ClcData *dat;
	RECT clRect;
	int hit;
	HANDLE hContact;

	if (pDropTargetHelper && hwndCurrentDrag)
		pDropTargetHelper->DragOver((POINT*)&pt, *pdwEffect);

	*pdwEffect = 0;
	if (hwndCurrentDrag == NULL) {
		*pdwEffect = DROPEFFECT_NONE;
		return S_OK;
	}
	CallService(MS_CLIST_PAUSEAUTOHIDE, 0, 0);
	dat = (struct ClcData *) GetWindowLongPtr(hwndCurrentDrag, 0);
	shortPt.x = pt.x;
	shortPt.y = pt.y;
	ScreenToClient(hwndCurrentDrag, &shortPt);
	GetClientRect(hwndCurrentDrag, &clRect);

	if (shortPt.y < dat->dragAutoScrollHeight || shortPt.y >= clRect.bottom - dat->dragAutoScrollHeight) {
		*pdwEffect |= DROPEFFECT_SCROLL;
		cli.pfnScrollTo(hwndCurrentDrag, dat, dat->yScroll + (shortPt.y < dat->dragAutoScrollHeight ? -1 : 1) * dat->rowHeight * 2, 0);
	}
	hContact = HContactFromPoint(hwndCurrentDrag, dat, shortPt.x, shortPt.y, &hit);
	if (hContact == NULL) {
		hit = -1;
		*pdwEffect |= DROPEFFECT_NONE;
	}
	else
		*pdwEffect |= DROPEFFECT_COPY;

	if (dat->selection != hit) {
		dat->selection = hit;
		cli.pfnInvalidateRect(hwndCurrentDrag, NULL, FALSE);
		if (pDropTargetHelper) pDropTargetHelper->Show(FALSE);
		UpdateWindow(hwndCurrentDrag);
		if (pDropTargetHelper) pDropTargetHelper->Show(TRUE);
	}

	return S_OK;
}

HRESULT CDropTarget::DragEnter(IDataObject *pDataObj, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect)
{
	HWND hwnd;
	TCHAR szWindowClass[64];
	POINT shortPt;

	shortPt.x = pt.x;
	shortPt.y = pt.y;
	hwnd = WindowFromPoint(shortPt);
	GetClassName(hwnd, szWindowClass, SIZEOF(szWindowClass));
	if (!lstrcmp(szWindowClass, CLISTCONTROL_CLASS)) {
		struct ClcData *dat;
		hwndCurrentDrag = hwnd;
		dat = (struct ClcData *) GetWindowLongPtr(hwndCurrentDrag, 0);
		originalSelection = dat->selection;
		dat->showSelAlways = 1;
	}
	if (pDropTargetHelper && hwndCurrentDrag)
		pDropTargetHelper->DragEnter(hwndCurrentDrag, pDataObj, (POINT*)&pt, *pdwEffect);
	return DragOver(grfKeyState, pt, pdwEffect);
}

HRESULT CDropTarget::DragLeave(void)
{
	if (hwndCurrentDrag) {
		struct ClcData *dat;
		if (pDropTargetHelper)
			pDropTargetHelper->DragLeave();
		dat = (struct ClcData *) GetWindowLongPtr(hwndCurrentDrag, 0);
		dat->showSelAlways = 0;
		dat->selection = originalSelection;
		cli.pfnInvalidateRect(hwndCurrentDrag, NULL, FALSE);
	}
	hwndCurrentDrag = NULL;
	return S_OK;
}

static void AddToFileList(TCHAR ***pppFiles, int *totalCount, const TCHAR *szFilename)
{
	*pppFiles = (TCHAR **) mir_realloc(*pppFiles, (++*totalCount + 1) * sizeof(TCHAR *));
	(*pppFiles)[*totalCount] = NULL;
	(*pppFiles)[*totalCount - 1] = mir_tstrdup(szFilename);
	if (GetFileAttributes(szFilename) & FILE_ATTRIBUTE_DIRECTORY) {
		WIN32_FIND_DATA fd;
		HANDLE hFind;
		TCHAR szPath[MAX_PATH];
		lstrcpy(szPath, szFilename);
		lstrcat(szPath, _T("\\*"));
		if (hFind = FindFirstFile(szPath, &fd)) {
			do {
				if (!lstrcmp(fd.cFileName, _T(".")) || !lstrcmp(fd.cFileName, _T("..")))
					continue;
				lstrcpy(szPath, szFilename);
				lstrcat(szPath, _T("\\"));
				lstrcat(szPath, fd.cFileName);
				AddToFileList(pppFiles, totalCount, szPath);
			} while (FindNextFile(hFind, &fd));
			FindClose(hFind);
		}
	}
}

HRESULT CDropTarget::Drop(IDataObject * pDataObj, DWORD /*fKeyState*/, POINTL pt, DWORD * pdwEffect)
{
	FORMATETC fe = { CF_HDROP, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
	STGMEDIUM stg;
	HDROP hDrop;
	POINT shortPt;
	struct ClcData *dat;
	HANDLE hContact;

	if (pDropTargetHelper && hwndCurrentDrag)
		pDropTargetHelper->Drop(pDataObj, (POINT*)&pt, *pdwEffect);

	*pdwEffect = DROPEFFECT_NONE;
	if (hwndCurrentDrag == NULL || S_OK != pDataObj->GetData(&fe, &stg))
		return S_OK;
	hDrop = (HDROP) stg.hGlobal;
	dat = (struct ClcData *) GetWindowLongPtr(hwndCurrentDrag, 0);

	shortPt.x = pt.x;
	shortPt.y = pt.y;
	ScreenToClient(hwndCurrentDrag, &shortPt);
	hContact = HContactFromPoint(hwndCurrentDrag, dat, shortPt.x, shortPt.y, NULL);
	if (hContact != NULL) {
		TCHAR **ppFiles = NULL;
		TCHAR szFilename[MAX_PATH];
		int fileCount, totalCount = 0, i;

		fileCount = DragQueryFile(hDrop, -1, NULL, 0);
		ppFiles = NULL;
		for (i = 0; i < fileCount; i++) {
			DragQueryFile(hDrop, i, szFilename, SIZEOF(szFilename));
			AddToFileList(&ppFiles, &totalCount, szFilename);
		}

		if (!CallService(MS_FILE_SENDSPECIFICFILEST, (WPARAM) hContact, (LPARAM) ppFiles))
			*pdwEffect = DROPEFFECT_COPY;

		for (i = 0; ppFiles[i]; i++)
			mir_free(ppFiles[i]);
		mir_free(ppFiles);
	}

	if (stg.pUnkForRelease)
		stg.pUnkForRelease->Release();
	else
		GlobalFree(stg.hGlobal);

	DragLeave();
	return S_OK;
}

static VOID CALLBACK CreateDropTargetHelperTimerProc(HWND hwnd, UINT, UINT_PTR idEvent, DWORD)
{
	KillTimer(hwnd, idEvent);
	//This is a ludicrously slow function (~200ms) so we delay load it a bit.
	if (S_OK != CoCreateInstance(CLSID_DragDropHelper, NULL, CLSCTX_INPROC_SERVER, 
		IID_IDropTargetHelper, (LPVOID*)&dropTarget.pDropTargetHelper))
		dropTarget.pDropTargetHelper = NULL;
}

void InitFileDropping(void)
{
	// Disabled as this function loads tons of dlls for no apparenet reason
	// we will se what the reaction will be
//	SetTimer(NULL, 1, 1000, CreateDropTargetHelperTimerProc);
}

void fnRegisterFileDropping(HWND hwnd)
{
	RegisterDragDrop(hwnd, (IDropTarget *) & dropTarget);
}

void fnUnregisterFileDropping(HWND hwnd)
{
	RevokeDragDrop(hwnd);
}
