/**************************************************************
 * 
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 * 
 *************************************************************/



// MARKER(update_precomp.py): autogen include statement, do not remove
#include "precompiled_dtrans.hxx"
#include <com/sun/star/datatransfer/dnd/DNDConstants.hpp>
#include <com/sun/star/datatransfer/XTransferable.hpp>
#include <rtl/unload.h>

#include <stdio.h>
#include "target.hxx"
#include "idroptarget.hxx"
#include "globals.hxx"
#include "targetdropcontext.hxx"
#include "targetdragcontext.hxx"
#include <rtl/ustring.h>
using namespace rtl;
using namespace cppu;
using namespace osl;
using namespace com::sun::star::datatransfer;
using namespace com::sun::star::datatransfer::dnd;
using namespace com::sun::star::datatransfer::dnd::DNDConstants;

#define WM_REGISTERDRAGDROP WM_USER + 1
#define WM_REVOKEDRAGDROP WM_USER + 2
//--> TRA
extern Reference< XTransferable > g_XTransferable;

//<-- TRA

extern rtl_StandardModuleCount g_moduleCount;
DWORD WINAPI DndTargetOleSTAFunc(LPVOID pParams);

DropTarget::DropTarget( const Reference<XMultiServiceFactory>& sf):
	m_hWnd( NULL),
	m_serviceFactory( sf),
	WeakComponentImplHelper3<XInitialization,XDropTarget, XServiceInfo>(m_mutex),
	m_bActive(sal_True),
	m_nDefaultActions(ACTION_COPY|ACTION_MOVE|ACTION_LINK|ACTION_DEFAULT),
	m_nCurrentDropAction( ACTION_NONE),
	m_oleThreadId( 0),
	m_pDropTarget( NULL),
	m_threadIdWindow(0),
	m_threadIdTarget(0),
	m_hOleThread(0),
	m_nLastDropAction(0)


{
	g_moduleCount.modCnt.acquire( &g_moduleCount.modCnt );
}


DropTarget::~DropTarget()
{
	g_moduleCount.modCnt.release( &g_moduleCount.modCnt );

}
// called from WeakComponentImplHelperX::dispose
// WeakComponentImplHelper calls disposing before it destroys
// itself.
// NOTE: RevokeDragDrop decrements the ref count on the IDropTarget
// interface. (m_pDropTarget)
// If the HWND is invalid then it doesn't decrement and
// the IDropTarget object will live on. MEMORY LEAK
void SAL_CALL DropTarget::disposing()
{
	HRESULT hr= S_OK;
	if( m_threadIdTarget)
	{
        // Call RevokeDragDrop and wait for the OLE thread to die;
		PostThreadMessage( m_threadIdTarget, WM_REVOKEDRAGDROP, (WPARAM)this, 0);
		WaitForSingleObject( m_hOleThread, INFINITE);
		CloseHandle( m_hOleThread);
		//OSL_ENSURE( SUCCEEDED( hr), "HWND not valid!" );
	}
	else
	{
		hr= RevokeDragDrop( m_hWnd);
		m_hWnd= 0;
	}
	if( m_pDropTarget)
    {
        CoLockObjectExternal( m_pDropTarget, FALSE, TRUE);
        m_pDropTarget->Release();
    }

	if( m_oleThreadId)
    {
        if( m_oleThreadId == CoGetCurrentProcess() )
            OleUninitialize();
    }

}

void SAL_CALL DropTarget::initialize( const Sequence< Any >& aArguments )
		throw(Exception, RuntimeException)
{
    // The window must be registered for Dnd by RegisterDragDrop. We must ensure
	// that RegisterDragDrop is called from an STA ( OleInitialize) thread.
	// As long as the window is registered we need to receive OLE messages in
	// an OLE thread. That is to say, if DropTarget::initialize was called from an
	// MTA thread then we create an OLE thread in which the window is registered.
    // The thread will stay alive until aver RevokeDragDrop has been called.

	// Additionally even if RegisterDragDrop is called from an STA thread we have
	// to ensure that it is called from the same thread that created the Window
	// otherwise meesages sent during DND won't reach the windows message queue.
	// Calling AttachThreadInput first would resolve this problem but would block
	// the message queue of the calling thread. So if the current thread 
	// (even if it's an STA thread) and the thread that created the window are not
	// identical we need to create a new thread as we do when the calling thread is
	// an MTA thread.
	
	if( aArguments.getLength() > 0)
	{
		// Get the window handle from aArgument. It is needed for RegisterDragDrop.
		m_hWnd= *(HWND*)aArguments[0].getValue();
		OSL_ASSERT( IsWindow( m_hWnd) );

		// Obtain the id of the thread that created the window
		m_threadIdWindow= GetWindowThreadProcessId( m_hWnd, NULL);

		HRESULT hr= OleInitialize( NULL);

		// Current thread is MTA or Current thread and Window thread are not identical
		if( hr == RPC_E_CHANGED_MODE || GetCurrentThreadId() != m_threadIdWindow  )
		{
			OSL_ENSURE( ! m_threadIdTarget,"initialize was called twice");
			// create the IDropTargetImplementation
			m_pDropTarget= new IDropTargetImpl( *static_cast<DropTarget*>( this) );
			m_pDropTarget->AddRef();


			// Obtain the id of the thread that created the window
			m_threadIdWindow= GetWindowThreadProcessId( m_hWnd, NULL);
			// The event is set by the thread that we will create momentarily.
			// It indicates that the thread is ready to receive messages.
			HANDLE m_evtThreadReady= CreateEvent( NULL, FALSE, FALSE, NULL);

			m_hOleThread= CreateThread( NULL, 0, (LPTHREAD_START_ROUTINE)DndTargetOleSTAFunc,
											&m_evtThreadReady, 0, &m_threadIdTarget);
			WaitForSingleObject( m_evtThreadReady, INFINITE);
			CloseHandle( m_evtThreadReady);
			PostThreadMessage( m_threadIdTarget, WM_REGISTERDRAGDROP, (WPARAM)static_cast<DropTarget*>(this), 0);
		}
		else if( hr == S_OK || hr == S_FALSE)
		{
			// current thread is STA
			// If OleInitialize has been called by the caller then we must not call
			// OleUninitialize
			if( hr == S_OK)
			{
				// caller did not call OleInitialize, so we call OleUninitialize
				// remember the thread that will call OleUninitialize
				m_oleThreadId= CoGetCurrentProcess(); // get a unique thread id
			}

			// Get the window handle from aArgument. It is needed for RegisterDragDrop.
			// create the IDropTargetImplementation
			m_pDropTarget= new IDropTargetImpl( *static_cast<DropTarget*>( this) );
			m_pDropTarget->AddRef();
			// CoLockObjectExternal is prescribed by the protocol. It bumps up the ref count
			if( SUCCEEDED( CoLockObjectExternal( m_pDropTarget, TRUE, FALSE)))
			{
				if( FAILED( RegisterDragDrop( m_hWnd,  m_pDropTarget) ) )
				{
					// do clean up if drag and drop is not possible
					CoLockObjectExternal( m_pDropTarget, FALSE, FALSE);
					m_pDropTarget->Release();
					m_hWnd= NULL;
				}
			}
		}
		else
			throw Exception();

	}
}

// This function is called as extra thread from DragSource::startDrag.
// The function carries out a drag and drop operation by calling
// DoDragDrop. The thread also notifies all XSourceListener.
DWORD WINAPI DndTargetOleSTAFunc(LPVOID pParams)
{
	HRESULT hr= OleInitialize( NULL);
	if( SUCCEEDED( hr) )
	{
		MSG msg;
		// force the creation of a message queue
		PeekMessage( &msg, (HWND)NULL, 0, 0, PM_NOREMOVE);
		// Signal the creator ( DropTarget::initialize) that the thread is
		// ready to receive messages.
		SetEvent( *(HANDLE*) pParams);
		// Thread id is needed for attaching this message queue to the one of the
		// thread where the window was created.
		DWORD threadId= GetCurrentThreadId();
		// We force the creation of a thread message queue. This is necessary
		// for a later call to AttachThreadInput
		while( GetMessage(&msg, (HWND)NULL, 0, 0) )
		{
			if( msg.message == WM_REGISTERDRAGDROP)
			{
				DropTarget *pTarget= (DropTarget*)msg.wParam;
				// This thread is attached to the thread that created the window. Hence
				// this thread also receives all mouse and keyboard messages which are
				// needed
				AttachThreadInput( threadId , pTarget->m_threadIdWindow, TRUE );

				if( SUCCEEDED( CoLockObjectExternal(pTarget-> m_pDropTarget, TRUE, FALSE)))
				{
					if( FAILED( RegisterDragDrop( pTarget-> m_hWnd, pTarget-> m_pDropTarget) ) )
					{
						// do clean up if drag and drop is not possible
						CoLockObjectExternal( pTarget->m_pDropTarget, FALSE, FALSE);
						pTarget->m_pDropTarget->Release();
						pTarget->m_hWnd= NULL;
					}
				}
			}
			else if( msg.message == WM_REVOKEDRAGDROP)
			{
				DropTarget *pTarget= (DropTarget*)msg.wParam;
				RevokeDragDrop( pTarget-> m_hWnd);
				// Detach this thread from the window thread
				AttachThreadInput( threadId, pTarget->m_threadIdWindow, FALSE);
				pTarget->m_hWnd= 0;
				break;
			}
			TranslateMessage(  &msg);
			DispatchMessage( &msg);
		}
		OleUninitialize();
	}
	return 0;
}




// XServiceInfo
OUString SAL_CALL DropTarget::getImplementationName(  ) throw (RuntimeException)
{
    return OUString(RTL_CONSTASCII_USTRINGPARAM(DNDTARGET_IMPL_NAME));;
}
// XServiceInfo
sal_Bool SAL_CALL DropTarget::supportsService( const OUString& ServiceName ) throw (RuntimeException)
{
    if( ServiceName.equals(OUString(RTL_CONSTASCII_USTRINGPARAM(DNDTARGET_SERVICE_NAME ))))
        return sal_True;
    return sal_False;
}

Sequence< OUString > SAL_CALL DropTarget::getSupportedServiceNames(  ) throw (RuntimeException)
{
    OUString names[1]= {OUString(RTL_CONSTASCII_USTRINGPARAM(DNDTARGET_SERVICE_NAME))};
    return Sequence<OUString>(names, 1);
}


// XDropTarget ----------------------------------------------------------------
void SAL_CALL DropTarget::addDropTargetListener( const Reference< XDropTargetListener >& dtl )
		throw(RuntimeException)
{
	rBHelper.addListener( ::getCppuType( &dtl ), dtl );
}

void SAL_CALL DropTarget::removeDropTargetListener( const Reference< XDropTargetListener >& dtl )
		throw(RuntimeException)
{
	rBHelper.removeListener( ::getCppuType( &dtl ), dtl );
}

sal_Bool SAL_CALL DropTarget::isActive(  ) throw(RuntimeException)
{
	return m_bActive; //m_bDropTargetRegistered;
}


void SAL_CALL DropTarget::setActive( sal_Bool _b ) throw(RuntimeException)
{
	MutexGuard g(m_mutex);
    m_bActive= _b;
}


sal_Int8 SAL_CALL DropTarget::getDefaultActions(  ) throw(RuntimeException)
{
	return m_nDefaultActions;
}

void SAL_CALL DropTarget::setDefaultActions( sal_Int8 actions ) throw(RuntimeException)
{
	OSL_ENSURE( actions < 8, "No valid default actions");
	m_nDefaultActions= actions;
}


HRESULT DropTarget::DragEnter( IDataObject *pDataObj,
									DWORD grfKeyState,
									POINTL pt,
									DWORD  *pdwEffect)
{
#if defined DBG_CONSOLE_OUT
	printf("\nDropTarget::DragEnter state: %x effect %d", grfKeyState, *pdwEffect);
#endif
    if( m_bActive )
    {
	    // Intersection of pdwEffect and the allowed actions ( setDefaultActions)
	    m_nCurrentDropAction= getFilteredActions( grfKeyState, *pdwEffect);
		// m_nLastDropAction has to be set by a listener. If no listener calls
		//XDropTargetDragContext::acceptDrag and specifies an action then pdwEffect
		// will be DROPEFFECT_NONE throughout
		m_nLastDropAction= ACTION_DEFAULT | ACTION_MOVE;

	    m_currentDragContext= static_cast<XDropTargetDragContext*>( new TargetDragContext(
		    static_cast<DropTarget*>(this) ) );

		//--> TRA

		// shortcut
		if ( g_XTransferable.is( ) )
			m_currentData = g_XTransferable;
		else
		{
			// Convert the IDataObject to a XTransferable
			m_currentData= m_aDataConverter.createTransferableFromDataObj(
				                            m_serviceFactory, IDataObjectPtr(pDataObj));
		}

		//<-- TRA

	    if( m_nCurrentDropAction != ACTION_NONE)
	    {
		    DropTargetDragEnterEvent e;
		    e.SupportedDataFlavors= m_currentData->getTransferDataFlavors();
		    e.DropAction= m_nCurrentDropAction;
		    e.Source= Reference<XInterface>( static_cast<XDropTarget*>(this),UNO_QUERY);
		    e.Context= m_currentDragContext;
		    POINT point={ pt.x, pt.y};
		    ScreenToClient( m_hWnd, &point);
		    e.LocationX= point.x;
		    e.LocationY= point.y;
		    e.SourceActions= dndOleDropEffectsToActions( *pdwEffect);

		    fire_dragEnter( e);
		    // Check if the action derived from grfKeyState (m_nCurrentDropAction) or the action set
		    // by the listener (m_nCurrentDropAction) is allowed by the source. Only a allowed action is set
		    // in pdwEffect. The listener notification is asynchron, that is we cannot expext that the listener
			// has already reacted to the notification.
		    // If there is more then one valid action which is the case when ALT or RIGHT MOUSE BUTTON is pressed
		    // then getDropEffect returns DROPEFFECT_MOVE which is the default value if no other modifier is pressed.
		    // On drop the target should present the user a dialog from which the user may change the action.
		    sal_Int8 allowedActions= dndOleDropEffectsToActions( *pdwEffect);
			*pdwEffect= dndActionsToSingleDropEffect( m_nLastDropAction & allowedActions);
	    }
	    else
	    {
		    *pdwEffect= DROPEFFECT_NONE;
	    }
    }
	return S_OK;
}

HRESULT DropTarget::DragOver( DWORD grfKeyState,
								   POINTL pt,
								   DWORD  *pdwEffect)
{
    if( m_bActive)
    {
	    m_nCurrentDropAction= getFilteredActions( grfKeyState, *pdwEffect);

	    if( m_nCurrentDropAction)
	    {
		    DropTargetDragEvent e;
		    e.DropAction= m_nCurrentDropAction;
		    e.Source= Reference<XInterface>(static_cast<XDropTarget*>(this),UNO_QUERY);
		    e.Context= m_currentDragContext;
		    POINT point={ pt.x, pt.y};
		    ScreenToClient( m_hWnd, &point);
		    e.LocationX= point.x;
		    e.LocationY= point.y;
		    e.SourceActions= dndOleDropEffectsToActions( *pdwEffect);

		    // if grfKeyState has changed since the last DragOver then fire events.
		    // A listener might change m_nCurrentDropAction by calling the
		    // XDropTargetDragContext::acceptDrag function. But this is not important
		    // because in the afterwards fired dragOver event the action reflects
		    // grgKeyState again.
			if( m_nLastDropAction != m_nCurrentDropAction)
				fire_dropActionChanged( e);

		    // The Event contains a XDropTargetDragContext implementation.
		    fire_dragOver( e);
		    // Check if the action derived from grfKeyState (m_nCurrentDropAction) or the action set
		    // by the listener (m_nCurrentDropAction) is allowed by the source. Only a allowed action is set
		    // in pdwEffect. The listener notification is asynchron, that is we cannot expext that the listener
			// has already reacted to the notification.
		    // If there is more then one valid action which is the case when ALT or RIGHT MOUSE BUTTON is pressed
		    // then getDropEffect returns DROPEFFECT_MOVE which is the default value if no other modifier is pressed.
		    // On drop the target should present the user a dialog from which the user may change the action.
		    sal_Int8 allowedActions= dndOleDropEffectsToActions( *pdwEffect);
			// set the last action to the current if listener has not changed the value yet
			*pdwEffect= dndActionsToSingleDropEffect( m_nLastDropAction & allowedActions);
	    }
	    else
	    {
		    *pdwEffect= DROPEFFECT_NONE;
	    }
    }
#if defined DBG_CONSOLE_OUT
	printf("\nDropTarget::DragOver %d", *pdwEffect );
#endif
	return S_OK;
}

HRESULT DropTarget::DragLeave( void)
{
#if defined DBG_CONSOLE_OUT
	printf("\nDropTarget::DragLeave");
#endif
    if( m_bActive)
    {

	    m_currentData=0;
	    m_currentDragContext= 0;
	    m_currentDropContext= 0;
		m_nLastDropAction= 0;

	    if( m_nDefaultActions != ACTION_NONE)
	    {
		    DropTargetEvent e;
		    e.Source=  static_cast<XDropTarget*>(this);

		    fire_dragExit( e);
	    }
    }
	return S_OK;
}

HRESULT DropTarget::Drop( IDataObject  * /*pDataObj*/,
				   DWORD grfKeyState,
				   POINTL pt,
				   DWORD *pdwEffect)
{
#if defined DBG_CONSOLE_OUT
	printf("\nDropTarget::Drop");
#endif
    if( m_bActive)
    {

	    m_bDropComplete= sal_False;

	    m_nCurrentDropAction= getFilteredActions( grfKeyState, *pdwEffect);
	    m_currentDropContext= static_cast<XDropTargetDropContext*>( new TargetDropContext( static_cast<DropTarget*>(this ))  );
	    if( m_nCurrentDropAction)
	    {
		    DropTargetDropEvent e;
		    e.DropAction= m_nCurrentDropAction;
		    e.Source= Reference<XInterface>( static_cast<XDropTarget*>(this), UNO_QUERY);
		    e.Context= m_currentDropContext;
		    POINT point={ pt.x, pt.y};
		    ScreenToClient( m_hWnd, &point);
		    e.LocationX= point.x;
		    e.LocationY= point.y;
		    e.SourceActions= dndOleDropEffectsToActions( *pdwEffect);
		    e.Transferable= m_currentData;
		    fire_drop( e);

		    //if fire_drop returns than a listener might have modified m_nCurrentDropAction
		    if( m_bDropComplete == sal_True)
		    {
			    sal_Int8 allowedActions= dndOleDropEffectsToActions( *pdwEffect);
			    *pdwEffect= dndActionsToSingleDropEffect( m_nCurrentDropAction & allowedActions);
		    }
		    else
			    *pdwEffect= DROPEFFECT_NONE;
	    }
	    else
		    *pdwEffect= DROPEFFECT_NONE;

	    m_currentData= 0;
	    m_currentDragContext= 0;
	    m_currentDropContext= 0;
		m_nLastDropAction= 0;
    }
	return S_OK;
}



void DropTarget::fire_drop( const DropTargetDropEvent& dte)
{
	OInterfaceContainerHelper* pContainer= rBHelper.getContainer( getCppuType( (Reference<XDropTargetListener>* )0 ) );
	if( pContainer)
	{
		OInterfaceIteratorHelper iter( *pContainer);
		while( iter.hasMoreElements())
		{
			Reference<XDropTargetListener> listener( static_cast<XDropTargetListener*>( iter.next()));
			listener->drop( dte);
		}
	}
}

void DropTarget::fire_dragEnter( const DropTargetDragEnterEvent& e )
{
	OInterfaceContainerHelper* pContainer= rBHelper.getContainer( getCppuType( (Reference<XDropTargetListener>* )0 ) );
	if( pContainer)
	{
		OInterfaceIteratorHelper iter( *pContainer);
		while( iter.hasMoreElements())
		{
			Reference<XDropTargetListener> listener( static_cast<XDropTargetListener*>( iter.next()));
			listener->dragEnter( e);
		}
	}
}

void DropTarget::fire_dragExit( const DropTargetEvent& dte )
{
	OInterfaceContainerHelper* pContainer= rBHelper.getContainer( getCppuType( (Reference<XDropTargetListener>* )0 ) );

	if( pContainer)
	{
		OInterfaceIteratorHelper iter( *pContainer);
		while( iter.hasMoreElements())
		{
			Reference<XDropTargetListener> listener( static_cast<XDropTargetListener*>( iter.next()));
			listener->dragExit( dte);
		}
	}
}

void DropTarget::fire_dragOver( const DropTargetDragEvent& dtde )
{
	OInterfaceContainerHelper* pContainer= rBHelper.getContainer( getCppuType( (Reference<XDropTargetListener>* )0 ) );
	if( pContainer)
	{
		OInterfaceIteratorHelper iter( *pContainer );
		while( iter.hasMoreElements())
		{
			Reference<XDropTargetListener> listener( static_cast<XDropTargetListener*>( iter.next()));
			listener->dragOver( dtde);
		}
	}
}

void DropTarget::fire_dropActionChanged( const DropTargetDragEvent& dtde )
{
	OInterfaceContainerHelper* pContainer= rBHelper.getContainer( getCppuType( (Reference<XDropTargetListener>* )0 ) );
	if( pContainer)
	{
		OInterfaceIteratorHelper iter( *pContainer);
		while( iter.hasMoreElements())
		{
			Reference<XDropTargetListener> listener( static_cast<XDropTargetListener*>( iter.next()));
			listener->dropActionChanged( dtde);
		}
	}
}

// Non - interface functions ============================================================
// DropTarget fires events to XDropTargetListeners. The event object contains an
// XDropTargetDropContext implementaion. When the listener calls on that interface
// then the calls are delegated from DropContext (XDropTargetDropContext) to these
// functions.
// Only one listener which visible area is affected is allowed to call on
// XDropTargetDropContext
// Returning sal_False would cause the XDropTargetDropContext or ..DragContext implementation
// to throw an InvalidDNDOperationException, meaning that a Drag is not currently performed.
// return sal_False results in throwing a InvalidDNDOperationException in the caller.

void DropTarget::_acceptDrop(sal_Int8 dropOperation, const Reference<XDropTargetDropContext>& context)
{
	if( context == m_currentDropContext)
	{
		m_nCurrentDropAction= dropOperation;
	}
}

void DropTarget::_rejectDrop( const Reference<XDropTargetDropContext>& context)
{
	if( context == m_currentDropContext)
	{
		m_nCurrentDropAction= ACTION_NONE;
	}
}

void DropTarget::_dropComplete(sal_Bool success, const Reference<XDropTargetDropContext>& context)
{
	if(context == m_currentDropContext)
	{
		m_bDropComplete= success;
	}
}
// --------------------------------------------------------------------------------------
// DropTarget fires events to XDropTargetListeners. The event object can contains an
// XDropTargetDragContext implementaion. When the listener calls on that interface
// then the calls are delegated from DragContext (XDropTargetDragContext) to these
// functions.
// Only one listener which visible area is affected is allowed to call on
// XDropTargetDragContext
void DropTarget::_acceptDrag( sal_Int8 dragOperation, const Reference<XDropTargetDragContext>& context)
{
	if( context == m_currentDragContext)
	{
		m_nLastDropAction= dragOperation;
	}
}

void DropTarget::_rejectDrag( const Reference<XDropTargetDragContext>& context)
{
	if(context == m_currentDragContext)
	{
		m_nLastDropAction= ACTION_NONE;
	}
}


//--------------------------------------------------------------------------------------


// This function determines the action dependend on the pressed
// key modifiers ( CTRL, SHIFT, ALT, Right Mouse Button). The result
// is then checked against the allowed actions which can be set through
// XDropTarget::setDefaultActions. Only those values which are also
// default actions are returned. If setDefaultActions has not been called
// beforehand the default actions comprise all possible actions.
// params: grfKeyState - the modifier keys and mouse buttons currently pressed
inline sal_Int8 DropTarget::getFilteredActions( DWORD grfKeyState, DWORD dwEffect)
{
	sal_Int8 actions= dndOleKeysToAction( grfKeyState, dndOleDropEffectsToActions( dwEffect));
	return actions &  m_nDefaultActions;
}