/**************************************************************
 * 
 * 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"

//------------------------------------------------------------------------
// includes
//------------------------------------------------------------------------
#include "filepickerstate.hxx"
#include <osl/diagnose.h>
#include "controlaccess.hxx"
#include <com/sun/star/ui/dialogs/ExtendedFilePickerElementIds.hpp>
#include <com/sun/star/ui/dialogs/CommonFilePickerElementIds.hpp>
#include <com/sun/star/ui/dialogs/ListBoxControlActions.hpp>
#include <com/sun/star/ui/dialogs/ControlActions.hpp>
#include "controlcommandrequest.hxx"
#include "controlcommandresult.hxx"
#include <com/sun/star/uno/Reference.hxx>
#include <com/sun/star/uno/XInterface.hpp>
#include <osl/file.hxx>
#include "FileOpenDlg.hxx"

#include <memory>
#include "..\misc\WinImplHelper.hxx"
//---------------------------------------------
//
//---------------------------------------------

using rtl::OUString;
using com::sun::star::uno::Any;
using com::sun::star::uno::Sequence;
using com::sun::star::uno::Reference;
using com::sun::star::uno::XInterface;

using namespace ::com::sun::star::ui::dialogs::ExtendedFilePickerElementIds;
using namespace ::com::sun::star::ui::dialogs::CommonFilePickerElementIds;
using namespace ::com::sun::star::ui::dialogs::ListboxControlActions;

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

const sal_Int32 MAX_LABEL = 256;
const sal_Int16 LISTBOX_LABEL_OFFSET = 100;

//---------------------------------------------
// declaration
//---------------------------------------------

CFilePickerState::~CFilePickerState( )
{
}

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

CNonExecuteFilePickerState::CNonExecuteFilePickerState( ) :
    m_FirstControlCommand( NULL )
{
}

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

CNonExecuteFilePickerState::~CNonExecuteFilePickerState( )
{
    reset( );
}

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

void SAL_CALL CNonExecuteFilePickerState::setValue( sal_Int16 aControlId, sal_Int16 aControlAction, const Any& aValue )
{
    CValueControlCommand* value_command = new CValueControlCommand( 
        aControlId, aControlAction, aValue );

    addControlCommand( value_command );
}

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

Any SAL_CALL CNonExecuteFilePickerState::getValue( sal_Int16 aControlId, sal_Int16 aControlAction )
{
    CValueControlCommandRequest value_request( aControlId, aControlAction );    
    Any aAny;	

	if (m_FirstControlCommand)
	{
		// pass the request along the command-chain
		std::auto_ptr< CControlCommandResult > result( m_FirstControlCommand->handleRequest( &value_request ) );

		OSL_ENSURE( result.get(), "invalid getValue request" );

		if ( result.get() )
		{
			// #101753 must remove assertion else running into deadlock
			// because getValue may be called asynchronously from main thread
			// with locked SOLAR_MUTEX but we also need SOLAR_MUTEX in 
			// WinFileOpenDialog::onInitDone ... but we cannot dismiss the
			// assertion dialog because at this point the FileOpen Dialog 
			// has aleady the focus but is not yet visible :-(
			// The real cure is to remove the VCL/SOLAR_MUTEX dependency
			// cause by the use of our resource manager and not being able to
			// generate native windows resources
			//OSL_ENSURE( result->hasResult( ), "invalid getValue request" );

			if ( result->hasResult( ) )
			{
				CValueCommandResult* value_result = dynamic_cast< CValueCommandResult* >( result.get( ) );
				OSL_ENSURE( value_result, "should have be a CValueCommandResult" );

				aAny = value_result->getValue( );
				OSL_ENSURE( aAny.hasValue( ), "empty any" );
			}
		}
	}

    return aAny;
}

//---------------------------------------------
//
//---------------------------------------------
	
void SAL_CALL CNonExecuteFilePickerState::enableControl( sal_Int16 aControlId, sal_Bool bEnable )
{
    CEnableControlCommand* enable_command = new CEnableControlCommand( 
        aControlId, bEnable );

    addControlCommand( enable_command );
}

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

void SAL_CALL CNonExecuteFilePickerState::setLabel( sal_Int16 aControlId, const ::rtl::OUString& aLabel )
{
    CLabelControlCommand* label_command = new CLabelControlCommand(
        aControlId, aLabel );

    addControlCommand( label_command );
}

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

OUString SAL_CALL CNonExecuteFilePickerState::getLabel( sal_Int16 aControlId )
{
    CControlCommandRequest label_request( aControlId );

    // pass the request along the command-chain
    std::auto_ptr< CControlCommandResult > result( m_FirstControlCommand->handleRequest( &label_request ) );

    OSL_ENSURE( result->hasResult( ), "invalid getValue request" );

    OUString aLabel;

    if ( result->hasResult( ) )
    {
        CLabelCommandResult* label_result = dynamic_cast< CLabelCommandResult* >( result.get( ) );
        OSL_ENSURE( label_result, "should have be a CLabelCommandResult" );

        aLabel = label_result->getLabel( );
    }

    return aLabel;
}

/*  #i26224# 
    When typing file names with drive letter but without '\'
    in the "File name" box of the FileOpen dialog the FileOpen
    dialog makes strange paths out of them e.g. "d:.\test.sxw".
    Such file names will not be accepted by sal so we fix these
    somehow broken paths here. */
OUString MatchFixBrokenPath(const OUString& path)
{
    OSL_ASSERT(path.getLength() >= 4);
    
    if (path[1] == ':' && path[2] == '.' && path[3] == '\\')
    {
        // skip the '.'
        return OUString(path, 2) + path.copy(3, path.getLength() - 3);        
    }    
    return path;   
}

//-----------------------------------------------------------------------------------------
// 
//-----------------------------------------------------------------------------------------
static ::rtl::OUString trimTrailingSpaces(const ::rtl::OUString& rString)
{
    rtl::OUString aResult(rString);
    
    sal_Int32 nIndex = rString.lastIndexOf(' ');
    if (nIndex == rString.getLength()-1)
    {
        while (nIndex >= 0 && rString[nIndex] == ' ')
            nIndex--;
        if (nIndex >= 0)
            aResult = rString.copy(0,nIndex+1);
        else
            aResult = ::rtl::OUString();
    }
    return aResult;
}
        
Sequence< OUString > SAL_CALL CNonExecuteFilePickerState::getFiles( CFileOpenDialog* aFileOpenDialog )
{
    OSL_PRECOND( aFileOpenDialog, "invalid parameter" );

    Sequence< OUString > aFilePathList;
    OUString aFilePathURL;
    OUString aFilePath;
    ::osl::FileBase::RC rc;

    aFilePath = aFileOpenDialog->getFullFileName( );

    if ( aFilePath.getLength( ) )
    {
        // tokenize the returned string and copy the
        // sub-strings separately into a sequence
        const sal_Unicode* pTemp = aFilePath.getStr();        
        const sal_Unicode* pStrEnd = pTemp + aFilePath.getLength();
        sal_uInt32 lSubStr; 

        while (pTemp < pStrEnd)
        {
            // detect the length of the next sub string 
            lSubStr = rtl_ustr_getLength(pTemp);
                
            aFilePathList.realloc(aFilePathList.getLength() + 1);
                
            aFilePathList[aFilePathList.getLength() - 1] = 
                MatchFixBrokenPath(OUString(pTemp, lSubStr));

            pTemp += (lSubStr + 1);
        }

        // change all entries to file URLs 
        sal_Int32 lenFileList = aFilePathList.getLength( );
		OSL_ASSERT( lenFileList >= 1 );

        for ( sal_Int32 i = 0; i < lenFileList; i++ )
        {	        
			aFilePath = trimTrailingSpaces(aFilePathList[i]);
			rc = ::osl::FileBase::getFileURLFromSystemPath( 
                aFilePath, aFilePathURL );
                
            // we do return all or nothing, that means
            // in case of failures we destroy the sequence
            // and return an empty sequence
            if ( rc != ::osl::FileBase::E_None )
            {
                aFilePathList.realloc( 0 );
                break;
            }

            aFilePathList[i] = aFilePathURL;
        }
    }

    return aFilePathList;
}

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

OUString SAL_CALL CNonExecuteFilePickerState::getDisplayDirectory( CFileOpenDialog* aFileOpenDialog )
{
    OSL_PRECOND( aFileOpenDialog, "invalid parameter" );

    OUString pathURL;
    OUString displayDir;

    displayDir = aFileOpenDialog->getLastDisplayDirectory( );
    
	if ( displayDir.getLength( ) )
        ::osl::FileBase::getFileURLFromSystemPath( displayDir, pathURL );
	
	return pathURL;
}

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

void SAL_CALL CNonExecuteFilePickerState::reset( )
{
    CControlCommand* nextCommand;
    CControlCommand* currentCommand = m_FirstControlCommand;
    
    while( currentCommand )
    {
        nextCommand = currentCommand->getNextCommand( );
        delete currentCommand;
        currentCommand = nextCommand;
    }

    m_FirstControlCommand = NULL;
}

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

CControlCommand* SAL_CALL CNonExecuteFilePickerState::getControlCommand( ) const
{
    return m_FirstControlCommand;
}

//---------------------------------------------
// we append new control commands to the end
// of the list so that we are sure the commands
// will be executed as the client issued it
//---------------------------------------------

void SAL_CALL CNonExecuteFilePickerState::addControlCommand( CControlCommand* aControlCommand )
{
    OSL_ASSERT( aControlCommand );

    if ( NULL == m_FirstControlCommand )
    {
        m_FirstControlCommand = aControlCommand;
    }
    else
    {
        CControlCommand* pNextControlCommand = m_FirstControlCommand;

        while ( pNextControlCommand->getNextCommand( ) != NULL )
            pNextControlCommand = pNextControlCommand->getNextCommand( );

        pNextControlCommand->setNextCommand( aControlCommand );
    }
}

//#######################################################################

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

CExecuteFilePickerState::CExecuteFilePickerState( HWND hwndDlg ) :
    m_hwndDlg( hwndDlg )
{    
}

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

void SAL_CALL CExecuteFilePickerState::setValue( sal_Int16 aControlId, sal_Int16 aControlAction, const Any& aValue )
{
    // we do not support SET_HELP_URL/GET_HELP_URL
    if ( com::sun::star::ui::dialogs::ControlActions::SET_HELP_URL == aControlAction )
        return;
    
    HWND hwndCtrl = GetHwndDlgItem( aControlId );

	// the filter listbox can be manipulated via this 
	// method the caller should use XFilterManager
    if ( !hwndCtrl || (aControlId == LISTBOX_FILTER) )
    {        
        OSL_ENSURE( sal_False, "invalid control id" );
        return;
    }

    CTRL_CLASS aCtrlClass = GetCtrlClass( hwndCtrl );
    if ( UNKNOWN == aCtrlClass )
    {
        OSL_ENSURE( sal_False, "unsupported control class" );
        return;
    }

    CTRL_SETVALUE_FUNCTION_T lpfnSetValue =
        GetCtrlSetValueFunction( aCtrlClass, aControlAction );

    if ( !lpfnSetValue )
    {
        OSL_ENSURE( sal_False, "unsupported control action" );             
        return;
    }

    // the function that we call should throw an IllegalArgumentException if 
    // the given value is invalid or empty, that's why we provide a Reference
    // to an XInterface and a argument position
    lpfnSetValue( hwndCtrl, aValue, Reference< XInterface >( ), 3 );
}

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

Any SAL_CALL CExecuteFilePickerState::getValue( sal_Int16 aControlId, sal_Int16 aControlAction )
{
    // we do not support SET_HELP_URL/GET_HELP_URL
    if ( com::sun::star::ui::dialogs::ControlActions::GET_HELP_URL == aControlAction )
        return Any( );

    HWND hwndCtrl = GetHwndDlgItem( aControlId );

	// the filter listbox can be manipulated via this 
	// method the caller should use XFilterManager
    if ( !hwndCtrl || (aControlId == LISTBOX_FILTER) )
    {
        OSL_ENSURE( sal_False, "invalid control id" );
        return Any( );
    }

    CTRL_CLASS aCtrlClass = GetCtrlClass( hwndCtrl );
    if ( UNKNOWN == aCtrlClass )
    {
        OSL_ENSURE( sal_False, "unsupported control class" );
        return Any( );
    }

    CTRL_GETVALUE_FUNCTION_T lpfnGetValue =
        GetCtrlGetValueFunction( aCtrlClass, aControlAction );

    if ( !lpfnGetValue )
    {
        OSL_ENSURE( sal_False, "unsupported control action" );
        return Any( );
    }

    return lpfnGetValue( hwndCtrl );
}

//---------------------------------------------
//
//---------------------------------------------
	
void SAL_CALL CExecuteFilePickerState::enableControl( sal_Int16 aControlId, sal_Bool bEnable )
{
    HWND hwndCtrl = GetHwndDlgItem( aControlId );

    OSL_ENSURE( IsWindow( hwndCtrl ), "invalid element id");
 	
    EnableWindow( hwndCtrl, bEnable );
}

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

void SAL_CALL CExecuteFilePickerState::setLabel( sal_Int16 aControlId, const OUString& aLabel )
{
    HWND hwndCtrl = GetHwndDlgItem( aControlId );
    
    OSL_ENSURE( IsWindow( hwndCtrl ), "invalid element id");
 
    if ( IsListboxControl( hwndCtrl ) )
        hwndCtrl = GetListboxLabelItem( aControlId );

    OUString aWinLabel = SOfficeToWindowsLabel( aLabel );

	// somewhat risky because we don't know if OUString
	// has a terminating '\0'
    SetWindowText( hwndCtrl, reinterpret_cast<LPCTSTR>(aWinLabel.getStr( )) );
}

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

OUString SAL_CALL CExecuteFilePickerState::getLabel( sal_Int16 aControlId )
{
    HWND hwndCtrl = GetHwndDlgItem( aControlId );
    
    OSL_ENSURE( IsWindow( hwndCtrl ), "invalid element id");
       
    if ( IsListboxControl( hwndCtrl ) )
        hwndCtrl = GetListboxLabelItem( aControlId );

    sal_Unicode aLabel[MAX_LABEL];
    int nRet = GetWindowText( hwndCtrl, reinterpret_cast<LPTSTR>(aLabel), MAX_LABEL );
    
    OUString ctrlLabel;
    if ( nRet )
    {
        ctrlLabel = OUString( aLabel, rtl_ustr_getLength( aLabel ) );
        ctrlLabel = WindowsToSOfficeLabel( aLabel );
    }

    return ctrlLabel;
}

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

Sequence< OUString > SAL_CALL CExecuteFilePickerState::getFiles( CFileOpenDialog* aFileOpenDialog )
{
    OSL_POSTCOND( aFileOpenDialog, "invalid parameter" );

    Sequence< OUString > aFilePathList;
    OUString aFilePathURL;
    OUString aFilePath;
    ::osl::FileBase::RC rc;

	// in execution mode getFullFileName doesn't
	// return anything, so we must use another way
       
    // returns the currently selected file(s) 
    // including path information
    aFilePath = aFileOpenDialog->getCurrentFilePath( );        

	// if multiple files are selected or the user
	// typed anything that doesn't seem to be a valid
	// file name getFileURLFromSystemPath fails
	// and we return an empty file list
    rc = ::osl::FileBase::getFileURLFromSystemPath( 
        aFilePath, aFilePathURL );

    if ( ::osl::FileBase::E_None == rc )
    {
        aFilePathList.realloc( 1 );
        aFilePathList[0] = aFilePathURL;
    }        
    
    return aFilePathList;
}

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

OUString SAL_CALL CExecuteFilePickerState::getDisplayDirectory( CFileOpenDialog* aFileOpenDialog )
{
    OSL_POSTCOND( aFileOpenDialog, "invalid parameter" );

    OUString pathURL;
    OUString displayDir;

    displayDir = aFileOpenDialog->getCurrentFolderPath( );

    if ( displayDir.getLength( ) )
        ::osl::FileBase::getFileURLFromSystemPath( displayDir, pathURL );
	
	return pathURL;
}

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

void SAL_CALL CExecuteFilePickerState::initFilePickerControls( CControlCommand* firstControlCommand )
{
    CControlCommand* aControlCommand = firstControlCommand;

    while ( aControlCommand )
    {
        aControlCommand->exec( this );
        aControlCommand = aControlCommand->getNextCommand( );
    }
}

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

void SAL_CALL CExecuteFilePickerState::cacheControlState( HWND hwndControl, CFilePickerState* aNonExecFilePickerState )
{
    OSL_ENSURE( hwndControl && aNonExecFilePickerState, "invalid parameters" );

    CTRL_CLASS aCtrlClass = GetCtrlClass( hwndControl );   
    
    sal_Int16 aControlAction;
    CTRL_GETVALUE_FUNCTION_T lpfnGetValue;

    if ( CHECKBOX == aCtrlClass )
    {   
        aControlAction = 0;
          
        lpfnGetValue = GetCtrlGetValueFunction( aCtrlClass, aControlAction );

        OSL_ASSERT( lpfnGetValue );

        aNonExecFilePickerState->setValue( 
            sal::static_int_cast< sal_Int16 >( GetDlgCtrlID( hwndControl ) ),
            aControlAction,
            lpfnGetValue( hwndControl ) ); 
        
        aNonExecFilePickerState->setLabel(
            sal::static_int_cast< sal_Int16 >( GetDlgCtrlID( hwndControl ) ),
            getLabel(
                sal::static_int_cast< sal_Int16 >(
                    GetDlgCtrlID( hwndControl ) ) ) );
    }
    else if ( LISTBOX == aCtrlClass )
    {
        // for listboxes we save only the
        // last selected item and the last 
        // selected item index

        aControlAction = GET_SELECTED_ITEM;
        
        lpfnGetValue = GetCtrlGetValueFunction( aCtrlClass, aControlAction );

        aNonExecFilePickerState->setValue( 
            sal::static_int_cast< sal_Int16 >( GetDlgCtrlID( hwndControl ) ),
            aControlAction,
            lpfnGetValue( hwndControl ) );

        aControlAction = ::com::sun::star::ui::dialogs::ControlActions::GET_SELECTED_ITEM_INDEX;
        
        lpfnGetValue = GetCtrlGetValueFunction( aCtrlClass, aControlAction );

        aNonExecFilePickerState->setValue( 
            sal::static_int_cast< sal_Int16 >( GetDlgCtrlID( hwndControl ) ),
            aControlAction,
            lpfnGetValue( hwndControl ) );
    }    
}

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

void SAL_CALL CExecuteFilePickerState::setHwnd( HWND hwndDlg )
{
    m_hwndDlg = hwndDlg;
}

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

inline sal_Bool SAL_CALL CExecuteFilePickerState::IsListboxControl( HWND hwndControl ) const
{
    OSL_PRECOND( IsWindow( hwndControl ), "invalid parameter" );

    CTRL_CLASS aCtrlClass = GetCtrlClass( hwndControl );        
    return ( LISTBOX == aCtrlClass );
}

//---------------------------------------------
// because listboxes (comboboxes) and their labels
// are seperated we have to translate the listbox
// id to their corresponding label id
// the convention is that the label id of a listbox
// is the id of the listbox + 100
//---------------------------------------------

inline sal_Int16 SAL_CALL CExecuteFilePickerState::ListboxIdToListboxLabelId( sal_Int16 aListboxId ) const
{
    return ( aListboxId + LISTBOX_LABEL_OFFSET );
}

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

inline HWND SAL_CALL CExecuteFilePickerState::GetListboxLabelItem( sal_Int16 aControlId ) const
{
    sal_Int16 aLabelId = ListboxIdToListboxLabelId( aControlId );
    HWND hwndCtrl = GetHwndDlgItem( aLabelId );

    OSL_ASSERT( IsWindow( hwndCtrl ) );

    return hwndCtrl;
}

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

HWND SAL_CALL CExecuteFilePickerState::GetHwndDlgItem( sal_Int16 aControlId, sal_Bool bIncludeStdCtrls ) const
{
    OSL_ENSURE( IsWindow( m_hwndDlg ), "no valid parent window set before" );

    HWND hwndCtrl = GetDlgItem( m_hwndDlg, aControlId );

    // maybe it's a control of the dialog itself for instance 
    // the ok and cancel button
    if ( !hwndCtrl && bIncludeStdCtrls )
    {
		hwndCtrl = GetDlgItem(
            GetParent( m_hwndDlg ),
            CommonFilePickerCtrlIdToWinFileOpenCtrlId( aControlId ) );
    }

	return hwndCtrl;
}
