/*
 * This file implements some Win32 OLE2 specific Drag and Drop structures.
 * Most of the code is a modification of the OLE2 SDK sample programs
 * "drgdrps" and "drgdrpt". If you want to see them, don't search at
 * Microsoft (I didn't find them there, seems they reorganised their servers)
 * but instead at
 *  http://ftp.se.kde.org/pub/vendor/microsoft/Softlib/MSLFILES/
 */

#if defined(_WIN32) && defined(USE_EXULTSTUDIO)

#	include "windrag.h"

#	include "ignore_unused_variable_warning.h"
#	include "u7drag.h"

#	include <cstring>
#	include <iostream>

static UINT CF_EXULT = RegisterClipboardFormat(U7_TARGET_DROPFILE_NAME_MIME);

// Just in case
#	ifndef DECLSPEC_NOTHROW
#		define DECLSPEC_NOTHROW
#	endif

// IDropTarget implementation

Windnd::Windnd(
		HWND hgwnd, Move_shape_handler_fun movefun,
		Move_combo_handler_fun movecmbfun, Drop_shape_handler_fun shapefun,
		Drop_chunk_handler_fun cfun, Drop_npc_handler_fun npcfun,
		Drop_combo_handler_fun combfun)
		: gamewin(hgwnd), move_shape_handler(movefun),
		  move_combo_handler(movecmbfun), shape_handler(shapefun),
		  chunk_handler(cfun), npc_handler(npcfun), combo_handler(combfun),
		  drag_id(-1) {
	std::memset(&data, 0, sizeof(data));
	m_cRef = 1;
}

DECLSPEC_NOTHROW STDMETHODIMP
		Windnd::QueryInterface(REFIID iid, void** ppvObject) {
	*ppvObject = nullptr;
	if (IID_IUnknown == iid || IID_IDropTarget == iid) {
		*ppvObject = this;
	}
	if (nullptr == *ppvObject) {
		return E_NOINTERFACE;
	}
	static_cast<LPUNKNOWN>(*ppvObject)->AddRef();
	return NOERROR;
}

DECLSPEC_NOTHROW STDMETHODIMP_(ULONG) Windnd::AddRef() {
	return ++m_cRef;
}

DECLSPEC_NOTHROW STDMETHODIMP_(ULONG) Windnd::Release() {
	if (0 != --m_cRef) {
		return m_cRef;
	}
	delete this;
	return 0;
}

DECLSPEC_NOTHROW STDMETHODIMP Windnd::DragEnter(
		IDataObject* pDataObject, DWORD grfKeyState, POINTL pt,
		DWORD* pdwEffect) {
	ignore_unused_variable_warning(grfKeyState, pt);
	if (!is_valid(pDataObject)) {
		*pdwEffect = DROPEFFECT_NONE;
	} else {
		*pdwEffect = DROPEFFECT_COPY;
	}

	std::memset(&data, 0, sizeof(data));

	FORMATETC fetc;
	fetc.cfFormat = CF_EXULT;
	fetc.ptd      = nullptr;
	fetc.dwAspect = DVASPECT_CONTENT;
	fetc.lindex   = -1;
	fetc.tymed    = TYMED_HGLOBAL;

	STGMEDIUM med;
	pDataObject->GetData(&fetc, &med);
	const unsigned char* dragdata
			= static_cast<unsigned char*>(GlobalLock(med.hGlobal));

	if (Is_u7_shapeid(dragdata)) {
		drag_id = U7_TARGET_SHAPEID;
		Get_u7_shapeid(
				dragdata, data.shape.file, data.shape.shape, data.shape.frame);
	} else if (Is_u7_chunkid(dragdata)) {
		drag_id = U7_TARGET_CHUNKID;
		Get_u7_chunkid(dragdata, data.chunk.chunknum);
	} else if (Is_u7_comboid(dragdata)) {
		drag_id = U7_TARGET_COMBOID;
		Get_u7_comboid(
				dragdata, data.combo.xtiles, data.combo.ytiles,
				data.combo.right, data.combo.below, data.combo.cnt,
				data.combo.combo);
	} else if (Is_u7_npcid(dragdata)) {
		drag_id = U7_TARGET_NPCID;
		Get_u7_npcid(dragdata, data.npc.npcnum);
	}

	GlobalUnlock(med.hGlobal);
	ReleaseStgMedium(&med);

	prevx = -1;
	prevy = -1;

	return S_OK;
}

DECLSPEC_NOTHROW STDMETHODIMP
		Windnd::DragOver(DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) {
	ignore_unused_variable_warning(grfKeyState);
	*pdwEffect = DROPEFFECT_COPY;
	// Todo

	POINT pnt = {pt.x, pt.y};
	ScreenToClient(gamewin, &pnt);

	switch (drag_id) {
	case U7_TARGET_SHAPEID:
		if (data.shape.file == U7_SHAPE_SHAPES) {
			if (move_shape_handler) {
				move_shape_handler(
						data.shape.shape, data.shape.frame, pnt.x, pnt.y, prevx,
						prevy, true);
			}
		}
		break;

	case U7_TARGET_COMBOID:
		if (data.combo.cnt > 0) {
			if (move_combo_handler) {
				move_combo_handler(
						data.combo.xtiles, data.combo.ytiles, data.combo.right,
						data.combo.below, pnt.x, pnt.y, prevx, prevy, true);
			}
		}
		break;

	default:
		break;
	}

	prevx = pnt.x;
	prevy = pnt.y;

	return S_OK;
}

DECLSPEC_NOTHROW STDMETHODIMP Windnd::DragLeave() {
	if (move_shape_handler) {
		move_shape_handler(-1, -1, 0, 0, prevx, prevy, true);
	}

	switch (drag_id) {
	case U7_TARGET_SHAPEID:
		break;
	case U7_TARGET_COMBOID:
		delete data.combo.combo;
		break;

	default:
		break;
	}
	std::memset(&data, 0, sizeof(data));
	drag_id = -1;

	return S_OK;
}

DECLSPEC_NOTHROW STDMETHODIMP Windnd::Drop(
		IDataObject* pDataObject, DWORD grfKeyState, POINTL pt,
		DWORD* pdwEffect) {
	ignore_unused_variable_warning(grfKeyState);
	*pdwEffect = DROPEFFECT_COPY;

	// retrieve the dragged data
	FORMATETC fetc;
	fetc.cfFormat = CF_EXULT;
	fetc.ptd      = nullptr;
	fetc.dwAspect = DVASPECT_CONTENT;
	fetc.lindex   = -1;
	fetc.tymed    = TYMED_HGLOBAL;

	STGMEDIUM med;
	pDataObject->GetData(&fetc, &med);
	const unsigned char* dragdata
			= static_cast<unsigned char*>(GlobalLock(med.hGlobal));

	POINT pnt = {pt.x, pt.y};
	ScreenToClient(gamewin, &pnt);

	if (Is_u7_shapeid(dragdata)) {
		int file;
		int shape;
		int frame;
		Get_u7_shapeid(dragdata, file, shape, frame);
		std::cerr << "(Exult) Dropping SHAPE at " << pt.x << " " << pt.y
				  << " for " << file << " " << shape << " " << frame
				  << std::endl;
		if (file == U7_SHAPE_SHAPES) {
			if (shape_handler) {
				(*shape_handler)(shape, frame, pnt.x, pnt.y);
			}
		}
	} else if (Is_u7_npcid(dragdata)) {
		int npcnum;
		Get_u7_npcid(dragdata, npcnum);
		std::cerr << "(Exult) Dropping NPC at " << pt.x << " " << pt.y
				  << " for " << npcnum << std::endl;
		if (npc_handler) {
			(*npc_handler)(npcnum, pnt.x, pnt.y);
		}
	} else if (Is_u7_chunkid(dragdata)) {
		int chunknum;
		Get_u7_chunkid(dragdata, chunknum);
		std::cerr << "(Exult) Dropping CHUNK at " << pt.x << " " << pt.y
				  << " for " << chunknum << std::endl;
		if (chunk_handler) {
			(*chunk_handler)(chunknum, pnt.x, pnt.y);
		}
	} else if (Is_u7_comboid(dragdata)) {
		int            xtiles;
		int            ytiles;
		int            right;
		int            below;
		int            cnt;
		U7_combo_data* combo;
		Get_u7_comboid(dragdata, xtiles, ytiles, right, below, cnt, combo);
		std::cerr << "(Exult) Dropping COMBO at " << pt.x << " " << pt.y
				  << " for " << xtiles << " " << ytiles << " " << right << " "
				  << below << " " << cnt << std::endl;
		if (combo_handler) {
			(*combo_handler)(cnt, combo, pnt.x, pnt.y);
		}
		delete combo;
	}

	GlobalUnlock(med.hGlobal);
	ReleaseStgMedium(&med);

	return S_OK;
}

bool Windnd::is_valid(IDataObject* pDataObject) {
	FORMATETC fetc;
	fetc.cfFormat = CF_EXULT;
	fetc.ptd      = nullptr;
	fetc.dwAspect = DVASPECT_CONTENT;
	fetc.lindex   = -1;
	fetc.tymed    = TYMED_HGLOBAL;

	return SUCCEEDED(pDataObject->QueryGetData(&fetc));
}

#endif
