// Copyright (c) 2005-2009 Nokia Corporation and/or its subsidiary(-ies).
// All rights reserved.
// This component and the accompanying materials are made available
// under the terms of "Eclipse Public License v1.0"
// which accompanies this distribution, and is available
// at the URL "http://www.eclipse.org/legal/epl-v10.html".
//
// Initial Contributors:
// Nokia Corporation - initial contribution.
//
// Contributors:
//
// Description:
//

#include <e32base.h>
#include "IPCBuf.h"
#include "SqlPanic.h"
#include "SqlDbSession.h"

/**
Standard, phase-one HIpcBuf factory method.
Created HIpcBuf instance will be used for transfering large text or binary objects from/to SQL server.
The created HIpcBuf instance will be placed in the cleanup stack.

@param aSession A reference to RSqlDbSession instance.
@param aFunction Prepared function code (with all statement handle bits set)
@param aArgs A set of IPC arguments to be sent to the SQL server.

@return A pointer to the created HIpcBuf instance

@leave KErrNoMemory Out of memory.
*/
HIpcBuf* HIpcBuf::NewLC(RSqlDbSession& aSession, TInt aFunction, TIpcArgs& aArgs)
	{
	HIpcBuf* self = new (ELeave) HIpcBuf(aSession);
	self->PushL();
	self->ConstructL(aFunction, aArgs);
	return self;
	}

/**
Standard, phase-one HIpcBuf factory method.
Created HIpcBuf instance will be used for transfering large text or binary objects from/to SQL server.

@param aSession A reference to RSqlDbSession instance.
@param aFunction Prepared function code (with all statement handle bits set)
@param aArgs A set of IPC arguments to be sent to the SQL server.

@return A pointer to the created HIpcBuf instance

@leave KErrNoMemory Out of memory.
*/
HIpcBuf* HIpcBuf::NewL(RSqlDbSession& aSession, TInt aFunction, TIpcArgs& aArgs)
	{
	HIpcBuf* self = NewLC(aSession, aFunction, aArgs);
	CleanupStack::Pop();
	return self;
	}

/**
Standard, phase-two HIpcBuf construction method.

@param aFunction The command code which will be sent to the SQL server
@param aArgs A set of IPC arguments to be sent to the SQL server.

@leave KErrNoMemory Out of memory.

Usage of the IPC call arguments:
Arg 2: [in/out]  IPC buffer
iBuf.iExt: [in]  stream size in bytes
*/
void HIpcBuf::ConstructL(TInt aFunction, TIpcArgs& aArgs)
	{
	TPckg<TIpcStreamBuf> pckg(iBuf);
	aArgs.Set(2, &pckg);
	__SQLLEAVE_IF_ERROR(iHandle = iSession.SendReceive(aFunction, aArgs));
	TUint8* base = iBuf.iData;
	// if reading we already have one buffer-full of data
	TInt avail = Max(0, Min(iBuf.iExt, KIpcBufSize));
	SetBuf(ERead, base, base + avail);
	SetPos(ERead, avail);
	SetBuf(EWrite, base, base);
	SetPos(EWrite, 0);
	}

/**
@param aSession A reference to a sesion object.
*/
HIpcBuf::HIpcBuf(RSqlDbSession& aSession) :
	iSession(aSession),
	iHandle(0),
	iRPos(0),
	iWPos(0)
	{
	iBuf.iExt = -1;
	}

/**
*/
HIpcBuf::~HIpcBuf()
	{
	if(iHandle > 0) //iHandle is valid only when > 0.
		{
		(void)iSession.SendReceive(::MakeMsgCode(ESqlSrvStreamClose, ESqlSrvStreamHandle, iHandle));
		}
	}

/**
Fill the buffer's read area.
*/
TInt HIpcBuf::UnderflowL(TInt)
	{
	// when handle is null there is no data to read from server
	if(!iHandle)
		{
		return 0;
		}
	__SQLASSERT(Avail(ERead) == 0, ESqlPanicInternalError);
	TUint8* base=iBuf.iData;
	IpcWriteL(base,Lag(EWrite));
	SetBuf(EWrite,base,base);

	TInt len=IpcReadL(base,iBuf.ESize);
	SetBuf(ERead,base,base+len);
	return len;
	}

/**
Set up the buffer's write area.
*/
void HIpcBuf::OverflowL()
	{
	__SQLASSERT(Avail(EWrite) == 0, ESqlPanicInternalError);
	
	TUint8* base = iBuf.iData;
	MovePos(ERead, Lag(ERead));
	SetBuf(ERead, base, base);

	IpcWriteL(base, Lag(EWrite));
	SetBuf(EWrite, base, base + iBuf.ESize);
	}

/**
Destroys HIpcBuf instance.
*/
void HIpcBuf::DoRelease()
	{
	delete this;
	}

/**
Synchronise this buffer with its file, giving up on outstanding writes in case of failure.
*/
void HIpcBuf::DoSynchL()
	{
	TUint8* base = iBuf.iData;
	MovePos(ERead, Lag(ERead));
	TInt lag = Lag(EWrite);
	SetBuf(ERead | EWrite, base, base);
	iBuf.iExt = -1;
	IpcWriteL(base, lag);
	__SQLLEAVE_IF_ERROR(iSession.SendReceive(::MakeMsgCode(ESqlSrvStreamSynch, ESqlSrvStreamHandle, iHandle)));
	}

/**
Read direct from ipc if asked to transfer more than a bufferful.
*/
TInt HIpcBuf::DoReadL(TAny* aPtr, TInt aMaxLength)
	{
	__SQLASSERT(aMaxLength > 0, ESqlPanicInternalError);
	TInt avail = Avail(ERead);
	__SQLASSERT(avail >= 0 && Avail(EWrite) >= 0, ESqlPanicInternalError);
	if(avail > 0)
		{
		TInt len = Min(aMaxLength, avail);
		TUint8* ptr = Ptr(ERead);
		aPtr = Mem::Copy(aPtr, ptr, len);
		SetPtr(ERead, ptr + len);
		aMaxLength -= len;
		if(aMaxLength == 0)
			return len; // that's it
		}
	__SQLASSERT(Avail(ERead) == 0, ESqlPanicInternalError);
	if(aMaxLength < iBuf.ESize)
		return avail + TStreamBuf::DoReadL(aPtr, aMaxLength);

	// when handle is null there is no more data to read from server
	if(!iHandle)
		{
		return 0;
		}

	TUint8* base = iBuf.iData;
	IpcWriteL(base, Lag(EWrite));
	SetBuf(ERead | EWrite, base, base);
	return avail + IpcReadL(aPtr, aMaxLength);
	}

/**
Write direct to ipc if asked to transfer more than a bufferful.
*/
void HIpcBuf::DoWriteL(const TAny* aPtr,TInt aLength)
	{
	__SQLASSERT(aLength > 0, ESqlPanicInternalError);
	TInt avail = Avail(EWrite);
	__SQLASSERT(Avail(ERead) >= 0 && avail >= 0, ESqlPanicInternalError);
	if(avail > 0)
		{
		TInt len = Min(aLength, avail);
		SetPtr(EWrite, Mem::Copy(Ptr(EWrite), aPtr, len));
		aLength -= len;
		if(aLength == 0)
			return; // done

		aPtr = (TUint8*)aPtr + len;
		}
	__SQLASSERT(Avail(EWrite) == 0, ESqlPanicInternalError);
	if(aLength < iBuf.ESize)
		TStreamBuf::DoWriteL(aPtr, aLength);
	else
		{
		TUint8* base = iBuf.iData;
		IpcWriteL(base, Lag(EWrite));
		MovePos(ERead, Lag(ERead));
		SetBuf(ERead | EWrite, base, base);
		IpcWriteL(aPtr, aLength);
		}
	}

/**
Position the mark(s) indicated by aMark at aOffset from aLocation.
*/
TStreamPos HIpcBuf::DoSeekL(TMark aMark, TStreamLocation aLocation, TInt aOffset)
	{
	TUint8* base = iBuf.iData;
	TInt end = EndL();

	switch(aLocation)
		{
	case EStreamBeginning:
		break;
	case EStreamMark:
		switch(aMark)
			{
		case ERead:
			aOffset += Mark(ERead);
			break;
		case EWrite:
			aOffset += Mark(EWrite);
			break;
		default:
			__SQLASSERT_ALWAYS(0, ESqlPanicStreamMarkInvalid);
			break;
			}
		break;
	case EStreamEnd:
		aOffset += end;
		break;
	default:
		__SQLASSERT_ALWAYS(0, ESqlPanicStreamLocationInvalid);
		break;
		}
	TInt r = KErrNone;
	if(aOffset < 0)
		{
		aOffset = 0;
		r = KErrEof;
		}
	else if(aOffset > end)
		{
		aOffset = end;
		r = KErrEof;
		}

	__SQLASSERT_ALWAYS(!(aMark & ~(ERead | EWrite)), ESqlPanicStreamMarkInvalid);
	if(aMark & ERead)
		{
		TInt lag = aOffset - Pos(ERead);
		if(lag >= base - End(ERead) && lag <= 0)
			SetPtr(ERead, End(ERead) + lag);
		else
			{
			SetPos(ERead, aOffset);
			SetBuf(ERead, base, base);
			}
		}
	if(aMark & EWrite && aOffset != Mark(EWrite))
		{
		IpcWriteL(base, Lag(EWrite));
		SetPos(EWrite, aOffset);
		SetBuf(EWrite, base, base);
		}
	__SQLLEAVE_IF_ERROR(r);
	return TStreamPos(aOffset);
	}

/**
Read from the server at the current read position.
Arg 0:            not used
Arg 1: [out]      from which position to read
Arg 2: [in/out]   IPC buffer
Arg 3: [out]      max length of the requested data
*/
TInt HIpcBuf::IpcReadL(TAny* aPtr, TInt aMaxLength)
	{
	__SQLASSERT(aMaxLength >= 0, ESqlPanicInternalError);
	if(aMaxLength == 0)
		return 0;

	TPtr8 des((TUint8*)aPtr, aMaxLength);
	TInt pos = Pos(ERead);
		
	TInt len = __SQLLEAVE_IF_ERROR(iSession.SendReceive(::MakeMsgCode(ESqlSrvStreamRead, ESqlSrvStreamHandle, iHandle), TIpcArgs(0, pos, &des, aMaxLength)));
	pos += len;
	if(len < aMaxLength)
		iBuf.iExt = pos; // end-of-file encountered
	SetPos(ERead, pos);
	return len;
	}

/**
Write to the server at the current write position.
Arg 0:            not used
Arg 1: [out]      from which position to write
Arg 2: [in/out]   IPC buffer
*/
void HIpcBuf::IpcWriteL(const TAny* aPtr, TInt aLength)
	{
	__SQLASSERT(aLength >= 0, ESqlPanicInternalError);
	if(aLength == 0)
		return;

	TPtrC8 ptr((TUint8*)aPtr, aLength);
	TInt ext = iBuf.iExt;
	iBuf.iExt = -1;
	TInt pos = Pos(EWrite);
	__SQLLEAVE_IF_ERROR(iSession.SendReceive(::MakeMsgCode(ESqlSrvStreamWrite, ESqlSrvStreamHandle, iHandle), TIpcArgs(0, pos, &ptr)));
	pos += aLength;
	if(ext >=0 && pos > ext)
		iBuf.iExt = pos;
	SetPos(EWrite, pos);
	}

/**
Determine the end of the stream
*/
TInt HIpcBuf::EndL()
	{
	TInt ext = iBuf.iExt;
	if(ext < 0)
		{
		iBuf.iExt = ext = __SQLLEAVE_IF_ERROR(iSession.SendReceive(::MakeMsgCode(ESqlSrvStreamSize, ESqlSrvStreamHandle, iHandle)));
		}
	return Max(ext, Mark(EWrite));
	}

