/**************************************************************
 * 
 * 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_fpicker.hxx"
#include <osl/diagnose.h>
#include <osl/conditn.hxx>

#include "MtaFop.hxx"
#include <wchar.h>
#include <process.h>
#include "..\misc\resourceprovider.hxx"

#include <systools/win32/comtools.hxx>

using rtl::OUString;
using osl::Condition;

const sal_uInt32 MSG_BROWSEFORFOLDER = WM_USER + 1;
const sal_uInt32 MSG_SHUTDOWN        = WM_USER + 2;

const sal_uInt32 MAX_WAITTIME		 = 2000; // msec

const sal_Bool MANUAL_RESET     = sal_True;
const sal_Bool AUTO_RESET       = sal_False;
const sal_Bool INIT_NONSIGNALED = sal_False;

typedef sal::systools::COMReference<IMalloc> IMallocPtr;
typedef sal::systools::COMReference<IShellFolder> IShellFolderPtr;

namespace
{	
	const char* FOLDERPICKER_SRV_DLL_NAME = "fop.dll";
	const char g_szWndClsName[]			  = "FopStaReqWnd###";
    const char* CURRENT_INSTANCE          = "CurrInst";

    typedef struct _RequestContext
    {
	    HANDLE	 hEvent;
	    sal_Bool bRet;
    } RequestContext;

    inline sal_Bool InitializeRequestContext( RequestContext* aRequestContext )
    {
        OSL_ASSERT( aRequestContext );

        aRequestContext->hEvent = CreateEventA(
            0, AUTO_RESET, INIT_NONSIGNALED, NULL );
        
	    aRequestContext->bRet = sal_False;

        return ( 0 != aRequestContext->hEvent );
    }

    inline void DeinitializeRequestContext( RequestContext* aRequestContext )
    {
        OSL_ASSERT( aRequestContext && aRequestContext->hEvent );
        CloseHandle( aRequestContext->hEvent );
    }
    
    //-------------------------------
    // Determine if current thread is 
    // an MTA or STA thread
    //-------------------------------
    bool IsMTA()
    {
	    HRESULT hr = CoInitialize(NULL);
    	
	    if (RPC_E_CHANGED_MODE == hr)
		    return true;
    	
	    if(SUCCEEDED(hr))
		    CoUninitialize();

	    return false;
    }
}

//----------------------------------------------------------------
//	static member initialization
//----------------------------------------------------------------

ATOM CMtaFolderPicker::s_ClassAtom = 0;
osl::Mutex CMtaFolderPicker::s_Mutex;
sal_Int32 CMtaFolderPicker::s_StaRequestWndRegisterCount = 0;

//--------------------------------------------------------------------
// ctor
//--------------------------------------------------------------------

CMtaFolderPicker::CMtaFolderPicker( sal_uInt32 Flags ) : 
	m_hStaThread( NULL ),
	m_uStaThreadId( 0 ),
	m_hEvtThrdReady( NULL ),	
	m_hwndStaRequestWnd( NULL )
{ 
    m_hInstance = GetModuleHandleA( FOLDERPICKER_SRV_DLL_NAME );
	OSL_ENSURE( m_hInstance, "The name of the FolderPicker service dll must have changed" );

	ZeroMemory( &m_bi, sizeof( m_bi ) );

    // !!!!!!!!!!!!!!!!!  IMPORTANT !!!!!!!!!!!!!!!!!!!
    //
    // Remember: This HACK prevents you from stepping
    // through your code in the debugger because if you
    // set a break point in the ctor here the debugger
    // may become the owner of the FolderBrowse dialog
    // and so it seems that the Visual Studio and the 
    // office are hanging
    m_bi.hwndOwner = GetForegroundWindow( );

	/*
		Flag				Available
		--------------------------------
		BIF_EDITBOX			Version 4.71
		BIF_NEWDIALOGSTYLE	Version 5.0
		BIF_SHAREABLE		Version 5.0
		BIF_VALIDATE		Version 4.71

		Version 4.71 - Internet Explorer 4.0
		Version 5.0	 - Internet Explorer 5.0
					   Windows 2000
	*/
	m_bi.ulFlags = Flags;

	m_bi.lpfn    = CMtaFolderPicker::FolderPickerCallback;
	m_bi.lParam  = reinterpret_cast< LPARAM >( this );

    //---------------------------------------
    // read the default strings for title and
    // description from a resource file

    CResourceProvider ResProvider;

    m_dialogTitle = ResProvider.getResString( 500 );
    m_Description = ResProvider.getResString( 501 );
            
	// signals that the thread was successfully set up
	m_hEvtThrdReady  = CreateEventA( 
		0, 
		MANUAL_RESET, 
		INIT_NONSIGNALED, 
		NULL );				

    if ( m_hEvtThrdReady )
    {
	    // setup the sta thread
	    m_hStaThread = (HANDLE)_beginthreadex( 
		    NULL, 
            0, 
            CMtaFolderPicker::StaThreadProc, 
            this, 
            0, 
            &m_uStaThreadId );	  

        OSL_ASSERT( m_hStaThread );
    }

    OSL_ASSERT( m_hEvtThrdReady );    
}

//--------------------------------------------------------------------
// dtor
//--------------------------------------------------------------------

CMtaFolderPicker::~CMtaFolderPicker( )
{	
    // only if the is a valid event handle
    // there may also be a thread a hidden
    // target request window and so on
    // see ctor
	if ( m_hEvtThrdReady )
    {
        // block calling threads because we
        // are about to shutdown
		ResetEvent( m_hEvtThrdReady );
                
        // force the destruction of the sta thread request window
        // and the end of the thread
        // remeber: DestroyWindow may only be called from within
        // the thread that created the window
        if ( IsWindow( m_hwndStaRequestWnd ) )
        {
            SendMessageA( m_hwndStaRequestWnd, MSG_SHUTDOWN, 0, 0 );

            // we place unregister class here because 
            // if we have a valid window we must have 
            // sucessfully registered a window class
            // if the creation of the window itself 
            // failed after registering the window 
            // class we have unregistered it immediately
            // in createStaRequestWindow below
            UnregisterStaRequestWindowClass( );	
        }
        
        if ( m_hStaThread )
        {
            // wait for thread shutdown
            sal_uInt32 dwResult = WaitForSingleObject( m_hStaThread, MAX_WAITTIME );
            OSL_ENSURE( dwResult == WAIT_OBJECT_0, "sta thread could not terminate" );
	            
            // terminate the thread if it 
            // doesn't shutdown itself
            if ( WAIT_OBJECT_0 != dwResult )
	            TerminateThread(
                    m_hStaThread, sal::static_int_cast< DWORD >(-1) );

            CloseHandle( m_hStaThread );
        }

	    CloseHandle( m_hEvtThrdReady );        
    }
}

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

sal_Bool CMtaFolderPicker::browseForFolder( )
{
    sal_Bool bRet = sal_False;
    
    if (IsMTA())
    {
    		
        OSL_ASSERT( m_hEvtThrdReady );

	    if ( WaitForSingleObject( m_hEvtThrdReady, MAX_WAITTIME ) != WAIT_OBJECT_0 )
        {
            OSL_ENSURE( sal_False, "sta thread not ready" );
		    return sal_False;
        }
    	
	    RequestContext aReqCtx;  

        if ( !InitializeRequestContext( &aReqCtx ) )
        {
            OSL_ASSERT( sal_False );
            return sal_False;
        }
        
	    // marshall request into the sta thread
	    PostMessageA( 
		    m_hwndStaRequestWnd, 
            MSG_BROWSEFORFOLDER, 
            0, 
            reinterpret_cast< LPARAM >( &aReqCtx ) );     

        // waiting for the event to be signaled or 
        // window messages so that we don't block
        // our parent window

        sal_Bool bContinue = sal_True;
        
        while ( bContinue )
        {
            DWORD dwResult = MsgWaitForMultipleObjects(
                1, &aReqCtx.hEvent, sal_False, INFINITE, QS_ALLEVENTS );
            
            switch ( dwResult )
            {
            // the request context event is signaled
            case WAIT_OBJECT_0: 
                bContinue = sal_False;
                break;

            // a window message has arrived
            case WAIT_OBJECT_0 + 1:
                {
                    // dispatching all messages but we expect to 
                    // receive only paint or timer messages that's
                    // why we don't need to call TranslateMessage or
                    // TranslateAccelerator, because keybord or
                    // mouse messages are for the FolderPicker which
                    // is in the foreground and should not arrive here
                    MSG msg;
                    while ( PeekMessageA( &msg, NULL, 0, 0, PM_REMOVE ) )            
                        DispatchMessageA(&msg);               
                }
                break;
            
            // should not happen
            default:
                OSL_ASSERT( sal_False );
            }
        }

        /*sal_Bool*/ bRet = aReqCtx.bRet;
        DeinitializeRequestContext( &aReqCtx );
    }
    else
    {
        bRet = onBrowseForFolder();
    }
    
    return bRet;
}

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

void SAL_CALL CMtaFolderPicker::setDisplayDirectory( const OUString& aDirectory )
{
	m_displayDir = aDirectory;
}

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

OUString SAL_CALL CMtaFolderPicker::getDisplayDirectory( )
{
	return m_displayDir;
}

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

OUString SAL_CALL CMtaFolderPicker::getDirectory( )
{
    return m_SelectedDir;
}

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

void SAL_CALL CMtaFolderPicker::setDescription( const rtl::OUString& aDescription )
{
    m_Description = aDescription;
}

//--------------------------------------------------------------------
// 
//--------------------------------------------------------------------
	
void SAL_CALL CMtaFolderPicker::setTitle( const OUString& aTitle )
{
	m_dialogTitle = aTitle;
}

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

OUString SAL_CALL CMtaFolderPicker::getTitle( )
{
	return m_dialogTitle;
}

//-----------------------------------------------------
// XCancellable
//-----------------------------------------------------
    
void SAL_CALL CMtaFolderPicker::cancel( )
{
    if ( IsWindow( m_hwnd ) )
    {
        // simulate a mouse click to the 
        // cancel button
        PostMessageA( 
            m_hwnd, 
            WM_COMMAND, 
            MAKEWPARAM( IDCANCEL, BN_CLICKED ), 
            (LPARAM)GetDlgItem( m_hwnd, IDCANCEL ) );
    }
}

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

sal_Bool SAL_CALL CMtaFolderPicker::onBrowseForFolder( )
{	
	sal_Bool     bRet;
	LPITEMIDLIST lpiid;
	
	// pre SHBrowseFroFolder

	m_bi.pidlRoot       = 0;
	m_bi.pszDisplayName = reinterpret_cast<LPWSTR>(m_pathBuff.get());
    
    if ( m_Description.getLength( ) )
        m_bi.lpszTitle = reinterpret_cast<LPCWSTR>(m_Description.getStr( ));

	lpiid = SHBrowseForFolderW( &m_bi );
	bRet = ( NULL != lpiid );

	// post SHBrowseForFolder

	m_SelectedDir = getPathFromItemIdList( lpiid );
	releaseItemIdList( lpiid );

	return bRet;
}

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

void SAL_CALL CMtaFolderPicker::releaseItemIdList( LPITEMIDLIST lpItemIdList )
{
	IMallocPtr pIMalloc;
	SHGetMalloc(&pIMalloc);	
	if (pIMalloc.is())
	{
	    pIMalloc->Free(lpItemIdList);	
	    lpItemIdList = NULL;	
	}
}

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

LPITEMIDLIST SAL_CALL CMtaFolderPicker::getItemIdListFromPath( const rtl::OUString& aDirectory )
{
	// parameter checking
	if ( !aDirectory.getLength( ) )
		return NULL;

	LPITEMIDLIST lpItemIdList(NULL);
	
	IShellFolderPtr pIShellFolder;
	SHGetDesktopFolder(&pIShellFolder);

    if (pIShellFolder.is())
    {
	    pIShellFolder->ParseDisplayName(
		    NULL,
		    NULL,
		    reinterpret_cast<LPWSTR>(const_cast< sal_Unicode* >( aDirectory.getStr( ) )),
		    NULL,
		    &lpItemIdList,
		    NULL );
    }
	
	return lpItemIdList;
}

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

OUString SAL_CALL CMtaFolderPicker::getPathFromItemIdList( LPCITEMIDLIST lpItemIdList )
{
	OUString path;

	if ( lpItemIdList )
	{		
		bool bRet = SHGetPathFromIDListW( lpItemIdList, reinterpret_cast<LPWSTR>(m_pathBuff.get()) );
		if ( bRet )
			path = m_pathBuff.get( );
	}

	return path;
}

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

void SAL_CALL CMtaFolderPicker::enableOk( sal_Bool bEnable )
{
	OSL_ASSERT( IsWindow( m_hwnd ) );

	SendMessageW(
		m_hwnd,
		BFFM_ENABLEOK,
		static_cast< WPARAM >( 0 ),
		static_cast< LPARAM >( bEnable ) );
}

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

void SAL_CALL CMtaFolderPicker::setSelection( const rtl::OUString& aDirectory )
{
	OSL_ASSERT( IsWindow( m_hwnd ) );

#ifdef _MSC_VER
#pragma message( "#######################################" )
#pragma message( "SendMessageW wrapper has to be extended" )
#pragma message( "#######################################" )
#endif

	SendMessageW(
		m_hwnd,
		BFFM_SETSELECTIONW,
		static_cast< WPARAM >( sal_True ),
		reinterpret_cast< LPARAM >( aDirectory.getStr( ) ) );
}

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

void SAL_CALL CMtaFolderPicker::setStatusText( const rtl::OUString& aStatusText )
{
	OSL_ASSERT( IsWindow( m_hwnd ) );

	SendMessageW( 
		m_hwnd, 
		BFFM_SETSTATUSTEXTW, 
		static_cast< WPARAM >( 0 ), 
		reinterpret_cast< LPARAM >( aStatusText.getStr( ) ) );
}

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

void SAL_CALL CMtaFolderPicker::onInitialized( )
{
    LPITEMIDLIST lpiidDisplayDir = getItemIdListFromPath( m_displayDir );

	if ( lpiidDisplayDir )
	{
		SendMessageA( 
			m_hwnd, 
			BFFM_SETSELECTION, 
			(WPARAM)sal_False, 
			(LPARAM) lpiidDisplayDir );

		releaseItemIdList( lpiidDisplayDir );    
	}
}

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

sal_uInt32 CMtaFolderPicker::onValidateFailed()
{
	// to be overwritten by subclasses
	return 1;
}

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

int CALLBACK CMtaFolderPicker::FolderPickerCallback( HWND hwnd, UINT uMsg, LPARAM lParam, LPARAM lpData )
{
	CMtaFolderPicker* pImpl = reinterpret_cast< CMtaFolderPicker* >( lpData );
	OSL_ASSERT( pImpl );
	
	int nRC = 0;
	
	switch( uMsg )
	{
		case BFFM_INITIALIZED:			
            pImpl->m_hwnd = hwnd;
			pImpl->onInitialized( );
			SetWindowTextW( hwnd, reinterpret_cast<LPCWSTR>(pImpl->m_dialogTitle.getStr()) );
		break;

		case BFFM_SELCHANGED:
            pImpl->m_hwnd = hwnd;
			pImpl->onSelChanged( 
				pImpl->getPathFromItemIdList( 
					reinterpret_cast< LPITEMIDLIST >( lParam ) ) );
		break;

		case BFFM_VALIDATEFAILEDW:
			nRC = pImpl->onValidateFailed();
			break;

		default:
			OSL_ASSERT( sal_False );
	}

	return nRC;
}

//--------------------------------------------------------------------
// the window proc
//--------------------------------------------------------------------

LRESULT CALLBACK CMtaFolderPicker::StaWndProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam )
{
	LRESULT           lResult = 0;
	CMtaFolderPicker* pImpl   = NULL;

    /*
        we connect to the belonging class instance of this 
        window using SetProp, GetProp etc.
        this may fail if somehow the class instance destroyed
        before the window        
    */

	switch( uMsg )
	{
        case WM_CREATE:
            {
                LPCREATESTRUCT lpcs = 
                    reinterpret_cast< LPCREATESTRUCT >( lParam );

                OSL_ASSERT( lpcs->lpCreateParams );

                // connect the instance handle to the window            
                SetPropA( hWnd, CURRENT_INSTANCE, lpcs->lpCreateParams );
            }
            break;

        case WM_NCDESTROY:
                // RemoveProp returns the saved value on success
                pImpl = reinterpret_cast< CMtaFolderPicker* >( 
                    RemovePropA( hWnd, CURRENT_INSTANCE ) );

                OSL_ASSERT( pImpl && !IsBadReadPtr( pImpl, sizeof( CMtaFolderPicker ) ) );
            break;

	    case MSG_BROWSEFORFOLDER:
            {
                RequestContext* aReqCtx = reinterpret_cast< RequestContext* >( lParam );
                OSL_ASSERT( aReqCtx );

                pImpl = reinterpret_cast< CMtaFolderPicker* >(
                    GetPropA( hWnd, CURRENT_INSTANCE ) );

                OSL_ASSERT( pImpl && !IsBadReadPtr( pImpl, sizeof( CMtaFolderPicker ) ) );
            
                aReqCtx->bRet = pImpl->onBrowseForFolder( );
                SetEvent( aReqCtx->hEvent );
            }
			break;

		case MSG_SHUTDOWN:
            pImpl = reinterpret_cast< CMtaFolderPicker* >(
                GetPropA( hWnd, CURRENT_INSTANCE ) );

            OSL_ASSERT( pImpl && !IsBadReadPtr( pImpl, sizeof( CMtaFolderPicker ) ) );

            DestroyWindow( pImpl->m_hwndStaRequestWnd );
			break;

		case WM_DESTROY:		
			PostQuitMessage( 0 );
			break;

		default:
			lResult = DefWindowProcA( hWnd, uMsg, wParam, lParam );
			break;
	}

	return lResult;
}

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

sal_Bool SAL_CALL CMtaFolderPicker::createStaRequestWindow( )
{
    bool bIsWnd = false;

	if ( RegisterStaRequestWindowClass( ) )
    {
		m_hwndStaRequestWnd = CreateWindowA( 
			g_szWndClsName, NULL, 
            0, 0, 0, 0, 0, 
            NULL, NULL, m_hInstance, 
            (LPVOID)this // provide the instance of the class
        );
        
        bIsWnd = IsWindow( m_hwndStaRequestWnd );
      
        // we do immediately unregister the window class 
        // if the creation of the window fails because we
        // don't want to spoil the register class counter
        if ( !bIsWnd )
            UnregisterStaRequestWindowClass( );

        OSL_ENSURE( bIsWnd, "sta request window creation failed" );
    }

    return bIsWnd;
}

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

unsigned int CMtaFolderPicker::run( )
{	
    OSL_ASSERT( m_hEvtThrdReady );

    // setup an sta environment
	HRESULT hr = CoInitialize( NULL );
	
    // if we can't setup an sta environment 
    // we stop here and return 
    if ( FAILED( hr ) )
    {
        OSL_ENSURE( sal_False, "CoInitialize failed" );
        return sal::static_int_cast< unsigned int >(-1);
    }
    
	unsigned int nRet;

	if ( createStaRequestWindow( ) )
	{
		SetEvent( m_hEvtThrdReady );
        
		// pumping messages
		MSG msg;		
		while( GetMessageA( &msg, NULL, 0, 0 ) )
			DispatchMessageA( &msg );	

		nRet = 0;        
	}
	else
    {        
        OSL_ENSURE( sal_False, "failed to create sta thread" );        
		nRet = sal::static_int_cast< unsigned int >(-1);
    }

    // shutdown sta environment
    CoUninitialize( );

	return nRet;
}

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

unsigned int WINAPI CMtaFolderPicker::StaThreadProc( LPVOID pParam )
{
	CMtaFolderPicker* pInst = 
		reinterpret_cast<CMtaFolderPicker*>( pParam );

	OSL_ASSERT( pInst );
	
	HRESULT	hr = OleInitialize( NULL );

	unsigned int	result = pInst->run( );

	if ( SUCCEEDED( hr ) )
		OleUninitialize();

	return result;
}

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

ATOM SAL_CALL CMtaFolderPicker::RegisterStaRequestWindowClass( )
{    
    osl::MutexGuard aGuard( s_Mutex );

    if ( 0 == s_ClassAtom )
    {
        WNDCLASSEXA  wcex;
		  
	    ZeroMemory( &wcex, sizeof( WNDCLASSEXA ) );

	    wcex.cbSize			= sizeof(WNDCLASSEXA); 
	    wcex.style			= 0;
	    wcex.lpfnWndProc	= static_cast< WNDPROC >( CMtaFolderPicker::StaWndProc );
	    wcex.cbClsExtra		= 0;
	    wcex.cbWndExtra		= 0;
	    wcex.hInstance		= m_hInstance;
	    wcex.hIcon			= NULL;
	    wcex.hCursor		= NULL;
	    wcex.hbrBackground	= NULL;
	    wcex.lpszMenuName	= NULL;
	    wcex.lpszClassName	= g_szWndClsName;
	    wcex.hIconSm		= NULL;

    	s_ClassAtom = RegisterClassExA( &wcex );
        OSL_ASSERT( s_ClassAtom );        
    }

    // increment the register class counter
    // so that we keep track of the number
    // of class registrations
    if ( 0 != s_ClassAtom )
        s_StaRequestWndRegisterCount++;

    return s_ClassAtom;
}

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

void SAL_CALL CMtaFolderPicker::UnregisterStaRequestWindowClass( )
{
    osl::MutexGuard aGuard( s_Mutex );
    
    OSL_ASSERT( 0 != s_ClassAtom );

    // update the register class counter 
    // and unregister the window class if
    // counter drops to zero
    if ( 0 != s_ClassAtom )
    {
        s_StaRequestWndRegisterCount--;
        OSL_ASSERT( s_StaRequestWndRegisterCount >= 0 );
    }

    if ( 0 == s_StaRequestWndRegisterCount )
    {
        UnregisterClass( 
	        (LPCTSTR)MAKELONG( s_ClassAtom, 0 ), m_hInstance );
        
        s_ClassAtom = 0;
    }    
}