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

//#include <tools/presys.h>
#include <vector>
//#include <tools/postsys.h>
#include <smart/com/sun/star/test/XSimpleTest.hxx>
#include <smart/com/sun/star/lang/XMultiServiceFactory.hxx>  // for the multiservice-factories

#include <stdio.h>

#include <smart/com/sun/star/io/XActiveDataSource.hxx>
#include <smart/com/sun/star/io/XOutputStream.hxx>
#include <smart/com/sun/star/xml/sax/SAXParseException.hxx>
#include <smart/com/sun/star/xml/sax/XParser.hxx>
#include <smart/com/sun/star/xml/sax/XExtendedDocumentHandler.hxx>

#include <rtl/wstring.hxx>
#include <osl/time.h>
#include <usr/weak.hxx>
#include <tools/string.hxx>

#include <usr/factoryhlp.hxx>

#include <usr/reflserv.hxx>  // for EXTERN_SERVICE_CALLTYPE

using namespace std;
using namespace rtl;
using namespace vos;
using namespace usr;

#define BUILD_ERROR(expr, Message)\
		{\
			m_seqErrors.realloc( m_seqErrors.getLen() + 1 ); \
		m_seqExceptions.realloc(  m_seqExceptions.getLen() + 1 ); \
		String str; \
		str += __FILE__;\
		str += " "; \
		str += "(" ; \
		str += __LINE__ ;\
		str += ")\n";\
		str += "[ " ; \
		str += #expr; \
		str += " ] : " ; \
		str += Message; \
		m_seqErrors.getArray()[ m_seqErrors.getLen()-1] = StringToUString( str , CHARSET_SYSTEM ); \
		}\
		((void)0)


#define WARNING_ASSERT(expr, Message) \
		if( ! (expr) ) { \
			m_seqWarnings.realloc( m_seqErrors.getLen() +1 ); \
			String str;\
			str += __FILE__;\
			str += " "; \
			str += "(" ; \
			str += __LINE__ ;\
			str += ")\n";\
			str += "[ " ; \
			str += #expr; \
			str += " ] : " ; \
			str += Message; \
			m_seqWarnings.getArray()[ m_seqWarnings.getLen()-1] = StringToUString( str , CHARSET_SYSTEM ); \
			return; \
		}\
		((void)0)

#define ERROR_ASSERT(expr, Message) \
		if( ! (expr) ) { \
			BUILD_ERROR(expr, Message );\
			return; \
		}\
		((void)0)

#define ERROR_EXCEPTION_ASSERT(expr, Message, Exception) \
	if( !(expr)) { \
		BUILD_ERROR(expr,Message);\
		m_seqExceptions.getArray()[ m_seqExceptions.getLen()-1] = UsrAny( Exception );\
		return; \
	} \
	((void)0)

/****
* test szenarios :
*
*
*
****/


class OFileWriter :
		public XOutputStream,
		public OWeakObject
{
public:
	OFileWriter( char *pcFile ) { strcpy( m_pcFile , pcFile ); m_f = 0; }


public: // refcounting
	BOOL						queryInterface( Uik aUik, XInterfaceRef & rOut )
	{
		if( XOutputStream::getSmartUik() == aUik ) {
			rOut = (XOutputStream *) this;
		}
		else return OWeakObject::queryInterface( aUik , rOut );

		return TRUE;
	}
	void 						acquire() 						 { OWeakObject::acquire(); }
	void 						release() 						 { OWeakObject::release(); }
	void* 						getImplementation(Reflection *p) { return OWeakObject::getImplementation(p); }

public:
    virtual void writeBytes(const Sequence< BYTE >& aData)
    					THROWS( (NotConnectedException, BufferSizeExceededException, UsrSystemException) );
    virtual void flush(void)
    					THROWS( (NotConnectedException, BufferSizeExceededException, UsrSystemException) );
    virtual void closeOutput(void)
    					THROWS( (NotConnectedException, BufferSizeExceededException, UsrSystemException) );


private:
	char m_pcFile[256];
	FILE *m_f;
};


void OFileWriter::writeBytes(const Sequence< BYTE >& aData)
			THROWS( (NotConnectedException, BufferSizeExceededException, UsrSystemException) )
{
	if( ! m_f ) {
		m_f = fopen( m_pcFile , "w" );
	}

	fwrite( aData.getConstArray() , 1 , aData.getLen() , m_f );

}


void OFileWriter::flush(void)
			THROWS( (NotConnectedException, BufferSizeExceededException, UsrSystemException) )
{
	fflush( m_f );
}

void OFileWriter::closeOutput(void)
    					THROWS( (NotConnectedException, BufferSizeExceededException, UsrSystemException) )
{
	fclose( m_f );
	m_f = 0;
}


class OSaxWriterTest :
		public XSimpleTest,
        public OWeakObject
{
public:
	OSaxWriterTest( const XMultiServiceFactoryRef & rFactory ) : m_rFactory( rFactory )
	{

	}
	~OSaxWriterTest() {}

public: // refcounting
	BOOL						queryInterface( Uik aUik, XInterfaceRef & rOut );
	void 						acquire() 						 { OWeakObject::acquire(); }
	void 						release() 						 { OWeakObject::release(); }
	void* 						getImplementation(Reflection *p) { return OWeakObject::getImplementation(p); }

public:
    virtual void testInvariant(const UString& TestName, const XInterfaceRef& TestObject)
    															THROWS( (	IllegalArgumentException,
    																		UsrSystemException) );

    virtual INT32 test(	const UString& TestName,
    					const XInterfaceRef& TestObject,
    					INT32 hTestHandle) 						THROWS( (	IllegalArgumentException,
    																		UsrSystemException) );

    virtual BOOL testPassed(void) 								THROWS( (	UsrSystemException) );
    virtual Sequence< UString > getErrors(void) 				THROWS( (UsrSystemException) );
    virtual Sequence< UsrAny > getErrorExceptions(void) 		THROWS( (UsrSystemException) );
    virtual Sequence< UString > getWarnings(void) 				THROWS( (UsrSystemException) );

private:
	void testSimple( const XExtendedDocumentHandlerRef &r );
	void testExceptions( const XExtendedDocumentHandlerRef &r );
	void testDTD( const XExtendedDocumentHandlerRef &r );
	void testPerformance( const XExtendedDocumentHandlerRef &r );
	void writeParagraph( const XExtendedDocumentHandlerRef &r , const UString & s);

private:
	Sequence<UsrAny>  		m_seqExceptions;
	Sequence<UString> 		m_seqErrors;
	Sequence<UString> 		m_seqWarnings;
	XMultiServiceFactoryRef m_rFactory;

};



/*----------------------------------------
*
* 	Attributlist implementation
*
*----------------------------------------*/
struct AttributeListImpl_impl;
class AttributeListImpl :
		public XAttributeList,
		public OWeakObject
{
public:
	AttributeListImpl();
	AttributeListImpl( const AttributeListImpl & );
	~AttributeListImpl();

public:
	BOOL					queryInterface( Uik aUik, XInterfaceRef & rOut );
	void 					acquire() 						 { OWeakObject::acquire(); }
	void 					release() 						 { OWeakObject::release(); }
	void* 					getImplementation(Reflection *p) { return OWeakObject::getImplementation(p); }

public:
    virtual INT16 getLength(void) THROWS( (UsrSystemException) );
    virtual UString getNameByIndex(INT16 i) THROWS( (UsrSystemException) );
    virtual UString getTypeByIndex(INT16 i) THROWS( (UsrSystemException) );
    virtual UString getTypeByName(const UString& aName) THROWS( (UsrSystemException) );
    virtual UString getValueByIndex(INT16 i) THROWS( (UsrSystemException) );
    virtual UString getValueByName(const UString& aName) THROWS( (UsrSystemException) );

public:
	void addAttribute( const UString &sName , const UString &sType , const UString &sValue );
	void clear();

private:
	struct AttributeListImpl_impl *m_pImpl;
};


struct TagAttribute
{
	TagAttribute(){}
	TagAttribute( const UString &sName, const UString &sType , const UString &sValue )
	{
		this->sName 	= sName;
		this->sType 	= sType;
		this->sValue 	= sValue;
	}

	UString sName;
	UString sType;
	UString sValue;
};

struct AttributeListImpl_impl
{
	AttributeListImpl_impl()
	{
		// performance improvement during adding
		vecAttribute.reserve(20);
	}
	vector<struct TagAttribute> vecAttribute;
};



INT16 AttributeListImpl::getLength(void) THROWS( (UsrSystemException) )
{
	return m_pImpl->vecAttribute.size();
}


AttributeListImpl::AttributeListImpl( const AttributeListImpl &r )
{
	m_pImpl = new AttributeListImpl_impl;
	*m_pImpl = *(r.m_pImpl);
}

UString AttributeListImpl::getNameByIndex(INT16 i) THROWS( (UsrSystemException) )
{
	if( i < m_pImpl->vecAttribute.size() ) {
		return m_pImpl->vecAttribute[i].sName;
	}
	return UString();
}


UString AttributeListImpl::getTypeByIndex(INT16 i) THROWS( (UsrSystemException) )
{
	if( i < m_pImpl->vecAttribute.size() ) {
		return m_pImpl->vecAttribute[i].sType;
	}
	return UString();
}

UString AttributeListImpl::getValueByIndex(INT16 i) THROWS( (UsrSystemException) )
{
	if( i < m_pImpl->vecAttribute.size() ) {
		return m_pImpl->vecAttribute[i].sValue;
	}
	return UString();

}

UString AttributeListImpl::getTypeByName( const UString& sName ) THROWS( (UsrSystemException) )
{
	vector<struct TagAttribute>::iterator ii = m_pImpl->vecAttribute.begin();

	for( ; ii != m_pImpl->vecAttribute.end() ; ii ++ ) {
		if( (*ii).sName == sName ) {
			return (*ii).sType;
		}
	}
	return UString();
}

UString AttributeListImpl::getValueByName(const UString& sName) THROWS( (UsrSystemException) )
{
	vector<struct TagAttribute>::iterator ii = m_pImpl->vecAttribute.begin();

	for( ; ii != m_pImpl->vecAttribute.end() ; ii ++ ) {
		if( (*ii).sName == sName ) {
			return (*ii).sValue;
		}
	}
	return UString();
}


BOOL AttributeListImpl::queryInterface( Uik aUik, XInterfaceRef & rOut )
{
	if( aUik == XAttributeList::getSmartUik() ) {
		rOut = (XAttributeList * )this;
	}
	else {
		return OWeakObject::queryInterface( aUik , rOut );
	}
	return TRUE;
}


AttributeListImpl::AttributeListImpl()
{
	m_pImpl = new AttributeListImpl_impl;
}



AttributeListImpl::~AttributeListImpl()
{
	delete m_pImpl;
}


void AttributeListImpl::addAttribute( 	const UString &sName ,
										const UString &sType ,
										const UString &sValue )
{
	m_pImpl->vecAttribute.push_back( TagAttribute( sName , sType , sValue ) );
}

void AttributeListImpl::clear()
{
	vector<struct TagAttribute> dummy;
	m_pImpl->vecAttribute.swap( dummy );

	OSL_ASSERT( ! getLength() );
}











/**
* for external binding
*
*
**/
XInterfaceRef OSaxWriterTest_CreateInstance( const XMultiServiceFactoryRef & rSMgr ) THROWS((Exception))
{
	OSaxWriterTest *p = new OSaxWriterTest( rSMgr );
	XInterfaceRef xService = *p;
	return xService;
}

UString     OSaxWriterTest_getServiceName( ) THROWS( () )
{
	return L"test.com.sun.star.xml.sax.Writer";
}

UString 	OSaxWriterTest_getImplementationName( ) THROWS( () )
{
	return L"test.extensions.xml.sax.Writer";
}

Sequence<UString> OSaxWriterTest_getSupportedServiceNames( ) THROWS( () )
{
	Sequence<UString> aRet(1);

	aRet.getArray()[0] = OSaxWriterTest_getImplementationName( );

	return aRet;
}


BOOL OSaxWriterTest::queryInterface( Uik uik , XInterfaceRef &rOut )
{
	if( XSimpleTest::getSmartUik() == uik ) {
		rOut = (XSimpleTest *) this;
	}
	else {
		return OWeakObject::queryInterface( uik , rOut );
	}
	return TRUE;
}


void OSaxWriterTest::testInvariant( const UString& TestName, const XInterfaceRef& TestObject )
    															THROWS( (	IllegalArgumentException,
    																		UsrSystemException) )
{
	if( L"com.sun.star.xml.sax.Writer" == TestName ) {
		XDocumentHandlerRef doc( TestObject , USR_QUERY );
		XExtendedDocumentHandlerRef ext( TestObject , USR_QUERY );
		XActiveDataSourceRef source( TestObject , USR_QUERY );

		ERROR_ASSERT( doc.is() , "XDocumentHandler cannot be queried" );
		ERROR_ASSERT( ext.is() , "XExtendedDocumentHandler cannot be queried" );
		ERROR_ASSERT( source.is() , "XActiveDataSource cannot be queried" );
	}
	else {
		BUILD_ERROR( 0 , "wrong test" );
	}
}


INT32 OSaxWriterTest::test(	const UString& TestName,
    					const XInterfaceRef& TestObject,
    					INT32 hTestHandle) 						THROWS( (	IllegalArgumentException,
    																		UsrSystemException) )
{
	if( L"com.sun.star.xml.sax.Writer" == TestName )  {
		try {
			if( 0 == hTestHandle ) {
				testInvariant( TestName , TestObject );
			}
			else {

				XExtendedDocumentHandlerRef writer( TestObject , USR_QUERY );

				if( 1 == hTestHandle ) {
					testSimple( writer );
				}
				else if( 2 == hTestHandle ) {
					testExceptions( writer );
				}
				else if( 3 == hTestHandle ) {
					testDTD( writer );
				}
				else if( 4 == hTestHandle ) {
					testPerformance( writer );
				}
			}
		}
		catch( Exception& e )  {
			BUILD_ERROR( 0 , UStringToString( e.getName() , CHARSET_SYSTEM ).GetCharStr() );
		}
		catch(...) {
			BUILD_ERROR( 0 , "unknown exception (Exception is  not base class)" );
		}

		hTestHandle ++;

		if( hTestHandle >= 5) {
			// all tests finished.
			hTestHandle = -1;
		}
	}
	else {
		BUILD_ERROR( 0 , "service not supported by test." );
	}
	return hTestHandle;
}



BOOL OSaxWriterTest::testPassed(void) 										THROWS( (UsrSystemException) )
{
	return m_seqErrors.getLen() == 0;
}


Sequence< UString > OSaxWriterTest::getErrors(void) 							THROWS( (UsrSystemException) )
{
	return m_seqErrors;
}


Sequence< UsrAny > OSaxWriterTest::getErrorExceptions(void) 					THROWS( (UsrSystemException) )
{
	return m_seqExceptions;
}


Sequence< UString > OSaxWriterTest::getWarnings(void) 						THROWS( (UsrSystemException) )
{
	return m_seqWarnings;
}

void OSaxWriterTest::writeParagraph( const XExtendedDocumentHandlerRef &r , const UString & s)
{
	int nMax = s.len();
	int nStart = 0;

	Sequence<UINT16> seq( s.len() );
	memcpy( seq.getArray() , s.getStr() , s.len() * sizeof( UINT16 ) );

	for( int n = 1 ; n < nMax ; n++ ){
		if( 32 == seq.getArray()[n] ) {
			r->allowLineBreak();
			r->characters( s.copy( nStart , n - nStart ) );
			nStart = n;
		}
	}
	r->allowLineBreak();
	r->characters( s.copy( nStart , n - nStart ) );


}



void OSaxWriterTest::testSimple( const XExtendedDocumentHandlerRef &r )
{
	UString testParagraph = L"Dies ist ein bloeder Test um zu uberpruefen, ob der SAXWriter "
	                        L"wohl Zeilenumbrueche halbwegs richtig macht oder ob er die Zeile "
	                        L"bis zum bitteren Ende schreibt.";

	OFileWriter *pw = new OFileWriter("output.xml");
	AttributeListImpl *pList = new AttributeListImpl;

	XAttributeListRef rList( (XAttributeList *) pList , USR_QUERY );
	XOutputStreamRef ref( ( XOutputStream * ) pw , USR_QUERY );

	XActiveDataSourceRef source( r , USR_QUERY );

	ERROR_ASSERT( ref.is() , "no output stream" );
	ERROR_ASSERT( source.is() , "no active data source" );

	source->setOutputStream( ref );

	r->startDocument();

	pList->addAttribute( L"Arg1" , L"CDATA" , L"bla\n	u" );
	pList->addAttribute( L"Arg2" , L"CDATA" , L"blub" );

	r->startElement( L"tag1"  , rList );
	r->ignorableWhitespace( L"" );

	r->characters( L"huhu" );
	r->ignorableWhitespace( L"" );

	r->startElement( L"hi" , rList );
	r->ignorableWhitespace( L"" );

	// the enpassant must be converted & -> &amp;
	r->characters( L"&#252;" );

	// Test added for mib. Tests if errors during conversions occurs
	r->ignorableWhitespace( UString() );
	sal_Char array[256];
	for( sal_Int32 n = 32 ; n < 254 ; n ++ ) {
		array[n-32] = n;
	}
	array[254-32] = 0;
	r->characters(
		StringToUString( array , RTL_TEXTENCODING_SYMBOL )
		);
	r->ignorableWhitespace( UString() );

	// '>' must not be converted
	r->startCDATA();
	r->characters( L">fsfsdf<" );
	r->endCDATA();
	r->ignorableWhitespace( UString() );

	writeParagraph( r , testParagraph );


	r->ignorableWhitespace( UString() );
	r->comment( L"Dies ist ein Kommentar !" );
	r->ignorableWhitespace( UString() );

	r->startElement( L"emptytagtest"  , rList );
	r->endElement( L"emptytagtest" );

	r->endElement( L"hi" );
	r->ignorableWhitespace( L"" );

	r->endElement( L"tag1" );
	r->endDocument();

}

void OSaxWriterTest::testExceptions( const XExtendedDocumentHandlerRef & r )
{

	OFileWriter *pw = new OFileWriter("output2.xml");
	AttributeListImpl *pList = new AttributeListImpl;

	XAttributeListRef rList( (XAttributeList *) pList , USR_QUERY );
	XOutputStreamRef ref( ( XOutputStream * ) pw , USR_QUERY );

	XActiveDataSourceRef source( r , USR_QUERY );

	ERROR_ASSERT( ref.is() , "no output stream" );
	ERROR_ASSERT( source.is() , "no active data source" );

	source->setOutputStream( ref );

	{ // startDocument must be called before start element
		BOOL bException = TRUE;
		try {
			r->startElement( L"huhu" , rList );
			bException = FALSE;
		}
		catch( SAXException& e ) {

		}
		ERROR_ASSERT( bException , "expected exception not thrown !" );
	}

	r->startDocument();
	
	r->startElement( L"huhu" , rList );
	r->startCDATA();

	{
		BOOL bException = TRUE;
		try { 
			r->startElement( L"huhu" , rList );
			bException = FALSE;
		}
		catch( SAXException& e ) {

		}
		ERROR_ASSERT( bException , "expected exception not thrown !" );
	}
	
	r->endCDATA();
	r->endElement( L"hi" );

	r->endDocument();
}


void OSaxWriterTest::testDTD(const  XExtendedDocumentHandlerRef &r )
{
	OFileWriter *pw = new OFileWriter("outputDTD.xml");
	AttributeListImpl *pList = new AttributeListImpl;
	
	XAttributeListRef rList( (XAttributeList *) pList , USR_QUERY );
	XOutputStreamRef ref( ( XOutputStream * ) pw , USR_QUERY );
	
	XActiveDataSourceRef source( r , USR_QUERY );
	
	ERROR_ASSERT( ref.is() , "no output stream" );
	ERROR_ASSERT( source.is() , "no active data source" );
	
	source->setOutputStream( ref );

	
	r->startDocument();
	r->unknown( L"<!DOCTYPE iCalendar >\n" );
	r->startElement( L"huhu" , rList );
	
	r->endElement( L"huhu" );
	r->endDocument();
}

void OSaxWriterTest::testPerformance(const  XExtendedDocumentHandlerRef &r )
{
	OFileWriter *pw = new OFileWriter("testPerformance.xml");
	AttributeListImpl *pList = new AttributeListImpl;

	UString testParagraph = L"Dies ist ein bloeder Test um zu uberpruefen, ob der SAXWriter "
	                        L"wohl > Zeilenumbrueche halbwegs richtig macht oder ob er die Zeile "
	                        L"bis zum bitteren Ende schreibt.";

	
	XAttributeListRef rList( (XAttributeList *) pList , USR_QUERY );
	XOutputStreamRef ref( ( XOutputStream * ) pw , USR_QUERY );
	
	XActiveDataSourceRef source( r , USR_QUERY );
	
	ERROR_ASSERT( ref.is() , "no output stream" );
	ERROR_ASSERT( source.is() , "no active data source" );
	
	source->setOutputStream( ref );

	TimeValue aStartTime, aEndTime;
	osl_getSystemTime( &aStartTime );


	r->startDocument();
	// just write a bunch of xml tags !
	// for performance testing
	sal_Int32 i2;
	for( i2 = 0 ; i2 < 15 ; i2 ++ )
	{
		r->startElement( UString( L"tag" ) + UString::valueOf( i2 ), rList );
		for( sal_Int32 i = 0 ; i < 450 ; i ++ )
		{
			r->ignorableWhitespace( L"");
			r->startElement( L"huhu" , rList );
			r->characters( testParagraph );
//			writeParagraph( r , testParagraph );
			
			r->ignorableWhitespace( L"");
			r->endElement( L"huhu" );
		}
	}
	for( i2 = 14 ; i2 >= 0  ; i2-- )
	{
		r->ignorableWhitespace( L"");
		r->endElement( UString( L"tag" ) + UString::valueOf( i2 ) );
	}
	
	r->endDocument();

	osl_getSystemTime( &aEndTime );

	double fStart = (double)aStartTime.Seconds + ((double)aStartTime.Nanosec / 1000000000.0);
	double fEnd = (double)aEndTime.Seconds + ((double)aEndTime.Nanosec / 1000000000.0);

	printf( "Performance writing : %g s\n" , fEnd - fStart );
}
