/**************************************************************
 *
 * 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
        // remember: 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
            // successfully 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 keyboard 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;
    }
}
