/**************************************************************
 * 
 * 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_ucb.hxx"

/**************************************************************************
								TODO
 **************************************************************************

 - remove root storage access workaround

 *************************************************************************/

#define ROOTSTORAGE_ACCESS_WORKAROUND 1

#include <memory>

#include "com/sun/star/beans/XPropertySet.hpp"
#include "com/sun/star/embed/ElementModes.hpp"
#include "com/sun/star/lang/XSingleServiceFactory.hpp"

#include "tdoc_uri.hxx"
#include "tdoc_docmgr.hxx"
#include "tdoc_stgelems.hxx"

#include "tdoc_storage.hxx"

using namespace com::sun::star;
using namespace tdoc_ucp;


//=========================================================================
//=========================================================================
//
// StorageElementFactory Implementation.
//
//=========================================================================
//=========================================================================

StorageElementFactory::StorageElementFactory(
    const uno::Reference< lang::XMultiServiceFactory > & xSMgr,
    const rtl::Reference< OfficeDocumentsManager > & xDocsMgr )
: m_xDocsMgr( xDocsMgr ),
  m_xSMgr( xSMgr )
{
}

//=========================================================================
StorageElementFactory::~StorageElementFactory()
{
    OSL_ENSURE( m_aMap.size() == 0,
        "StorageElementFactory::~StorageElementFactory - Dangling storages!" );
}

//=========================================================================
uno::Reference< embed::XStorage >
StorageElementFactory::createTemporaryStorage()
	throw ( uno::Exception,
			uno::RuntimeException )
{
	uno::Reference< embed::XStorage > xStorage;
	uno::Reference< lang::XSingleServiceFactory > xStorageFac;
	if ( m_xSMgr.is() )
	{
		xStorageFac = uno::Reference< lang::XSingleServiceFactory >(
   			m_xSMgr->createInstance(
   				rtl::OUString( RTL_CONSTASCII_USTRINGPARAM(
   					"com.sun.star.embed.StorageFactory" ) ) ),
   			uno::UNO_QUERY );
	}

	OSL_ENSURE( xStorageFac.is(), "Can't create storage factory!" );
	if ( xStorageFac.is() )
		xStorage = uno::Reference< embed::XStorage >(
							xStorageFac->createInstance(),
							uno::UNO_QUERY );

	if ( !xStorage.is() )
		throw uno::RuntimeException();

	return xStorage;
}

//=========================================================================
uno::Reference< embed::XStorage >
StorageElementFactory::createStorage( const rtl::OUString & rUri,
                                      StorageAccessMode eMode )
    throw ( embed::InvalidStorageException,
            lang::IllegalArgumentException,
            io::IOException,
            embed::StorageWrappedTargetException,
            uno::RuntimeException )
{
    osl::MutexGuard aGuard( m_aMutex );

    if ( ( eMode != READ ) &&
         ( eMode != READ_WRITE_NOCREATE ) &&
         ( eMode != READ_WRITE_CREATE ) )
        throw lang::IllegalArgumentException(
            rtl::OUString( RTL_CONSTASCII_USTRINGPARAM(
                "Invalid open mode!" ) ),
            uno::Reference< uno::XInterface >(),
            sal_Int16( 2 ) );

    Uri aUri( rUri );
    if ( aUri.isRoot() )
    {
        throw lang::IllegalArgumentException(
            rtl::OUString( RTL_CONSTASCII_USTRINGPARAM(
                "Root never has a storage!" ) ),
            uno::Reference< uno::XInterface >(),
            sal_Int16( 1 ) );
    }

    rtl::OUString aUriKey
        ( ( rUri.getStr()[ rUri.getLength() - 1 ] == sal_Unicode( '/' ) )
          ? rUri.copy( 0, rUri.getLength() - 1 )
          : rUri );

    StorageMap::iterator aIt ( m_aMap.begin() );
    StorageMap::iterator aEnd( m_aMap.end() );

    while ( aIt != aEnd )
    {
        if ( (*aIt).first.first == aUriKey )
        {
            // URI matches. Now, check open mode.
            bool bMatch = true;
            switch ( eMode )
            {
                case READ:
                    // No need to check; storage is at least readable.
                    bMatch = true;
                    break;

                case READ_WRITE_NOCREATE:
                case READ_WRITE_CREATE:
                    // If found storage is writable, it can be used.
                    // If not, a new one must be created.
                    bMatch = (*aIt).first.second;
                    break;
            }

            if ( bMatch )
                break;
        }
        ++aIt;
    }

    if ( aIt == aEnd )
    {
        uno::Reference< embed::XStorage > xParentStorage;

        // documents never have a parent storage.
        if ( !aUri.isDocument() )
        {
            xParentStorage = queryParentStorage( aUriKey, eMode );

            if ( !xParentStorage.is() )
            {
                // requested to create new storage, but failed?
                OSL_ENSURE( eMode != READ_WRITE_CREATE,
                            "Unable to create parent storage!" );
                return xParentStorage;
            }
        }

        uno::Reference< embed::XStorage > xStorage
            = queryStorage( xParentStorage, aUriKey, eMode );

        if ( !xStorage.is() )
        {
            // requested to create new storage, but failed?
            OSL_ENSURE( eMode != READ_WRITE_CREATE,
                        "Unable to create storage!" );
            return xStorage;
        }

        bool bWritable = ( ( eMode == READ_WRITE_NOCREATE )
                            || ( eMode == READ_WRITE_CREATE ) );

        std::auto_ptr< Storage > xElement(
            new Storage( m_xSMgr, this, aUriKey, xParentStorage, xStorage ) );

        aIt = m_aMap.insert(
            StorageMap::value_type(
                std::pair< rtl::OUString, bool >( aUriKey, bWritable ),
                xElement.get() ) ).first;

        aIt->second->m_aContainerIt = aIt;
        xElement.release();
        return aIt->second;
    }
    else if ( osl_incrementInterlockedCount( &aIt->second->m_refCount ) > 1 )
    {
        rtl::Reference< Storage > xElement( aIt->second );
        osl_decrementInterlockedCount( &aIt->second->m_refCount );
        return aIt->second;
    }
    else
    {
        osl_decrementInterlockedCount( &aIt->second->m_refCount );
        aIt->second->m_aContainerIt = m_aMap.end();

        uno::Reference< embed::XStorage > xParentStorage;

        // documents never have a parent storage.
        if ( !aUri.isDocument() )
        {
            xParentStorage = queryParentStorage( aUriKey, eMode );

            if ( !xParentStorage.is() )
            {
                // requested to create new storage, but failed?
                OSL_ENSURE( eMode != READ_WRITE_CREATE,
                            "Unable to create parent storage!" );
                return xParentStorage;
            }
        }

        uno::Reference< embed::XStorage > xStorage
            = queryStorage( xParentStorage, aUriKey, eMode );

        if ( !xStorage.is() )
        {
            // requested to create new storage, but failed?
            OSL_ENSURE( eMode != READ_WRITE_CREATE,
                        "Unable to create storage!" );
            return xStorage;
        }

        aIt->second
            = new Storage( m_xSMgr, this, aUriKey, xParentStorage, xStorage );
        aIt->second->m_aContainerIt = aIt;
        return aIt->second;
    }
}

//=========================================================================
uno::Reference< io::XInputStream >
StorageElementFactory::createInputStream( const rtl::OUString & rUri,
                                          const rtl::OUString & rPassword )
    throw ( embed::InvalidStorageException,
            lang::IllegalArgumentException,
            io::IOException,
            embed::StorageWrappedTargetException,
            packages::WrongPasswordException,
            uno::RuntimeException )
{
    osl::MutexGuard aGuard( m_aMutex );

    uno::Reference< embed::XStorage > xParentStorage
        = queryParentStorage( rUri, READ );

    // Each stream must have a parent storage.
    if ( !xParentStorage.is() )
        return uno::Reference< io::XInputStream >();

    uno::Reference< io::XStream > xStream
        = queryStream( xParentStorage, rUri, rPassword, READ, false );

    if ( !xStream.is() )
        return uno::Reference< io::XInputStream >();

    return xStream->getInputStream();
}

//=========================================================================
uno::Reference< io::XOutputStream >
StorageElementFactory::createOutputStream( const rtl::OUString & rUri,
                                           const rtl::OUString & rPassword,
                                           bool bTruncate )
    throw ( embed::InvalidStorageException,
            lang::IllegalArgumentException,
            io::IOException,
            embed::StorageWrappedTargetException,
            packages::WrongPasswordException,
            uno::RuntimeException )
{
    osl::MutexGuard aGuard( m_aMutex );

    uno::Reference< embed::XStorage > xParentStorage
        = queryParentStorage( rUri, READ_WRITE_CREATE );

    // Each stream must have a parent storage.
    if ( !xParentStorage.is() )
    {
        OSL_ENSURE( false,
                    "StorageElementFactory::createOutputStream - "
                    "Unable to create parent storage!" );
        return uno::Reference< io::XOutputStream >();
    }

    uno::Reference< io::XStream > xStream
        = queryStream(
            xParentStorage, rUri, rPassword, READ_WRITE_CREATE, bTruncate );

    if ( !xStream.is() )
    {
        OSL_ENSURE( false,
                    "StorageElementFactory::createOutputStream - "
                    "Unable to create stream!" );
        return uno::Reference< io::XOutputStream >();
    }

    // Note: We need a wrapper to hold a reference to the parent storage to
    //       ensure that nobody else owns it at the moment we want to commit
    //       our changes. (There can be only one writable instance at a time
    //       and even no writable instance if there is  already another
    //       read-only instance!)
    return uno::Reference< io::XOutputStream >(
        new OutputStream(
            m_xSMgr, rUri, xParentStorage, xStream->getOutputStream() ) );
}

//=========================================================================
uno::Reference< io::XStream >
StorageElementFactory::createStream( const rtl::OUString & rUri,
                                     const rtl::OUString & rPassword,
                                     bool bTruncate )
    throw ( embed::InvalidStorageException,
            lang::IllegalArgumentException,
            io::IOException,
            embed::StorageWrappedTargetException,
            packages::WrongPasswordException,
            uno::RuntimeException )
{
    osl::MutexGuard aGuard( m_aMutex );

    uno::Reference< embed::XStorage > xParentStorage
        = queryParentStorage( rUri, READ_WRITE_CREATE );

    // Each stream must have a parent storage.
    if ( !xParentStorage.is() )
    {
        OSL_ENSURE( false,
                    "StorageElementFactory::createStream - "
                    "Unable to create parent storage!" );
        return uno::Reference< io::XStream >();
    }

    uno::Reference< io::XStream > xStream
        = queryStream(
            xParentStorage, rUri, rPassword, READ_WRITE_NOCREATE, bTruncate );

    if ( !xStream.is() )
    {
        OSL_ENSURE( false,
                    "StorageElementFactory::createStream - "
                    "Unable to create stream!" );
        return uno::Reference< io::XStream >();
    }

    return uno::Reference< io::XStream >(
        new Stream( m_xSMgr, rUri, xParentStorage, xStream ) );
}

//=========================================================================
void StorageElementFactory::releaseElement( Storage * pElement ) SAL_THROW( () )
{
    OSL_ASSERT( pElement );
    osl::MutexGuard aGuard( m_aMutex );
    if ( pElement->m_aContainerIt != m_aMap.end() )
        m_aMap.erase( pElement->m_aContainerIt );
}

//=========================================================================
//
// Non-UNO interface
//
//=========================================================================

uno::Reference< embed::XStorage > StorageElementFactory::queryParentStorage(
        const rtl::OUString & rUri, StorageAccessMode eMode )
    throw ( embed::InvalidStorageException,
            lang::IllegalArgumentException,
            io::IOException,
            embed::StorageWrappedTargetException,
            uno::RuntimeException )
{
    uno::Reference< embed::XStorage > xParentStorage;

    Uri aUri( rUri );
    Uri aParentUri( aUri.getParentUri() );
    if ( !aParentUri.isRoot() )
    {
        xParentStorage = createStorage( aUri.getParentUri(), eMode );
        OSL_ENSURE( xParentStorage.is()
                    // requested to create new storage, but failed?
                    || ( eMode != READ_WRITE_CREATE ),
                    "StorageElementFactory::queryParentStorage - No storage!" );
    }
    return xParentStorage;
}

//=========================================================================
uno::Reference< embed::XStorage > StorageElementFactory::queryStorage(
        const uno::Reference< embed::XStorage > & xParentStorage,
        const rtl::OUString & rUri,
        StorageAccessMode eMode )
    throw ( embed::InvalidStorageException,
            lang::IllegalArgumentException,
            io::IOException,
            embed::StorageWrappedTargetException,
            uno::RuntimeException )
{
    uno::Reference< embed::XStorage > xStorage;

    Uri aUri( rUri );

    if ( !xParentStorage.is() )
    {
        // document storage

        xStorage = m_xDocsMgr->queryStorage( aUri.getDocumentId() );

        if ( !xStorage.is() )
        {
            if ( eMode == READ_WRITE_CREATE )
                throw lang::IllegalArgumentException(
                    rtl::OUString( RTL_CONSTASCII_USTRINGPARAM(
                        "Invalid open mode: document storages cannot be "
                        "created!" ) ),
                    uno::Reference< uno::XInterface >(),
                    sal_Int16( 2 ) );
            else
                throw embed::InvalidStorageException(
                    rtl::OUString( RTL_CONSTASCII_USTRINGPARAM(
                        "Invalid document id!" ) ),
                    uno::Reference< uno::XInterface >() );
        }

        // match xStorage's open mode against requested open mode

        uno::Reference< beans::XPropertySet > xPropSet(
            xStorage, uno::UNO_QUERY );
        OSL_ENSURE( xPropSet.is(),
                    "StorageElementFactory::queryStorage - "
                    "No XPropertySet interface!" );
        try
        {
            uno::Any aPropValue = xPropSet->getPropertyValue(
                rtl::OUString(
                    RTL_CONSTASCII_USTRINGPARAM( "OpenMode" ) ) );

            sal_Int32 nOpenMode = 0;
            if ( aPropValue >>= nOpenMode )
            {
                switch ( eMode )
                {
                    case READ:
                        if ( !( nOpenMode & embed::ElementModes::READ ) )
                        {
                            // document opened, but not readable.
                            throw embed::InvalidStorageException(
                                rtl::OUString( RTL_CONSTASCII_USTRINGPARAM(
                                    "Storage is open, but not readable!" ) ),
                                uno::Reference< uno::XInterface >() );
                        }
                        // storage okay
                        break;

                    case READ_WRITE_NOCREATE:
                    case READ_WRITE_CREATE:
                        if ( !( nOpenMode & embed::ElementModes::WRITE ) )
                        {
                            // document opened, but not writable.
                            throw embed::InvalidStorageException(
                                rtl::OUString( RTL_CONSTASCII_USTRINGPARAM(
                                    "Storage is open, but not writable!" ) ),
                                uno::Reference< uno::XInterface >() );
                        }
                        // storage okay
                        break;
                }
            }
            else
            {
                OSL_ENSURE(
                    false, "Bug! Value of property OpenMode has wrong type!" );

                throw uno::RuntimeException(
                    rtl::OUString( RTL_CONSTASCII_USTRINGPARAM(
                        "Bug! Value of property OpenMode has wrong type!" ) ),
                    uno::Reference< uno::XInterface >() );
            }
        }
        catch ( beans::UnknownPropertyException const & e )
        {
            OSL_ENSURE( false, "Property OpenMode not supported!" );

            throw embed::StorageWrappedTargetException(
                    rtl::OUString( RTL_CONSTASCII_USTRINGPARAM(
                        "Bug! Value of property OpenMode has wrong type!" ) ),
                    uno::Reference< uno::XInterface >(),
                    uno::makeAny( e ) );
        }
        catch ( lang::WrappedTargetException const & e )
        {
            OSL_ENSURE( false, "Caught WrappedTargetException!" );

            throw embed::StorageWrappedTargetException(
                    rtl::OUString( RTL_CONSTASCII_USTRINGPARAM(
                        "WrappedTargetException during getPropertyValue!" ) ),
                    uno::Reference< uno::XInterface >(),
                    uno::makeAny( e ) );
        }
    }
    else
    {
        // sub storage

        const rtl::OUString & rName = aUri.getDecodedName();

        if ( eMode == READ )
        {
            try
            {
                sal_Int32 nOpenMode = embed::ElementModes::READ
                                      | embed::ElementModes::NOCREATE;
                xStorage
                    = xParentStorage->openStorageElement( rName, nOpenMode );
            }
            catch ( io::IOException const & )
            {
                // Another chance: Try to clone storage.
                xStorage = createTemporaryStorage();
                xParentStorage->copyStorageElementLastCommitTo( rName,
                                                                xStorage );
            }
        }
        else
        {
            sal_Int32 nOpenMode = embed::ElementModes::READWRITE;
            if ( eMode == READ_WRITE_NOCREATE )
                nOpenMode |= embed::ElementModes::NOCREATE;

            xStorage = xParentStorage->openStorageElement( rName, nOpenMode );
        }
    }

    OSL_ENSURE( xStorage.is() || ( eMode != READ_WRITE_CREATE ),
                "StorageElementFactory::queryStorage - No storage!" );
    return xStorage;
}

//=========================================================================
uno::Reference< io::XStream >
StorageElementFactory::queryStream(
                const uno::Reference< embed::XStorage > & xParentStorage,
                const rtl::OUString & rUri,
                const rtl::OUString & rPassword,
                StorageAccessMode eMode,
                bool bTruncate )
    throw ( embed::InvalidStorageException,
            lang::IllegalArgumentException,
            io::IOException,
            embed::StorageWrappedTargetException,
            packages::WrongPasswordException,
            uno::RuntimeException )
{
    osl::MutexGuard aGuard( m_aMutex );

    if ( !xParentStorage.is() )
    {
        throw lang::IllegalArgumentException(
            rtl::OUString( RTL_CONSTASCII_USTRINGPARAM(
                "No parent storage!" ) ),
            uno::Reference< uno::XInterface >(),
            sal_Int16( 2 ) );
    }

    Uri aUri( rUri );
    if ( aUri.isRoot() )
    {
        throw lang::IllegalArgumentException(
            rtl::OUString( RTL_CONSTASCII_USTRINGPARAM(
                "Root never is a stream!" ) ),
            uno::Reference< uno::XInterface >(),
            sal_Int16( 2 ) );
    }
    else if ( aUri.isDocument() )
    {
        throw lang::IllegalArgumentException(
            rtl::OUString( RTL_CONSTASCII_USTRINGPARAM(
                "A document never is a stream!" ) ),
            uno::Reference< uno::XInterface >(),
            sal_Int16( 2 ) );
    }

    sal_Int32 nOpenMode;
    switch ( eMode )
    {
        case READ:
            nOpenMode = embed::ElementModes::READ
                        | embed::ElementModes::NOCREATE
                        | embed::ElementModes::SEEKABLE;
            break;

        case READ_WRITE_NOCREATE:
            nOpenMode = embed::ElementModes::READWRITE
                        | embed::ElementModes::NOCREATE
                        | embed::ElementModes::SEEKABLE;

            if ( bTruncate )
                nOpenMode |= embed::ElementModes::TRUNCATE;

            break;

        case READ_WRITE_CREATE:
            nOpenMode = embed::ElementModes::READWRITE
                        | embed::ElementModes::SEEKABLE;

            if ( bTruncate )
                nOpenMode |= embed::ElementModes::TRUNCATE;

            break;

        default:
            OSL_ENSURE( false,
                "StorageElementFactory::queryStream : Unknown open mode!" );

            throw embed::InvalidStorageException(
                rtl::OUString( RTL_CONSTASCII_USTRINGPARAM(
                    "Unknown open mode!" ) ),
                uno::Reference< uno::XInterface >() );
    }

    // No object re-usage mechanism; streams are seekable => not stateless.

    uno::Reference< io::XStream > xStream;
    if ( rPassword.getLength() > 0 )
    {
        if ( eMode == READ )
        {
            try
            {
                xStream = xParentStorage->cloneEncryptedStreamElement(
                                                         aUri.getDecodedName(),
                                                         rPassword );
            }
            catch ( packages::NoEncryptionException const & )
            {
                xStream
                    = xParentStorage->cloneStreamElement( aUri.getDecodedName() );
            }
        }
        else
        {
            try
            {
                xStream = xParentStorage->openEncryptedStreamElement(
                                                         aUri.getDecodedName(),
                                                         nOpenMode,
                                                         rPassword );
            }
            catch ( packages::NoEncryptionException const & )
            {
                xStream
                    = xParentStorage->openStreamElement( aUri.getDecodedName(),
                                                         nOpenMode );
            }
        }
    }
    else
    {
        if ( eMode == READ )
        {
            xStream = xParentStorage->cloneStreamElement( aUri.getDecodedName() );
        }
        else
        {
            xStream = xParentStorage->openStreamElement( aUri.getDecodedName(),
                                                         nOpenMode );
        }
    }

    if ( !xStream.is() )
    {
        throw embed::InvalidStorageException(
            rtl::OUString( RTL_CONSTASCII_USTRINGPARAM(
                "No stream!" ) ),
            uno::Reference< uno::XInterface >() );
    }

    return xStream;
}
