/**************************************************************
 * 
 * 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_automation.hxx"
#include <stdio.h>
#if OSL_DEBUG_LEVEL > 1
#define DEBUGPRINTF(x) { printf(x); fflush( stdout ); }
#else
#define DEBUGPRINTF(x)
#endif
#include <tools/debug.hxx>
#include <vcl/svapp.hxx>
#include <vos/socket.hxx>
#include <tools/stream.hxx>
#include <vcl/timer.hxx>
#include <tools/fsys.hxx>

#include <automation/communi.hxx>


/*	Um den Destruktor protected zu machen wurde unten das delete entfernt.
	Die Methode wird ohnehin hucht benutzt.
//				delete *((AE*)pData+n);
*/

#undef  SV_IMPL_PTRARR_SORT
#define SV_IMPL_PTRARR_SORT( nm,AE )\
_SV_IMPL_SORTAR_ALG( nm,AE )\
	void nm::DeleteAndDestroy( sal_uInt16 nP, sal_uInt16 nL ) { \
		if( nL ) {\
			DBG_ASSERT( nP < nA && nP + nL <= nA, "ERR_VAR_DEL" );\
			for( sal_uInt16 n=nP; n < nP + nL; n++ ) \
				DBG_ERROR("Das Element der Liste wurde nicht gelöscht"); \
			SvPtrarr::Remove( nP, nL ); \
		} \
	} \
_SV_SEEK_PTR( nm, AE )




SV_IMPL_PTRARR_SORT( CommunicationLinkList, CommunicationLink* );

vos::OMutex *pMPostUserEvent=NULL;		// Notwendig, da nicht threadfest

CommunicationLinkViaSocket::CommunicationLinkViaSocket( CommunicationManager *pMan, vos::OStreamSocket *pSocket )
: SimpleCommunicationLinkViaSocket( pMan, pSocket )
, nConnectionClosedEventId( 0 )
, nDataReceivedEventId( 0 )
, bShutdownStarted( sal_False )
, bDestroying( sal_False )
{
    SetPutDataReceivedHdl(LINK( this, CommunicationLinkViaSocket, PutDataReceivedHdl ));
    if ( !pMPostUserEvent )
        pMPostUserEvent = new vos::OMutex;
    // this is necassary to prevent the running thread from sending the close event
    // before the open event has been sent.
   	StartCallback();

	create();
}

CommunicationLinkViaSocket::~CommunicationLinkViaSocket()
{
    bDestroying = sal_True;
	StopCommunication();
	while ( nConnectionClosedEventId || nDataReceivedEventId )
		GetpApp()->Yield();
	{
		vos::OGuard aGuard( aMConnectionClosed );
		if ( nConnectionClosedEventId )
		{
			GetpApp()->RemoveUserEvent( nConnectionClosedEventId );
			nConnectionClosedEventId = 0;
			INFO_MSG( CByteString("Event gelöscht"),
				CByteString( "ConnectionClosedEvent aus Queue gelöscht"),
				CM_MISC, NULL );
		}
	}
	{
		vos::OGuard aGuard( aMDataReceived );
		if ( nDataReceivedEventId )
		{
			GetpApp()->RemoveUserEvent( nDataReceivedEventId );
			nDataReceivedEventId = 0;
			delete GetServiceData();
			INFO_MSG( CByteString("Event gelöscht"),
				CByteString( "DataReceivedEvent aus Queue gelöscht"),
				CM_MISC, NULL );
		}
	}
}

sal_Bool CommunicationLinkViaSocket::ShutdownCommunication()
{
	if ( isRunning() )
	{

		terminate();
		if ( GetStreamSocket() )
			GetStreamSocket()->shutdown();

		if ( GetStreamSocket() )	// Mal wieder nach oben verschoben, da sonst nicht vom Read runtergesprungen wird.
			GetStreamSocket()->close();

		resume();	// So daß das run auch die Schleife verlassen kann

		join();

        vos::OStreamSocket *pTempSocket = GetStreamSocket();
        SetStreamSocket( NULL );
        delete pTempSocket;

//		ConnectionClosed();		Wird am Ende des Thread gerufen

	}
	else
	{
		join();
	}

	return sal_True;
}

sal_Bool CommunicationLinkViaSocket::StopCommunication()
{
    if ( !bShutdownStarted )
    {
        return SimpleCommunicationLinkViaSocket::StopCommunication();
    }
    else
    {
        WaitForShutdown();
        return sal_True;
    }
}


IMPL_LINK( CommunicationLinkViaSocket, ShutdownLink, void*, EMPTYARG )
{
	if ( !IsCommunicationError() )
    	ShutdownCommunication();
    return 0;
}


void CommunicationLinkViaSocket::WaitForShutdown()
{
    if ( !bShutdownStarted )
    {
	    aShutdownTimer.SetTimeout( 30000 );		// Should be 30 Seconds
	    aShutdownTimer.SetTimeoutHdl( LINK( this, CommunicationLinkViaSocket, ShutdownLink ) );
	    aShutdownTimer.Start();
        bShutdownStarted = sal_True;
    }
    if ( bDestroying )
    {
	    while ( pMyManager && aShutdownTimer.IsActive() )
	    {
		    if ( IsCommunicationError() )
			    return;
		    GetpApp()->Yield();
	    }
	    ShutdownCommunication();
    }
}

sal_Bool CommunicationLinkViaSocket::IsCommunicationError()
{
	return !isRunning() || SimpleCommunicationLinkViaSocket::IsCommunicationError();
}

void CommunicationLinkViaSocket::run()
{
	sal_Bool bWasError = sal_False;
    while ( schedule() && !bWasError && GetStreamSocket() )
	{
		if ( bWasError |= !DoReceiveDataStream() )
			continue;

		TimeValue sNochEins = {0, 1000000};
		while ( schedule() && bIsInsideCallback )	// solange der letzte Callback nicht beendet ist
			sleep( sNochEins );
		SetNewPacketAsCurrent();
		StartCallback();
		{
			vos::OGuard aGuard( aMDataReceived );
            vos::OGuard aGuard2( *pMPostUserEvent );
            mlPutDataReceived.Call(this);
		}
	}
	TimeValue sNochEins = {0, 1000000};
	while ( schedule() && bIsInsideCallback )	// solange der letzte Callback nicht beendet ist
		sleep( sNochEins );

    StartCallback();
	{
		vos::OGuard aGuard( aMConnectionClosed );
        vos::OGuard aGuard2( *pMPostUserEvent );
		nConnectionClosedEventId = GetpApp()->PostUserEvent( LINK( this, CommunicationLinkViaSocket, ConnectionClosed ) );
	}
}

sal_Bool CommunicationLinkViaSocket::DoTransferDataStream( SvStream *pDataStream, CMProtocol nProtocol )
{
	if ( !isRunning() )
		return sal_False;

	return SimpleCommunicationLinkViaSocket::DoTransferDataStream( pDataStream, nProtocol );
}

/// Dies ist ein virtueller Link!!!
long CommunicationLinkViaSocket::ConnectionClosed( void* EMPTYARG )
{
	{
		vos::OGuard aGuard( aMConnectionClosed );
		nConnectionClosedEventId = 0;	// Achtung!! alles andere muß oben gemacht werden.
	}
	ShutdownCommunication();
	return CommunicationLink::ConnectionClosed( );
}

/// Dies ist ein virtueller Link!!!
long CommunicationLinkViaSocket::DataReceived( void* EMPTYARG )
{
	{
		vos::OGuard aGuard( aMDataReceived );
		nDataReceivedEventId = 0;	// Achtung!! alles andere muß oben gemacht werden.
	}
	return CommunicationLink::DataReceived( );
}

IMPL_LINK( CommunicationLinkViaSocket, PutDataReceivedHdl, CommunicationLinkViaSocket*, EMPTYARG )
{
    nDataReceivedEventId = GetpApp()->PostUserEvent( LINK( this, CommunicationLink, DataReceived ) );
    return 0;
}



MultiCommunicationManager::MultiCommunicationManager( sal_Bool bUseMultiChannel )
: CommunicationManager( bUseMultiChannel )
, bGracefullShutdown( sal_True )
{
	ActiveLinks = new CommunicationLinkList;
	InactiveLinks = new CommunicationLinkList;
}

MultiCommunicationManager::~MultiCommunicationManager()
{
	StopCommunication();

    if ( bGracefullShutdown )   // first try to collect all callbacks for closing channels
    {
        Timer aTimeout;
        aTimeout.SetTimeout( 40000 );
        aTimeout.Start();
        sal_uInt16 nLinkCount = 0;
        sal_uInt16 nNewLinkCount = 0;
        while ( aTimeout.IsActive() )
        {
            GetpApp()->Yield();
            nNewLinkCount = GetCommunicationLinkCount();
            if ( nNewLinkCount == 0 )
                aTimeout.Stop();
            if ( nNewLinkCount != nLinkCount )
            {
                aTimeout.Start();
                nLinkCount = nNewLinkCount;
            }
        }
    }

	// Alles weghauen, was nicht rechtzeitig auf die Bäume gekommen ist
	// Was bei StopCommunication übrig geblieben ist, da es sich asynchron austragen wollte
	sal_uInt16 i = ActiveLinks->Count();
	while ( i-- )
	{
		CommunicationLinkRef rTempLink = ActiveLinks->GetObject( i );
		ActiveLinks->Remove( i );
		rTempLink->InvalidateManager();
		rTempLink->ReleaseReference();
	}
	delete ActiveLinks;

	/// Die Links zwischen ConnectionClosed und Destruktor.
	/// Hier NICHT gerefcounted, da sie sich sonst im Kreis festhaten würden,
	/// da die Links sich erst in ihrem Destruktor austragen
	i = InactiveLinks->Count();
	while ( i-- )
	{
		CommunicationLinkRef rTempLink = InactiveLinks->GetObject( i );
		InactiveLinks->Remove( i );
		rTempLink->InvalidateManager();
	}
	delete InactiveLinks;
}

sal_Bool MultiCommunicationManager::StopCommunication()
{
	// Alle Verbindungen abbrechen
	// ConnectionClosed entfernt die Links aus der Liste. Je nach Implementation syncron
	// oder asyncron. Daher Von oben nach unten Abräumen, so daß sich nichts verschiebt.
	sal_uInt16 i = ActiveLinks->Count();
	int nFail = 0;
	while ( i )
	{
		if ( !ActiveLinks->GetObject(i-1)->StopCommunication() )
            nFail++;    // Hochzählen, da Verbindung sich nicht (sofort) beenden lässt.
		i--;
	}

	return nFail == 0;
}

sal_Bool MultiCommunicationManager::IsLinkValid( CommunicationLink* pCL )
{
	if ( ActiveLinks->Seek_Entry( pCL ) )
		return sal_True;
	else
		return sal_False;
}

sal_uInt16 MultiCommunicationManager::GetCommunicationLinkCount()
{
	return ActiveLinks->Count();
}

CommunicationLinkRef MultiCommunicationManager::GetCommunicationLink( sal_uInt16 nNr )
{
	return ActiveLinks->GetObject( nNr );
}

void MultiCommunicationManager::CallConnectionOpened( CommunicationLink* pCL )
{
	CommunicationLinkRef rHold(pCL);	// Hält den Zeiger bis zum Ende des calls
	ActiveLinks->C40_PTR_INSERT(CommunicationLink, pCL);
	rHold->AddRef();

	CommunicationManager::CallConnectionOpened( pCL );
}

void MultiCommunicationManager::CallConnectionClosed( CommunicationLink* pCL )
{
	CommunicationLinkRef rHold(pCL);	// Hält denm Zeiger bis zum Ende des calls

	CommunicationManager::CallConnectionClosed( pCL );

	sal_uInt16 nPos;
	if ( ActiveLinks->Seek_Entry( pCL, &nPos ) )
	{
		InactiveLinks->C40_PTR_INSERT(CommunicationLink, pCL);	// Ohne Reference
		ActiveLinks->Remove( nPos );
	}
	pCL->ReleaseReference();

	bIsCommunicationRunning = ActiveLinks->Count() > 0;
//	delete pCL;
#if OSL_DEBUG_LEVEL > 1
        rHold->bFlag = sal_True;
#endif
}

void MultiCommunicationManager::DestroyingLink( CommunicationLink *pCL )
{
	sal_uInt16 nPos;
	if ( InactiveLinks->Seek_Entry( pCL, &nPos ) )
		InactiveLinks->Remove( nPos );
	pCL->InvalidateManager();
}



CommunicationManagerClient::CommunicationManagerClient( sal_Bool bUseMultiChannel )
: MultiCommunicationManager( bUseMultiChannel )
{
	ByteString aApplication("Something inside ");
	aApplication.Append( ByteString( DirEntry( Application::GetAppFileName() ).GetName(), gsl_getSystemTextEncoding() ) );
    SetApplication( aApplication );
}



CommunicationManagerServerViaSocket::CommunicationManagerServerViaSocket( sal_uLong nPort, sal_uInt16 nMaxCon, sal_Bool bUseMultiChannel )
: CommunicationManagerServer( bUseMultiChannel )
, nPortToListen( nPort )
, nMaxConnections( nMaxCon )
, pAcceptThread( NULL )
{
}

CommunicationManagerServerViaSocket::~CommunicationManagerServerViaSocket()
{
	StopCommunication();
}

sal_Bool CommunicationManagerServerViaSocket::StartCommunication()
{
	if ( !pAcceptThread )
		pAcceptThread = new CommunicationManagerServerAcceptThread( this, nPortToListen, nMaxConnections );
	return sal_True;
}


sal_Bool CommunicationManagerServerViaSocket::StopCommunication()
{
	// Erst den Acceptor anhalten
	delete pAcceptThread;
	pAcceptThread = NULL;

	// Dann alle Verbindungen kappen
	return CommunicationManagerServer::StopCommunication();
}


void CommunicationManagerServerViaSocket::AddConnection( CommunicationLink *pNewConnection )
{
	CallConnectionOpened( pNewConnection );
}


CommunicationManagerServerAcceptThread::CommunicationManagerServerAcceptThread( CommunicationManagerServerViaSocket* pServer, sal_uLong nPort, sal_uInt16 nMaxCon )
: pMyServer( pServer )
, pAcceptorSocket( NULL )
, nPortToListen( nPort )
, nMaxConnections( nMaxCon )
, nAddConnectionEventId( 0 )
, xmNewConnection( NULL )
{
    if ( !pMPostUserEvent )
        pMPostUserEvent = new vos::OMutex;
	create();
}


CommunicationManagerServerAcceptThread::~CommunicationManagerServerAcceptThread()
{
#ifndef aUNX		// Weil das Accept nicht abgebrochen werden kann, so terminiert wenigstens das Prog
	// #62855# pl: gilt auch bei anderen Unixen
	// die richtige Loesung waere natuerlich, etwas auf die pipe zu schreiben,
	// was der thread als Abbruchbedingung erkennt
	// oder wenigstens ein kill anstatt join
	terminate();
	if ( pAcceptorSocket )
		pAcceptorSocket->close();	// Dann das Accept unterbrechen

	join();		// Warten bis fertig

	if ( pAcceptorSocket )
	{
		delete pAcceptorSocket;
		pAcceptorSocket = NULL;
	}
#else
	DEBUGPRINTF ("Destructor CommunicationManagerServerAcceptThread Übersprungen!!!! (wegen Solaris BUG)\n");
#endif
	{
		vos::OGuard aGuard( aMAddConnection );
		if ( nAddConnectionEventId )
		{
			GetpApp()->RemoveUserEvent( nAddConnectionEventId );
			nAddConnectionEventId = 0;
			CommunicationLinkRef xNewConnection = GetNewConnection();
			INFO_MSG( CByteString("Event gelöscht"),
				CByteString( "AddConnectionEvent aus Queue gelöscht"),
				CM_MISC, xNewConnection );
			xNewConnection->InvalidateManager();
			xNewConnection.Clear();	// sollte das Objekt hier löschen
		}
	}
}

void CommunicationManagerServerAcceptThread::run()
{
	if ( !nPortToListen )
		return;

	pAcceptorSocket = new vos::OAcceptorSocket();
	vos::OInetSocketAddr Addr;
	Addr.setPort( nPortToListen );
	pAcceptorSocket->setReuseAddr( 1 );
	if ( !pAcceptorSocket->bind( Addr ) )
	{
		return;
	}
	if ( !pAcceptorSocket->listen( nMaxConnections ) )
	{
		return;
	}


	vos::OStreamSocket *pStreamSocket = NULL;

	while ( schedule() )
	{
		pStreamSocket = new vos::OStreamSocket;
		switch ( pAcceptorSocket->acceptConnection( *pStreamSocket ) )
		{
		case vos::ISocketTypes::TResult_Ok:
			{
				pStreamSocket->setTcpNoDelay( 1 );

				TimeValue sNochEins = {0, 100};
				while ( schedule() && xmNewConnection.Is() )	// Solange die letzte Connection nicht abgeholt wurde warten wir
					sleep( sNochEins );
				xmNewConnection = new CommunicationLinkViaSocket( pMyServer, pStreamSocket );
				xmNewConnection->StartCallback();
				{
					vos::OGuard aGuard( aMAddConnection );
                    vos::OGuard aGuard2( *pMPostUserEvent );
					nAddConnectionEventId = GetpApp()->PostUserEvent( LINK( this, CommunicationManagerServerAcceptThread, AddConnection ) );
				}
			}
			break;
		case vos::ISocketTypes::TResult_TimedOut:
			delete pStreamSocket;
			pStreamSocket = NULL;
			break;
		case vos::ISocketTypes::TResult_Error:
			delete pStreamSocket;
			pStreamSocket = NULL;
			break;

		case vos::ISocketTypes::TResult_Interrupted:
		case vos::ISocketTypes::TResult_InProgress:
			break;  // -Wall not handled...
		}
	}
}


IMPL_LINK( CommunicationManagerServerAcceptThread, AddConnection, void*, EMPTYARG )
{
	{
		vos::OGuard aGuard( aMAddConnection );
		nAddConnectionEventId = 0;
	}
	pMyServer->AddConnection( xmNewConnection );
	xmNewConnection.Clear();
	return 1;
}


#define GETSET(aVar, KeyName, Dafault)                 \
	aVar = aConf.ReadKey(KeyName,"No Entry");          \
	if ( aVar == "No Entry" )                          \
	{                                                  \
		aVar = Dafault;                                \
		aConf.WriteKey(KeyName, aVar);                 \
	}


CommunicationManagerClientViaSocket::CommunicationManagerClientViaSocket( ByteString aHost, sal_uLong nPort, sal_Bool bUseMultiChannel )
: CommunicationManagerClient( bUseMultiChannel )
, aHostToTalk( aHost )
, nPortToTalk( nPort )
{
}

CommunicationManagerClientViaSocket::CommunicationManagerClientViaSocket( sal_Bool bUseMultiChannel )
: CommunicationManagerClient( bUseMultiChannel )
, aHostToTalk( "" )
, nPortToTalk( 0 )
{
}

CommunicationManagerClientViaSocket::~CommunicationManagerClientViaSocket()
{
}


