/**************************************************************
 * 
 * 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_embeddedobj.hxx"
#include <com/sun/star/frame/XFrame.hpp>
#include <com/sun/star/frame/XController.hpp>
#include <com/sun/star/frame/XComponentLoader.hpp>
#include <com/sun/star/awt/XTopWindow.hpp>
#include <com/sun/star/embed/XClassifiedObject.hpp>
#include <com/sun/star/io/XStream.hpp>
#include <com/sun/star/io/XInputStream.hpp>
#include <com/sun/star/io/XOutputStream.hpp>
#include <com/sun/star/io/XSeekable.hpp>
#include <com/sun/star/task/XInteractionHandler.hpp>
#include <com/sun/star/ucb/XSimpleFileAccess.hpp>
#include <com/sun/star/util/XCloseable.hpp>
#include <com/sun/star/beans/XPropertySet.hpp>

#ifndef _COM_SUN_STAR_DOCUMENT_XEVENTBRODCASTER_HPP_
#include <com/sun/star/document/XEventBroadcaster.hpp>
#endif
#include <com/sun/star/document/XEventListener.hpp>
#include <com/sun/star/document/XTypeDetection.hpp>
#include <com/sun/star/container/XNameAccess.hpp>
#include <cppuhelper/implbase1.hxx>
#include <comphelper/storagehelper.hxx>
#include <comphelper/mimeconfighelper.hxx>

#include "ownview.hxx"


using namespace ::com::sun::star;
using namespace ::comphelper;

::rtl::OUString GetNewTempFileURL_Impl( const uno::Reference< lang::XMultiServiceFactory >& xFactory ) throw( io::IOException );
::rtl::OUString GetNewFilledTempFile_Impl( const uno::Reference< io::XInputStream >& xInStream, const uno::Reference< lang::XMultiServiceFactory >& xFactory ) throw( io::IOException );
sal_Bool KillFile_Impl( const ::rtl::OUString& aURL, const uno::Reference< lang::XMultiServiceFactory >& xFactory );
uno::Reference< io::XStream > TryToGetAcceptableFormat_Impl( const uno::Reference< io::XStream >& xStream, const uno::Reference< lang::XMultiServiceFactory >& xFactory ) throw ( uno::Exception );

//========================================================
// Dummy interaction handler
//========================================================
//--------------------------------------------------------
class DummyHandler_Impl : public ::cppu::WeakImplHelper1< task::XInteractionHandler >
{
public:
	DummyHandler_Impl() {}
	~DummyHandler_Impl();

	virtual void SAL_CALL handle( const uno::Reference< task::XInteractionRequest >& xRequest )
			throw( uno::RuntimeException );
};

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

//--------------------------------------------------------
void SAL_CALL DummyHandler_Impl::handle( const uno::Reference< task::XInteractionRequest >& )
		throw( uno::RuntimeException )
{
	return;
}

//========================================================
// Object viewer
//========================================================
//--------------------------------------------------------
OwnView_Impl::OwnView_Impl( const uno::Reference< lang::XMultiServiceFactory >& xFactory,
							const uno::Reference< io::XInputStream >& xInputStream )
: m_xFactory( xFactory )
, m_bBusy( sal_False )
, m_bUseNative( sal_False )
{
	if ( !xFactory.is() || !xInputStream.is() )
		throw uno::RuntimeException();

	m_aTempFileURL = GetNewFilledTempFile_Impl( xInputStream, m_xFactory );
}

//--------------------------------------------------------
OwnView_Impl::~OwnView_Impl()
{
	try {
		KillFile_Impl( m_aTempFileURL, m_xFactory );
	} catch( uno::Exception& ) {}

	try {
		if ( m_aNativeTempURL.getLength() )
			KillFile_Impl( m_aNativeTempURL, m_xFactory );
	} catch( uno::Exception& ) {}
}

//--------------------------------------------------------
sal_Bool OwnView_Impl::CreateModelFromURL( const ::rtl::OUString& aFileURL )
{
	sal_Bool bResult = sal_False;

	if ( aFileURL.getLength() )
	{
		try {
			uno::Reference < frame::XComponentLoader > xDocumentLoader( 
							m_xFactory->createInstance ( 
										::rtl::OUString::createFromAscii( "com.sun.star.frame.Desktop" ) ),
							uno::UNO_QUERY );

			if ( xDocumentLoader.is() )
			{
				uno::Sequence< beans::PropertyValue > aArgs( m_aFilterName.getLength() ? 5 : 4 );
	
				aArgs[0].Name = ::rtl::OUString::createFromAscii( "URL" );
				aArgs[0].Value <<= aFileURL;
	
				aArgs[1].Name = ::rtl::OUString::createFromAscii( "ReadOnly" );
				aArgs[1].Value <<= sal_True;
	
				aArgs[2].Name = ::rtl::OUString::createFromAscii( "InteractionHandler" );
				aArgs[2].Value <<= uno::Reference< task::XInteractionHandler >(
									static_cast< ::cppu::OWeakObject* >( new DummyHandler_Impl() ), uno::UNO_QUERY );

				aArgs[3].Name = ::rtl::OUString::createFromAscii( "DontEdit" );
				aArgs[3].Value <<= sal_True;
				
				if ( m_aFilterName.getLength() )
				{
					aArgs[4].Name = ::rtl::OUString::createFromAscii( "FilterName" );
					aArgs[4].Value <<= m_aFilterName;
				}

				uno::Reference< frame::XModel > xModel( xDocumentLoader->loadComponentFromURL(
																aFileURL,
																::rtl::OUString::createFromAscii( "_blank" ),
																0,
																aArgs ),
															uno::UNO_QUERY );
	
				if ( xModel.is() )
				{
					uno::Reference< document::XEventBroadcaster > xBroadCaster( xModel, uno::UNO_QUERY );
					if ( xBroadCaster.is() )
						xBroadCaster->addEventListener( uno::Reference< document::XEventListener >(
																static_cast< ::cppu::OWeakObject* >( this ),
									 							uno::UNO_QUERY ) );
	
					uno::Reference< util::XCloseable > xCloseable( xModel, uno::UNO_QUERY );
					if ( xCloseable.is() )
					{
						xCloseable->addCloseListener( uno::Reference< util::XCloseListener >(
																		static_cast< ::cppu::OWeakObject* >( this ),
										  								uno::UNO_QUERY ) );
	
						::osl::MutexGuard aGuard( m_aMutex );
						m_xModel = xModel;	
						bResult = sal_True;
					}
				}
			}
		}
		catch( uno::Exception& )
		{
		}
	}

	return bResult;
}

//--------------------------------------------------------
sal_Bool OwnView_Impl::CreateModel( sal_Bool bUseNative )
{
	sal_Bool bResult = sal_False;

	try {
		bResult = CreateModelFromURL( bUseNative ? m_aNativeTempURL : m_aTempFileURL );
	}
	catch( uno::Exception& )
	{
	}

	return bResult;
}

//--------------------------------------------------------
::rtl::OUString OwnView_Impl::GetFilterNameFromExtentionAndInStream(
                                                    const ::com::sun::star::uno::Reference< ::com::sun::star::lang::XMultiServiceFactory >& xFactory,
                                                    const ::rtl::OUString& aNameWithExtention,
                                                    const uno::Reference< io::XInputStream >& xInputStream )
{
	if ( !xInputStream.is() )
		throw uno::RuntimeException();

	uno::Reference< document::XTypeDetection > xTypeDetection(
			xFactory->createInstance( ::rtl::OUString::createFromAscii( "com.sun.star.document.TypeDetection" ) ),
			uno::UNO_QUERY_THROW );

	::rtl::OUString aTypeName;

	if ( aNameWithExtention.getLength() )
	{
		::rtl::OUString aURLToAnalyze =
				( ::rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "file:///" ) ) + aNameWithExtention );
		aTypeName = xTypeDetection->queryTypeByURL( aURLToAnalyze );
	}

	uno::Sequence< beans::PropertyValue > aArgs( aTypeName.getLength() ? 3 : 2 );
	aArgs[0].Name = ::rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "URL" ) );
	aArgs[0].Value <<= ::rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "private:stream" ) );
	aArgs[1].Name = ::rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "InputStream" ) );
	aArgs[1].Value <<= xInputStream;
	if ( aTypeName.getLength() )
	{
		aArgs[2].Name = ::rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "TypeName" ) );
		aArgs[2].Value <<= aTypeName;
	}

	aTypeName = xTypeDetection->queryTypeByDescriptor( aArgs, sal_True );

	::rtl::OUString aFilterName;
	for ( sal_Int32 nInd = 0; nInd < aArgs.getLength(); nInd++ )
		if ( aArgs[nInd].Name.equalsAscii( "FilterName" ) )
			aArgs[nInd].Value >>= aFilterName;

	if ( !aFilterName.getLength() && aTypeName.getLength() )
	{
		// get the default filter name for the type
		uno::Reference< container::XNameAccess > xNameAccess( xTypeDetection, uno::UNO_QUERY_THROW );
		uno::Sequence< beans::PropertyValue > aTypes;

		if ( xNameAccess.is() && ( xNameAccess->getByName( aTypeName ) >>= aTypes ) )
		{
			for ( sal_Int32 nInd = 0; nInd < aTypes.getLength(); nInd++ )
			{
				if ( aTypes[nInd].Name.equalsAscii( "PreferredFilter" ) && ( aTypes[nInd].Value >>= aFilterName ) )
				{
					aTypes[nInd].Value >>= aFilterName;
					break;
				}
			}
		}
	}

	return aFilterName;
}

//--------------------------------------------------------
sal_Bool OwnView_Impl::ReadContentsAndGenerateTempFile( const uno::Reference< io::XInputStream >& xInStream,
														sal_Bool bParseHeader )
{
	uno::Reference< io::XSeekable > xSeekable( xInStream, uno::UNO_QUERY_THROW );
	xSeekable->seek( 0 );

	// create m_aNativeTempURL
	::rtl::OUString aNativeTempURL;
	uno::Reference < beans::XPropertySet > xNativeTempFile(
			m_xFactory->createInstance( ::rtl::OUString::createFromAscii( "com.sun.star.io.TempFile" ) ),
			uno::UNO_QUERY_THROW );
	uno::Reference < io::XStream > xNativeTempStream( xNativeTempFile, uno::UNO_QUERY_THROW );
	uno::Reference < io::XOutputStream > xNativeOutTemp = xNativeTempStream->getOutputStream();
	uno::Reference < io::XInputStream > xNativeInTemp = xNativeTempStream->getInputStream();
	if ( !xNativeOutTemp.is() || !xNativeInTemp.is() )
		throw uno::RuntimeException();

	try {
		xNativeTempFile->setPropertyValue( ::rtl::OUString::createFromAscii( "RemoveFile" ), uno::makeAny( sal_False ) );
		uno::Any aUrl = xNativeTempFile->getPropertyValue( ::rtl::OUString::createFromAscii( "Uri" ) );
		aUrl >>= aNativeTempURL;
	}
	catch ( uno::Exception& )
	{
	}

	sal_Bool bFailed = sal_False;
	::rtl::OUString aFileSuffix;

	if ( bParseHeader )
	{
		uno::Sequence< sal_Int8 > aReadSeq( 4 );
		// read the complete size of the Object Package
		if ( xInStream->readBytes( aReadSeq, 4 ) != 4 )
			return sal_False;
/*
		sal_uInt32 nLength = (sal_uInt8)aReadSeq[0]
							+ (sal_uInt8)aReadSeq[1] * 0x100
							+ (sal_uInt8)aReadSeq[2] * 0x10000
							+ (sal_uInt8)aReadSeq[3] * 0x1000000;
*/
		// read the first header ( have no idea what does this header mean )
		if ( xInStream->readBytes( aReadSeq, 2 ) != 2 || aReadSeq[0] != 2 || aReadSeq[1] != 0 )
			return sal_False;

		// read file name
		// only extension is interesting so only subset of symbols is accepted
		do
		{
			if ( xInStream->readBytes( aReadSeq, 1 ) != 1 )
				return sal_False;

			if (
                (aReadSeq[0] >= '0' && aReadSeq[0] <= '9') ||
                (aReadSeq[0] >= 'a' && aReadSeq[0] <= 'z') ||
                (aReadSeq[0] >= 'A' && aReadSeq[0] <= 'Z') ||
                aReadSeq[0] == '.' 
               )
            {
				aFileSuffix += ::rtl::OUString::valueOf( (sal_Unicode) aReadSeq[0] );
            }

		} while( aReadSeq[0] );

		// skip url
		do
		{
			if ( xInStream->readBytes( aReadSeq, 1 ) != 1 )
				return sal_False;
		} while( aReadSeq[0] );

		// check the next header
		if ( xInStream->readBytes( aReadSeq, 4 ) != 4
	  	|| aReadSeq[0] || aReadSeq[1] || aReadSeq[2] != 3 || aReadSeq[3] )
			return sal_False;

		// get the size of the next entry
		if ( xInStream->readBytes( aReadSeq, 4 ) != 4 )
			return sal_False;

		sal_uInt32 nUrlSize = (sal_uInt8)aReadSeq[0]
							+ (sal_uInt8)aReadSeq[1] * 0x100
							+ (sal_uInt8)aReadSeq[2] * 0x10000
							+ (sal_uInt8)aReadSeq[3] * 0x1000000;
		sal_Int64 nTargetPos = xSeekable->getPosition() + nUrlSize;

		xSeekable->seek( nTargetPos );
	
		// get the size of stored data
		if ( xInStream->readBytes( aReadSeq, 4 ) != 4 )
			return sal_False;

		sal_uInt32 nDataSize = (sal_uInt8)aReadSeq[0]
							+ (sal_uInt8)aReadSeq[1] * 0x100
							+ (sal_uInt8)aReadSeq[2] * 0x10000
							+ (sal_uInt8)aReadSeq[3] * 0x1000000;

		aReadSeq.realloc( 32000 );
		sal_uInt32 nRead = 0;
		while ( nRead < nDataSize )
		{
			sal_uInt32 nToRead = ( nDataSize - nRead > 32000 ) ? 32000 : nDataSize - nRead;
			sal_uInt32 nLocalRead = xInStream->readBytes( aReadSeq, nToRead );
			
	
			if ( !nLocalRead )
			{
				bFailed = sal_True;
				break;
			}
			else if ( nLocalRead == 32000 )
				xNativeOutTemp->writeBytes( aReadSeq );
			else
			{
				uno::Sequence< sal_Int8 > aToWrite( aReadSeq );
				aToWrite.realloc( nLocalRead );
				xNativeOutTemp->writeBytes( aToWrite );
			}
	
			nRead += nLocalRead;
		}
	}
	else
	{
		uno::Sequence< sal_Int8 > aData( 8 );
		if ( xInStream->readBytes( aData, 8 ) == 8
		  && aData[0] == -1 && aData[1] == -1 && aData[2] == -1 && aData[3] == -1
		  && ( aData[4] == 2 || aData[4] == 3 ) && aData[5] == 0 && aData[6] == 0 && aData[7] == 0 )
		{
			// the header has to be removed
			xSeekable->seek( 40 );
		}
		else
		{
			// the usual Ole10Native format
			xSeekable->seek( 4 );
		}

		::comphelper::OStorageHelper::CopyInputToOutput( xInStream, xNativeOutTemp );
	}
	
	xNativeOutTemp->closeOutput();

	// The temporary native file is created, now the filter must be detected
	if ( !bFailed )
	{
		m_aFilterName = GetFilterNameFromExtentionAndInStream( m_xFactory, aFileSuffix, xNativeInTemp );
		m_aNativeTempURL = aNativeTempURL;
	}

	return !bFailed;
}

//--------------------------------------------------------
void OwnView_Impl::CreateNative()
{
	if ( m_aNativeTempURL.getLength() )
		return;

	try
	{
		uno::Reference < ucb::XSimpleFileAccess > xAccess(
				m_xFactory->createInstance (
						::rtl::OUString::createFromAscii( "com.sun.star.ucb.SimpleFileAccess" ) ),
				uno::UNO_QUERY_THROW );

		uno::Reference< io::XInputStream > xInStream = xAccess->openFileRead( m_aTempFileURL );
		if ( !xInStream.is() )
			throw uno::RuntimeException();

		uno::Sequence< uno::Any > aArgs( 1 );
		aArgs[0] <<= xInStream;
		uno::Reference< container::XNameAccess > xNameAccess(
				m_xFactory->createInstanceWithArguments( 
						::rtl::OUString::createFromAscii( "com.sun.star.embed.OLESimpleStorage" ),
						aArgs ),
				uno::UNO_QUERY_THROW );

		::rtl::OUString aSubStreamName = ::rtl::OUString::createFromAscii( "\1Ole10Native" );
		uno::Reference< embed::XClassifiedObject > xStor( xNameAccess, uno::UNO_QUERY_THROW );
		uno::Sequence< sal_Int8 > aStorClassID = xStor->getClassID();

		if ( xNameAccess->hasByName( aSubStreamName ) )
		{
			sal_uInt8 aClassID[] =
				{ 0x00, 0x03, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46 };
			uno::Sequence< sal_Int8 > aPackageClassID( (sal_Int8*)aClassID, 16 );

			uno::Reference< io::XStream > xSubStream;
			xNameAccess->getByName( aSubStreamName ) >>= xSubStream;
			if ( xSubStream.is() )
			{
				sal_Bool bOk = sal_False;

				if ( MimeConfigurationHelper::ClassIDsEqual( aPackageClassID, aStorClassID ) )
				{
					// the storage represents Object Package
	
					bOk = ReadContentsAndGenerateTempFile( xSubStream->getInputStream(), sal_True );

					if ( !bOk && m_aNativeTempURL.getLength() )
					{
						KillFile_Impl( m_aNativeTempURL, m_xFactory );
						m_aNativeTempURL = ::rtl::OUString();
					}
				}

				if ( !bOk )
				{
					bOk = ReadContentsAndGenerateTempFile( xSubStream->getInputStream(), sal_False );

					if ( !bOk && m_aNativeTempURL.getLength() )
					{
						KillFile_Impl( m_aNativeTempURL, m_xFactory );
						m_aNativeTempURL = ::rtl::OUString();
					}
				}
			}
		}
		else
		{
			// TODO/LATER: No native stream, needs a new solution
		}
	}
	catch( uno::Exception& )
	{}
}

//--------------------------------------------------------
sal_Bool OwnView_Impl::Open()
{
	sal_Bool bResult = sal_False;

	uno::Reference< frame::XModel > xExistingModel;

	{
		::osl::MutexGuard aGuard( m_aMutex );
		xExistingModel = m_xModel;
		if ( m_bBusy )
			return sal_False;

		m_bBusy = sal_True;
	}

	if ( xExistingModel.is() )
	{
		try {
			uno::Reference< frame::XController > xController = xExistingModel->getCurrentController();
			if ( xController.is() )
			{
				uno::Reference< frame::XFrame > xFrame = xController->getFrame();
				if ( xFrame.is() )
				{
					xFrame->activate();
					uno::Reference<awt::XTopWindow> xTopWindow( xFrame->getContainerWindow(), uno::UNO_QUERY );
					if(xTopWindow.is())
						xTopWindow->toFront();

					bResult = sal_True;
				}
			}
		}
		catch( uno::Exception& )
		{
		}
	}
	else
	{
		bResult = CreateModel( m_bUseNative );

		if ( !bResult && !m_bUseNative )
		{
			// the original storage can not be recognized
			if ( !m_aNativeTempURL.getLength() )
			{
				// create a temporary file for the native representation if there is no
				CreateNative();
			}

			if ( m_aNativeTempURL.getLength() )
			{
				bResult = CreateModel( sal_True );
				if ( bResult )
					m_bUseNative = sal_True;
			}
		}
	}

	m_bBusy = sal_False;

	return bResult;
}

//--------------------------------------------------------
void OwnView_Impl::Close()
{
	uno::Reference< frame::XModel > xModel;

	{
		::osl::MutexGuard aGuard( m_aMutex );
		if ( !m_xModel.is() )
			return;
		xModel = m_xModel;
		m_xModel = uno::Reference< frame::XModel >();

		if ( m_bBusy )
			return;

		m_bBusy = sal_True;
	}

	try {
		uno::Reference< document::XEventBroadcaster > xBroadCaster( xModel, uno::UNO_QUERY );
		if ( xBroadCaster.is() )
			xBroadCaster->removeEventListener( uno::Reference< document::XEventListener >(
																	static_cast< ::cppu::OWeakObject* >( this ),
											 						uno::UNO_QUERY ) );

		uno::Reference< util::XCloseable > xCloseable( xModel, uno::UNO_QUERY );
		if ( xCloseable.is() )
		{
			xCloseable->removeCloseListener( uno::Reference< util::XCloseListener >(
																	static_cast< ::cppu::OWeakObject* >( this ),
											 						uno::UNO_QUERY ) );
			xCloseable->close( sal_True );
		}
	}
	catch( uno::Exception& )
	{}

	m_bBusy = sal_False;
}

//--------------------------------------------------------
void SAL_CALL OwnView_Impl::notifyEvent( const document::EventObject& aEvent )
		throw ( uno::RuntimeException )
{

	uno::Reference< frame::XModel > xModel;

	{
		::osl::MutexGuard aGuard( m_aMutex );
		if ( aEvent.Source == m_xModel && aEvent.EventName.equalsAscii( "OnSaveAsDone" ) )
		{
			// SaveAs operation took place, so just forget the model and deregister listeners
			xModel = m_xModel;
			m_xModel = uno::Reference< frame::XModel >();
		}
	}

	if ( xModel.is() )
	{
		try {
			uno::Reference< document::XEventBroadcaster > xBroadCaster( xModel, uno::UNO_QUERY );
			if ( xBroadCaster.is() )
				xBroadCaster->removeEventListener( uno::Reference< document::XEventListener >(
																		static_cast< ::cppu::OWeakObject* >( this ),
											 							uno::UNO_QUERY ) );
	
			uno::Reference< util::XCloseable > xCloseable( xModel, uno::UNO_QUERY );
			if ( xCloseable.is() )
				xCloseable->removeCloseListener( uno::Reference< util::XCloseListener >(
																		static_cast< ::cppu::OWeakObject* >( this ),
											 							uno::UNO_QUERY ) );
		}
		catch( uno::Exception& )
		{}
	}
}

//--------------------------------------------------------
void SAL_CALL OwnView_Impl::queryClosing( const lang::EventObject&, sal_Bool )
		throw ( util::CloseVetoException,
				uno::RuntimeException )
{
}

//--------------------------------------------------------
void SAL_CALL OwnView_Impl::notifyClosing( const lang::EventObject& Source )
		throw ( uno::RuntimeException )
{
	::osl::MutexGuard aGuard( m_aMutex );
	if ( Source.Source == m_xModel )
		m_xModel = uno::Reference< frame::XModel >();
}

//--------------------------------------------------------
void SAL_CALL OwnView_Impl::disposing( const lang::EventObject& Source )
		throw (uno::RuntimeException)
{
	::osl::MutexGuard aGuard( m_aMutex );
	if ( Source.Source == m_xModel )
		m_xModel = uno::Reference< frame::XModel >();
};

