/**************************************************************
 * 
 * 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 "VistaFilePickerEventHandler.hxx"
#include "asyncrequests.hxx"

#include <com/sun/star/lang/XMultiServiceFactory.hpp>
#include <com/sun/star/embed/XStorage.hpp>
#include <com/sun/star/document/XDocumentRevisionListPersistence.hpp>
#include <com/sun/star/util/RevisionTag.hpp>
#include <com/sun/star/ui/dialogs/CommonFilePickerElementIds.hpp>
#include <com/sun/star/ui/dialogs/ExtendedFilePickerElementIds.hpp>

#include <comphelper/processfactory.hxx>
#include <comphelper/storagehelper.hxx>
//#include <tools/urlobj.hxx>
//#include <unotools/ucbhelper.hxx>

#include <osl/file.hxx>

//------------------------------------------------------------------------
// namespace directives
//------------------------------------------------------------------------

namespace css = ::com::sun::star;

namespace fpicker{
namespace win32{
namespace vista{

//------------------------------------------------------------------------
// defines
//------------------------------------------------------------------------

//-----------------------------------------------------------------------------------------
VistaFilePickerEventHandler::VistaFilePickerEventHandler(IVistaFilePickerInternalNotify* pInternalNotify)
    : m_nRefCount           (0       )
    , m_nListenerHandle     (0       )
    , m_pDialog             (        )
    , m_lListener           (m_aMutex)
	, m_pInternalNotify     (pInternalNotify)
{
}

//-----------------------------------------------------------------------------------------
VistaFilePickerEventHandler::~VistaFilePickerEventHandler()
{
}

//-----------------------------------------------------------------------------------------
HRESULT STDMETHODCALLTYPE VistaFilePickerEventHandler::QueryInterface(REFIID rIID    ,
                                                                      void** ppObject)
{
	*ppObject=NULL;

	if ( rIID == IID_IUnknown )
		*ppObject = (IUnknown*)(IFileDialogEvents*)this;

	if ( rIID == IID_IFileDialogEvents )
		*ppObject = (IFileDialogEvents*)this;

	if ( rIID == IID_IFileDialogControlEvents )
		*ppObject = (IFileDialogControlEvents*)this;
    
	if ( *ppObject != NULL )
	{
		((IUnknown*)*ppObject)->AddRef();
		return S_OK;
	}

	return E_NOINTERFACE;
}

//-----------------------------------------------------------------------------------------
ULONG STDMETHODCALLTYPE VistaFilePickerEventHandler::AddRef()
{
	return osl_incrementInterlockedCount(&m_nRefCount);
}

//-----------------------------------------------------------------------------------------
ULONG STDMETHODCALLTYPE VistaFilePickerEventHandler::Release()
{
	ULONG nReturn = --m_nRefCount;
	if ( m_nRefCount == 0 )
		delete this;

    return nReturn;
}

//-----------------------------------------------------------------------------------------
STDMETHODIMP VistaFilePickerEventHandler::OnFileOk(IFileDialog* /*pDialog*/)
{
    return E_NOTIMPL;
}

//-----------------------------------------------------------------------------------------
STDMETHODIMP VistaFilePickerEventHandler::OnFolderChanging(IFileDialog* /*pDialog*/,
                                                           IShellItem*  /*pFolder*/)
{
    return E_NOTIMPL;
}

//-----------------------------------------------------------------------------------------
STDMETHODIMP VistaFilePickerEventHandler::OnFolderChange(IFileDialog* /*pDialog*/)
{
    impl_sendEvent(E_DIRECTORY_CHANGED, 0);
    return S_OK;
}

//-----------------------------------------------------------------------------
::rtl::OUString lcl_getURLFromShellItem2 (IShellItem* pItem)
{
    LPOLESTR pStr = NULL;
	::rtl::OUString sURL;

	SIGDN   eConversion = SIGDN_FILESYSPATH;
    HRESULT hr          = pItem->GetDisplayName ( eConversion, &pStr );

    if ( FAILED(hr) )
	{
		eConversion = SIGDN_URL;
		hr          = pItem->GetDisplayName ( eConversion, &pStr );

		if ( FAILED(hr) )
			return ::rtl::OUString();

		sURL = ::rtl::OUString(reinterpret_cast<sal_Unicode*>(pStr));
	}
	else
	{
		::osl::FileBase::getFileURLFromSystemPath( reinterpret_cast<sal_Unicode*>(pStr), sURL );
	}

    CoTaskMemFree (pStr);
    return sURL;
}

//-----------------------------------------------------------------------------------------
void lcl_updateVersionListDirectly(IFileDialog* pDialog)
{
    static const ::rtl::OUString SERVICENAME_REVISIONPERSISTENCE = ::rtl::OUString::createFromAscii("com.sun.star.document.DocumentRevisionListPersistence");
    static const ::sal_Int16     CONTROL_VERSIONLIST             = css::ui::dialogs::ExtendedFilePickerElementIds::LISTBOX_VERSION;

    TFileDialog          iDialog   (pDialog);
    TFileOpenDialog      iOpen     ;
    TFileDialogCustomize iCustomize;
    
#ifdef __MINGW32__
    iDialog->QueryInterface(IID_IFileOpenDialog, (void**)(&iOpen));
    iDialog->QueryInterface(IID_IFileDialogCustomize, (void**)(&iCustomize));
#else
    iDialog.query(&iOpen     );
    iDialog.query(&iCustomize);
#endif

    // make sure version list match to the current selection always ...
    // at least an empty version list will be better then the wrong one .-)
    iCustomize->RemoveAllControlItems(CONTROL_VERSIONLIST);
    
    HRESULT                   hResult = E_FAIL;
    ComPtr< IShellItemArray > iItems;
    ComPtr< IShellItem >      iItem;

    if (iOpen.is())
    {
        hResult = iOpen->GetSelectedItems(&iItems);
        if (FAILED(hResult))
            return;

        DWORD nCount;
        hResult = iItems->GetCount(&nCount);
        if ( FAILED(hResult) )
            return;

        // we can show one version list only within control
        if (nCount != 1)
            return;
        
        hResult = iItems->GetItemAt(0, &iItem);
    }    
    else
    if (iDialog.is())
        hResult = iDialog->GetCurrentSelection(&iItem);

    if ( FAILED(hResult) )
        return;
    
    const ::rtl::OUString sURL = lcl_getURLFromShellItem2(iItem);
    if (sURL.getLength() < 1)
        return;
/*
    INetURLObject aURL(sURL);
    if (aURL.GetProtocol() != INET_PROT_FILE)
        return;
    
    ::rtl::OUString sMain = aURL.GetMainURL(INetURLObject::NO_DECODE);
    if ( ! ::utl::UCBContentHelper::IsDocument(sURL))
        return;
*/    
    try
    {
        css::uno::Reference< css::embed::XStorage > xStorage = ::comphelper::OStorageHelper::GetStorageFromURL(sURL, css::embed::ElementModes::READ);
        if ( ! xStorage.is() )
            return;
        
        css::uno::Reference< css::lang::XMultiServiceFactory >                 xSMGR     = ::comphelper::getProcessServiceFactory();
        css::uno::Reference< css::document::XDocumentRevisionListPersistence > xReader   (xSMGR->createInstance(SERVICENAME_REVISIONPERSISTENCE), css::uno::UNO_QUERY_THROW);
        css::uno::Sequence< css::util::RevisionTag >                           lVersions = xReader->load(xStorage);
        
        for (::sal_Int32 i=0; i<lVersions.getLength(); ++i)
        {
            const css::util::RevisionTag& aTag = lVersions[i];
            iCustomize->AddControlItem(CONTROL_VERSIONLIST, i, reinterpret_cast<LPCTSTR>(aTag.Identifier.getStr()));
        }
        iCustomize->SetSelectedControlItem(CONTROL_VERSIONLIST, 0);
    }
    catch(const css::uno::Exception&)
    {}
}

//-----------------------------------------------------------------------------------------
STDMETHODIMP VistaFilePickerEventHandler::OnSelectionChange(IFileDialog* /*pDialog*/)
{
    impl_sendEvent(E_FILE_SELECTION_CHANGED, 0);
    //lcl_updateVersionListDirectly(pDialog);
    return S_OK;
}

//-----------------------------------------------------------------------------------------
STDMETHODIMP VistaFilePickerEventHandler::OnShareViolation(IFileDialog*                 /*pDialog*/  ,

                                                           IShellItem*                  /*pItem*/    ,

                                                           FDE_SHAREVIOLATION_RESPONSE* /*pResponse*/)
{
    impl_sendEvent(E_CONTROL_STATE_CHANGED, css::ui::dialogs::CommonFilePickerElementIds::LISTBOX_FILTER);
    return S_OK;
}

//-----------------------------------------------------------------------------------------
STDMETHODIMP VistaFilePickerEventHandler::OnTypeChange(IFileDialog* pDialog)
{
    UINT nFileTypeIndex;
	HRESULT hResult = pDialog->GetFileTypeIndex( &nFileTypeIndex );
	
	if ( hResult == S_OK )
	{
		if ( m_pInternalNotify->onFileTypeChanged( nFileTypeIndex ))
			impl_sendEvent(E_CONTROL_STATE_CHANGED, css::ui::dialogs::CommonFilePickerElementIds::LISTBOX_FILTER);
	}

    return S_OK;
}

//-----------------------------------------------------------------------------------------
STDMETHODIMP VistaFilePickerEventHandler::OnOverwrite(IFileDialog*            /*pDialog*/  ,
                                                      IShellItem*             /*pItem*/    ,
                                                      FDE_OVERWRITE_RESPONSE* /*pResponse*/)
{
    return E_NOTIMPL;
}

//-----------------------------------------------------------------------------------------
STDMETHODIMP VistaFilePickerEventHandler::OnItemSelected(IFileDialogCustomize* /*pCustomize*/,

                                                         DWORD                   nIDCtl      ,

                                                         DWORD                 /*nIDItem*/   )
{

    impl_sendEvent(E_CONTROL_STATE_CHANGED, static_cast<sal_Int16>( nIDCtl ));
    return S_OK;
}

//-----------------------------------------------------------------------------------------
STDMETHODIMP VistaFilePickerEventHandler::OnButtonClicked(IFileDialogCustomize* /*pCustomize*/,
                                                          DWORD                 nIDCtl    )
{

    impl_sendEvent(E_CONTROL_STATE_CHANGED, static_cast<sal_Int16>( nIDCtl));
    return S_OK;
}

//-----------------------------------------------------------------------------------------
STDMETHODIMP VistaFilePickerEventHandler::OnCheckButtonToggled(IFileDialogCustomize* /*pCustomize*/,
                                                               DWORD                 nIDCtl    ,
                                                               BOOL                  bChecked  )
{
	if (nIDCtl == css::ui::dialogs::ExtendedFilePickerElementIds::CHECKBOX_AUTOEXTENSION)
		m_pInternalNotify->onAutoExtensionChanged(bChecked);

    impl_sendEvent(E_CONTROL_STATE_CHANGED, static_cast<sal_Int16>( nIDCtl));

    return S_OK;
}

//-----------------------------------------------------------------------------------------
STDMETHODIMP VistaFilePickerEventHandler::OnControlActivating(IFileDialogCustomize* /*pCustomize*/,
                                                              DWORD                 nIDCtl    )
{
    impl_sendEvent(E_CONTROL_STATE_CHANGED, static_cast<sal_Int16>( nIDCtl));
    return S_OK;
}

//-----------------------------------------------------------------------------------------
void SAL_CALL VistaFilePickerEventHandler::addFilePickerListener( const css::uno::Reference< css::ui::dialogs::XFilePickerListener >& xListener ) 
    throw( css::uno::RuntimeException )
{
    m_lListener.addInterface(::getCppuType( (const css::uno::Reference< css::ui::dialogs::XFilePickerListener >*)NULL ), xListener);
}

//-----------------------------------------------------------------------------------------
void SAL_CALL VistaFilePickerEventHandler::removeFilePickerListener( const css::uno::Reference< css::ui::dialogs::XFilePickerListener >& xListener ) 
    throw( css::uno::RuntimeException )
{
    m_lListener.removeInterface(::getCppuType( (const css::uno::Reference< css::ui::dialogs::XFilePickerListener >*)NULL ), xListener);
}

//-----------------------------------------------------------------------------------------
void VistaFilePickerEventHandler::startListening( const TFileDialog& pBroadcaster )
{
    static const sal_Bool STARTUP_SUSPENDED = sal_True;
    static const sal_Bool STARTUP_WORKING   = sal_False;

    if (m_pDialog.is())
        return;
    
    m_pDialog = pBroadcaster;
    m_pDialog->Advise(this, &m_nListenerHandle);
}

//-----------------------------------------------------------------------------------------
void VistaFilePickerEventHandler::stopListening()
{
    if (m_pDialog.is())
    {
        m_pDialog->Unadvise(m_nListenerHandle);
        m_pDialog.release();
    }
}

static const ::rtl::OUString PROP_CONTROL_ID      = ::rtl::OUString::createFromAscii("control_id");
static const ::rtl::OUString PROP_PICKER_LISTENER = ::rtl::OUString::createFromAscii("picker_listener");

//-----------------------------------------------------------------------------------------
class AsyncPickerEvents : public RequestHandler
{
public:
    
    AsyncPickerEvents()
    {}
    
    virtual ~AsyncPickerEvents()
    {}

    virtual void before()
    {}
    
    virtual void doRequest(const RequestRef& rRequest)
    {
        const ::sal_Int32 nEventID   = rRequest->getRequest();
        const ::sal_Int16 nControlID = rRequest->getArgumentOrDefault(PROP_CONTROL_ID, (::sal_Int16)0);
        const css::uno::Reference< css::ui::dialogs::XFilePickerListener > xListener = rRequest->getArgumentOrDefault(PROP_PICKER_LISTENER, css::uno::Reference< css::ui::dialogs::XFilePickerListener >());
    
        if ( ! xListener.is())
            return;
        
        css::ui::dialogs::FilePickerEvent aEvent;
        aEvent.ElementId = nControlID;
        
        switch (nEventID)
        {
            case VistaFilePickerEventHandler::E_FILE_SELECTION_CHANGED :
                    xListener->fileSelectionChanged(aEvent);
                    break;
            
            case VistaFilePickerEventHandler::E_DIRECTORY_CHANGED :
                    xListener->directoryChanged(aEvent);
                    break;
            
            case VistaFilePickerEventHandler::E_HELP_REQUESTED :
                    xListener->helpRequested(aEvent);
                    break;
            
            case VistaFilePickerEventHandler::E_CONTROL_STATE_CHANGED :
                    xListener->controlStateChanged(aEvent);
                    break;
            
            case VistaFilePickerEventHandler::E_DIALOG_SIZE_CHANGED :
                    xListener->dialogSizeChanged();
                    break;
            
            // no default here. Let compiler detect changes on enum set !
        }
    }
    
    virtual void after()
    {}
};

//-----------------------------------------------------------------------------------------
void VistaFilePickerEventHandler::impl_sendEvent(  EEventType eEventType,
                                                 ::sal_Int16  nControlID)
{
    static AsyncRequests aNotify(RequestHandlerRef(new AsyncPickerEvents()));

    ::cppu::OInterfaceContainerHelper* pContainer = m_lListener.getContainer( ::getCppuType( ( const css::uno::Reference< css::ui::dialogs::XFilePickerListener >*) NULL ) );
    if ( ! pContainer)
        return;
    
    ::cppu::OInterfaceIteratorHelper pIterator(*pContainer);
    while (pIterator.hasMoreElements())
    {
        try
        {
            css::uno::Reference< css::ui::dialogs::XFilePickerListener > xListener (pIterator.next(), css::uno::UNO_QUERY);
        
            RequestRef rRequest(new Request());
            rRequest->setRequest (eEventType);
            rRequest->setArgument(PROP_PICKER_LISTENER, xListener);
			if ( nControlID )
				rRequest->setArgument(PROP_CONTROL_ID, nControlID);
        
            aNotify.triggerRequestDirectly(rRequest);
            //aNotify.triggerRequestNonBlocked(rRequest);
        }
        catch(const css::uno::RuntimeException&)
        {
            pIterator.remove();
        }
    }
}

} // namespace vista
} // namespace win32
} // namespace fpicker
