/**
 * @file lldragdrop32.cpp
 * @brief Handler for Windows specific drag and drop (OS to client) code
 *
 * $LicenseInfo:firstyear=2001&license=viewerlgpl$
 * Second Life Viewer Source Code
 * Copyright (C) 2010, Linden Research, Inc.
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation;
 * version 2.1 of the License only.
 * 
 * This library 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
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 * 
 * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
 * $/LicenseInfo$
 */

#if LL_WINDOWS

#if LL_OS_DRAGDROP_ENABLED

#include "linden_common.h"

#include "llwindowwin32.h"
#include "llkeyboardwin32.h"
#include "llwindowcallbacks.h"
#include "lldragdropwin32.h"

class LLDragDropWin32Target: 
	public IDropTarget
{
	public:
		////////////////////////////////////////////////////////////////////////////////
		//
		LLDragDropWin32Target( HWND  hWnd ) :
			mRefCount( 1 ),
			mAppWindowHandle( hWnd ),
			mAllowDrop(false),
			mIsSlurl(false)
		{
		};

		virtual ~LLDragDropWin32Target()
		{
		};

		////////////////////////////////////////////////////////////////////////////////
		//
		ULONG __stdcall AddRef( void ) override
		{
			return InterlockedIncrement( &mRefCount );
		};

		////////////////////////////////////////////////////////////////////////////////
		//
		ULONG __stdcall Release( void ) override
		{
			LONG count = InterlockedDecrement( &mRefCount );
				
			if ( count == 0 )
			{
				delete this;
				return 0;
			}
			else
			{
				return count;
			};
		};

		////////////////////////////////////////////////////////////////////////////////
		//
		HRESULT __stdcall QueryInterface( REFIID iid, void** ppvObject ) override
		{
			if ( iid == IID_IUnknown || iid == IID_IDropTarget )
			{
				AddRef();
				*ppvObject = this;
				return S_OK;
			}
			else
			{
				*ppvObject = nullptr;
				return E_NOINTERFACE;
			};
		};

		////////////////////////////////////////////////////////////////////////////////
		//
		HRESULT __stdcall DragEnter( IDataObject* pDataObject, DWORD grfKeyState, POINTL pt, DWORD* pdwEffect ) override
		{
			FORMATETC fmtetc = { CF_TEXT, nullptr, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };

			// support CF_TEXT using a HGLOBAL?
			if ( S_OK == pDataObject->QueryGetData( &fmtetc ) )
			{
				mAllowDrop = true;
				mDropUrl = std::string();
				mIsSlurl = false;

				STGMEDIUM stgmed;
				if( S_OK == pDataObject->GetData( &fmtetc, &stgmed ) )
				{
					PVOID data = GlobalLock( stgmed.hGlobal );
					mDropUrl = std::string( (char*)data );
					// XXX MAJOR MAJOR HACK!
					LLWindowWin32 *window_imp = (LLWindowWin32 *)GetWindowLongPtr(mAppWindowHandle, GWLP_USERDATA); // <alchemy/>
					if (nullptr != window_imp)
					{
						LLCoordGL gl_coord( 0, 0 );

						POINT pt2;
						pt2.x = pt.x;
						pt2.y = pt.y;
						ScreenToClient( mAppWindowHandle, &pt2 );

						LLCoordWindow cursor_coord_window( pt2.x, pt2.y );
						MASK mask = gKeyboard->currentMask(TRUE);

						LLWindowCallbacks::DragNDropResult result = window_imp->completeDragNDropRequest( cursor_coord_window.convert(), mask, 
							LLWindowCallbacks::DNDA_START_TRACKING, mDropUrl );

						switch (result)
						{
						case LLWindowCallbacks::DND_COPY:
							*pdwEffect = DROPEFFECT_COPY;
							break;
						case LLWindowCallbacks::DND_LINK:
							*pdwEffect = DROPEFFECT_LINK;
							break;
						case LLWindowCallbacks::DND_MOVE:
							*pdwEffect = DROPEFFECT_MOVE;
							break;
						case LLWindowCallbacks::DND_NONE:
						default:
							*pdwEffect = DROPEFFECT_NONE;
							break;
						}
					};

					GlobalUnlock( stgmed.hGlobal );
					ReleaseStgMedium( &stgmed );
				};
				SetFocus( mAppWindowHandle );
			}
			else
			{
				mAllowDrop = false;
				*pdwEffect = DROPEFFECT_NONE;
			};

			return S_OK;
		};

		////////////////////////////////////////////////////////////////////////////////
		//
		HRESULT __stdcall DragOver( DWORD grfKeyState, POINTL pt, DWORD* pdwEffect ) override
		{
			if ( mAllowDrop )
			{
				// XXX MAJOR MAJOR HACK!
				LLWindowWin32 *window_imp = (LLWindowWin32 *)GetWindowLongPtr(mAppWindowHandle, GWLP_USERDATA); // <alchemy/>
				if (nullptr != window_imp)
				{
					LLCoordGL gl_coord( 0, 0 );

					POINT pt2;
					pt2.x = pt.x;
					pt2.y = pt.y;
					ScreenToClient( mAppWindowHandle, &pt2 );

					LLCoordWindow cursor_coord_window( pt2.x, pt2.y );
					MASK mask = gKeyboard->currentMask(TRUE);

					LLWindowCallbacks::DragNDropResult result = window_imp->completeDragNDropRequest( cursor_coord_window.convert(), mask, 
						LLWindowCallbacks::DNDA_TRACK, mDropUrl );
					
					switch (result)
					{
					case LLWindowCallbacks::DND_COPY:
						*pdwEffect = DROPEFFECT_COPY;
						break;
					case LLWindowCallbacks::DND_LINK:
						*pdwEffect = DROPEFFECT_LINK;
						break;
					case LLWindowCallbacks::DND_MOVE:
						*pdwEffect = DROPEFFECT_MOVE;
						break;
					case LLWindowCallbacks::DND_NONE:
					default:
						*pdwEffect = DROPEFFECT_NONE;
						break;
					}
				};
			}
			else
			{
				*pdwEffect = DROPEFFECT_NONE;
			};

			return S_OK;
		};

		////////////////////////////////////////////////////////////////////////////////
		//
		HRESULT __stdcall DragLeave( void ) override
		{
			// XXX MAJOR MAJOR HACK!
			LLWindowWin32 *window_imp = (LLWindowWin32 *)GetWindowLongPtr(mAppWindowHandle, GWLP_USERDATA); // <alchemy/>
			if (nullptr != window_imp)
			{
				LLCoordGL gl_coord( 0, 0 );
				MASK mask = gKeyboard->currentMask(TRUE);
				window_imp->completeDragNDropRequest( gl_coord, mask, LLWindowCallbacks::DNDA_STOP_TRACKING, mDropUrl );
			};
			return S_OK;
		};

		////////////////////////////////////////////////////////////////////////////////
		//
		HRESULT __stdcall Drop( IDataObject* pDataObject, DWORD grfKeyState, POINTL pt, DWORD* pdwEffect ) override
		{
			if ( mAllowDrop )
			{
				// window impl stored in Window data (neat!)
				LLWindowWin32 *window_imp = (LLWindowWin32 *)GetWindowLongPtr( mAppWindowHandle, GWLP_USERDATA ); // <alchemy/>
				if (nullptr != window_imp )
				{
					POINT pt_client;
					pt_client.x = pt.x;
					pt_client.y = pt.y;
					ScreenToClient( mAppWindowHandle, &pt_client );

					LLCoordWindow cursor_coord_window( pt_client.x, pt_client.y );
					LLCoordGL gl_coord(cursor_coord_window.convert());
					LL_INFOS() << "### (Drop) URL is: " << mDropUrl << LL_ENDL;
					LL_INFOS() << "###        raw coords are: " << pt.x << " x " << pt.y << LL_ENDL;
					LL_INFOS() << "###	    client coords are: " << pt_client.x << " x " << pt_client.y << LL_ENDL;
					LL_INFOS() << "###         GL coords are: " << gl_coord.mX << " x " << gl_coord.mY << LL_ENDL;
					LL_INFOS() << LL_ENDL;

					// no keyboard modifier option yet but we could one day
					MASK mask = gKeyboard->currentMask( TRUE );

					// actually do the drop
					LLWindowCallbacks::DragNDropResult result = window_imp->completeDragNDropRequest( gl_coord, mask, 
						LLWindowCallbacks::DNDA_DROPPED, mDropUrl );

					switch (result)
					{
					case LLWindowCallbacks::DND_COPY:
						*pdwEffect = DROPEFFECT_COPY;
						break;
					case LLWindowCallbacks::DND_LINK:
						*pdwEffect = DROPEFFECT_LINK;
						break;
					case LLWindowCallbacks::DND_MOVE:
						*pdwEffect = DROPEFFECT_MOVE;
						break;
					case LLWindowCallbacks::DND_NONE:
					default:
						*pdwEffect = DROPEFFECT_NONE;
						break;
					}
				};
			}
			else
			{
				*pdwEffect = DROPEFFECT_NONE;
			};

			return S_OK;
		};

	////////////////////////////////////////////////////////////////////////////////
	//
	private:
		LONG mRefCount;
		HWND mAppWindowHandle;
		bool mAllowDrop;
		std::string mDropUrl;
		bool mIsSlurl;
		friend class LLWindowWin32;
};

////////////////////////////////////////////////////////////////////////////////
//
LLDragDropWin32::LLDragDropWin32() :
	mDropTarget(nullptr ),
	mDropWindowHandle(nullptr )

{
}

////////////////////////////////////////////////////////////////////////////////
//
LLDragDropWin32::~LLDragDropWin32()
{
}

////////////////////////////////////////////////////////////////////////////////
//
bool LLDragDropWin32::init( HWND hWnd )
{
	if ( NOERROR != OleInitialize(nullptr ) )
		return false; 

	try
	{
		mDropTarget = new LLDragDropWin32Target(hWnd);
	}
	catch (const std::bad_alloc& e)
	{
		LL_WARNS() << "Failed to allocate drag drop target with exception: " << e.what() << LL_ENDL;
		return false;
	}
	if ( mDropTarget )
	{
		HRESULT result = CoLockObjectExternal( mDropTarget, TRUE, FALSE );
		if ( S_OK == result )
		{
			result = RegisterDragDrop( hWnd, mDropTarget );
			if ( S_OK != result )
			{
				// RegisterDragDrop failed
				return false;
			};

			// all ok
			mDropWindowHandle = hWnd;
		}
		else
		{
			// Unable to lock OLE object
			return false;
		};
	};

	// success
	return true;
}

////////////////////////////////////////////////////////////////////////////////
//
void LLDragDropWin32::reset()
{
	if ( mDropTarget )
	{
		RevokeDragDrop( mDropWindowHandle );
		CoLockObjectExternal( mDropTarget, FALSE, TRUE );
		mDropTarget->Release();  
	};
	
	OleUninitialize();
}

#endif // LL_OS_DRAGDROP_ENABLED

#endif // LL_WINDOWS

