#include "wap.h"
#include <eikenv.h>
#include <cdbcols.h>
#include <commdb.h>
#include "pointer.h"
#include <apdatahandler.h> 
#include <apaccesspointitem.h> 
#include <aputils.h>

const TInt CWap::MAX_RETRIES=5;
const TInt CWap::MAXSIZE=25*1024;
const TInt CConnectionOpener::MAX_RETRIES=10;

CTimeOut::CTimeOut(MTimeOut& i_cb) : CTimer(EPriorityNormal), cb(i_cb)
{
}

void CTimeOut::ConstructL()
{
	CTimer::ConstructL();
	CActiveScheduler::Add(this);
}

CTimeOut* CTimeOut::NewL(MTimeOut& i_cb)
{
	auto_ptr<CTimeOut> ret (new (ELeave) CTimeOut(i_cb));
	ret->ConstructL();
	return ret.release();
}

void CTimeOut::RunL()
{
	cb.expired();
}

void CTimeOut::Wait(int seconds)
{
	Reset();
	TTime at;
	at.HomeTime();
	at+=TTimeIntervalSeconds(seconds);
	At(at);
}

void CTimeOut::Reset()
{
	Cancel();
}

CTimeOut::~CTimeOut()
{
	Cancel();
}


CConnectionOpener* CConnectionOpener::NewL(MApp_context& Context, MSocketObserver& Observer)
{
	auto_ptr<CConnectionOpener> ret(new (ELeave) CConnectionOpener(Context, Observer));
	ret->ConstructL();
	return ret.release();
}

void CConnectionOpener::ConstructL()
{
	iWait=CTimeOut::NewL(*this);
	CActiveScheduler::Add(this);
}

CConnectionOpener::CConnectionOpener(MApp_context& Context, MSocketObserver& Observer) : 
	CActive(EPriorityIdle), MContextBase(Context), iObserver(Observer)
{
}

void CConnectionOpener::MakeConnectionL(TUint32 IapID)
{
	iObserver.info(this, _L("MakeConnectionL"));

	Cancel();

	if (!iInitiator) iInitiator=CIntConnectionInitiator::NewL();
	TUint32 activeiap;
	if (iInitiator->GetActiveIap(activeiap)==KErrNone && activeiap==IapID) {
		iObserver.success(this);
		return;
	}

	iIapId=IapID;
	iConnPref.iRanking = 1; 
	iConnPref.iDirection = ECommDbConnectionDirectionOutgoing; 
	iConnPref.iDialogPref = ECommDbDialogPrefDoNotPrompt; 
	CCommsDbConnectionPrefTableView::TCommDbIapBearer bearer; 
	bearer.iBearerSet = KMaxTUint32;
	bearer.iIapId = IapID; 
	iConnPref.iBearer = bearer;

	// we cannot really know if the initiator is usable
	// at this point (if retrying/reusing). Let's just
	// recreate it
	delete iInitiator; iInitiator=0;
	iInitiator=CIntConnectionInitiator::NewL();

	TInt ret=iInitiator->TerminateActiveConnection(iStatus);
	if (ret==KErrNone) {
		current_state=CLOSING;
		SetActive();
	} else {
		current_state=CONNECTING;
		iInitiator->ConnectL(iConnPref, iStatus);
		SetActive();
	}

	iWait->Wait(45);

}

void CConnectionOpener::expired()
{
	++iRetryCount;
	if (iRetryCount>MAX_RETRIES) {
		iObserver.error(this, -1003, _L("Opener retries exceeded"));
	} else {
		iObserver.info(this, _L("Conn retry"));
		MakeConnectionL(iIapId);
	}
}

void CConnectionOpener::RunL()
{
	TBuf<50> msg;
	iWait->Reset();

	if ( (iStatus==KConnectionTerminated || iStatus==0)
		&& current_state==CLOSING) {
		iObserver.info(this, _L("prev conn closed"));
		// The initiator seems to close its handles after a TerminateActiveConnection
		// so it has to be recreated if we want to call some other methods
		delete iInitiator; iInitiator=0;
		iInitiator=CIntConnectionInitiator::NewL();
		iInitiator->ConnectL(iConnPref, iStatus);
		current_state=CONNECTING;
		SetActive();
		return;
	}

	if (iStatus==CONNECTED) return;

	if (iStatus!=KErrNone && iStatus!=KConnectionPref1Exists &&
		iStatus!=KConnectionPref1Created) {
		if (iRetryCount>=MAX_RETRIES) {
			msg.Format(_L("Opener error %d"), iStatus.Int());
			iObserver.error(this, iStatus.Int(), msg);
			return;
		} else {
			msg.Format(_L("Opener error %d (Retry)"), iStatus.Int());
			iObserver.info(this, msg);
			if (current_state==CONNECTING) {
				current_state=RETRYING_CONNECT;
			} else {
				current_state=RETRYING_CLOSE;
			}

			// it seems that we sometimes get an error even
			// though the connection gets established. We wait
			// for 15 secs, which should be enough to detect
			// the new connection
			iWait->Wait(15);
			current_state=IDLE;
			return;
		}
	}

	// If the connection doesn't exist the initiator sends *2* requestcompletes:
	// one with KConnectionPref1Exists and one with KConnectionPref1Created
	// if it does only KConnectionPref1Exists is send.

	if (current_state==CONNECTING) {
		iObserver.success(this);
		current_state=CONNECTED;
	}

	if (iStatus==KConnectionPref1Exists) {
		SetActive();
	}
}

void CConnectionOpener::DoCancel()
{
	iInitiator->Cancel();
}

TInt CConnectionOpener::RunError(TInt aError)
{
	if (current_state==CONNECTING) {
		TBuf<50> msg;
		msg.Format(_L("CConnectionOpener::RunError %d"), aError);
		iObserver.error(this, aError, msg);
	}
	iWait->Reset();
	return KErrNone;
}

void CConnectionOpener::CloseConnection()
{
	iWait->Reset();
	iRetryCount=0;
	Cancel();
	TRAPD(err,
		if (!iInitiator) iInitiator=CIntConnectionInitiator::NewL();
		iInitiator->TerminateActiveConnection();
	);
	delete iInitiator; iInitiator=0;
	current_state=IDLE;
}

CConnectionOpener::~CConnectionOpener()
{
	Cancel();
	delete iWait;
	if (iInitiator) iInitiator->TerminateActiveConnection();
	delete iInitiator; iInitiator=0;
}


CWap* CWap::NewL(MApp_context& Context, MSocketObserver& obs)
{
	auto_ptr<CWap> ret(new (ELeave) CWap(Context, obs));
	ret->ConstructL();
	return ret.release();
}

void CWap::DoCancel()
{
	connection.CancelGetEvent();
}

void CWap::ReleaseTx()
{
	if (!iTxOpen) return;
	transaction.Release();
	iTxOpen=false;
}

void CWap::ReleaseConn()
{
	if (!iConnOpen) return;
	connection.Close();
	iConnOpen=false;
}

void CWap::GetGwAddress(TDes8& addr)
{
	TInt ret;
	bool found=false;

	CCommsDatabase* db;
	iState=_L("CCommsDatabase::NewL");
	db=CCommsDatabase::NewL(EDatabaseTypeIAP);
	CleanupStack::PushL(db);

	iState=_L("db->OpenTableLC");
	CCommsDbTableView *view=db->OpenTableLC(TPtrC(WAP_IP_BEARER));
	
	iState=_L("view->GotoNextRecord");
	while (view->GotoNextRecord()==KErrNone) {
		TUint32 currid;
		iState=_L("view->ReadUintL");
		view->ReadUintL(TPtrC(WAP_IAP), currid);
		if (currid==iIapId) {
			found=true;
			TBuf<100> tmp;
			
			/* for WINS, since the commsdb modification
			 * doesn't work without a numpad
			view->UpdateRecord();
			view->WriteTextL(TPtrC(WAP_GATEWAY_ADDRESS), _L("169.254.1.68"));
			view->PutRecordChanges();
			*/

			iState=_L("view->ReadTextL");
			view->ReadTextL(TPtrC(WAP_GATEWAY_ADDRESS), tmp);

			iState=_L("Convert ret");
			TBuf<40> msg;
			msg.Format(_L("GW %S"), &tmp);
			observer.info(this, msg);
			CC()->ConvertFromUnicode(addr, tmp);
			// at least on the emulator there seems to be
			// NULL bytes in the string (I don't know why,
			// but let's remove them)
			int mv=0;
			for (int i=0; i<addr.Length(); i++) {
				if (addr[i]=='\0') ++mv;
				else if (mv>0) addr[i-mv]=addr[i];
			}
			if (mv>0) addr.SetLength(addr.Length()-mv);
			break;
		}
		iState=_L("view->GotoNextRecord");
	}
	
	iState=_L("GetGwAddr ret");
	CleanupStack::PopAndDestroy(2); //db, view

	if (!found || !addr.Compare(_L8("0.0.0.0")) || addr.Length()==0) {
		TBuf<40> msg;
		msg.Format(_L("Didn't find wap %d"), iIapId);
		observer.info(this, msg);
#ifndef __WINS__
		addr=_L8("213.161.40.40");
#else	
		addr=_L8("169.254.1.68");
#endif
	} 

}

void CWap::success(CBase* source)
{
	_LIT8(headers, "");

	observer.info(this, _L("Connect to GW..."));
	TBuf8<100> host;
	GetGwAddress(host);
	TInt port=9201;
	User::LeaveIfError(connection.Open(server, host, port, 0, EIP, EFalse));
	iConnOpen=true;
	TInt ret=connection.Connect(headers, iCap);
	if (ret!=KErrNone) {
		observer.error(this, ret, _L("Connect() failed"));
		return;
	}

	connection.GetEvent(event, event_tx, iStatus);
	SetActive();
	current_state=TX;
}

void CWap::error(CBase* source, TInt code, const TDesC& reason)
{
	observer.error(this, code, reason);
}

void CWap::info(CBase* source, const TDesC& msg)
{
	observer.info(this, msg);
}

void CWap::RunL()
{
	TBuf<50> msg;

	if (iStatus!=KErrNone) {
		if (current_state==TX) {
			RWSPCOConn::TSessionState s;
			connection.GetSessionState(s);
			msg.Format(_L("Conn state %d, error %d"), 
				TInt(s), iStatus);
		} else {
			msg.Format(_L("error %d"), iStatus.Int());
		}
		observer.error(this, iStatus.Int(), msg);
		return;
	}

	TBuf8<100> msg8;
	switch (current_state) {
	case CONNECTING:
		observer.error(this, -1002, _L("CWap Inconsistent state"));
		break;
	case TX:
		msg8.Format(_L8("\nRunL TX event %d"), (TInt)event());
		file.Write(msg8);
		{
		switch (event()) {
		case RWSPCOConn::EDisconnect_ind_s:
		case RWSPCOConn::EAbort_ind_t:
		case RWSPCOConn::EException_ind_e:
			{
			TBuf8<256> data;
			int i;
			for (i=RWSPCOConn::EServerHeaders; i<=RWSPCOConn::ESuspendReason; i++) {
				data.Zero();
				TInt ret=connection.GetSessionData(data, (RWSPCOConn::TSessionDataType)i);
				msg8.Format(_L8("\nSessionData %d, ret %d, len %d: "),
					i, ret, data.Length());
				file.Write(msg8);
				file.Write(data);
			}
			for (i=RWSPCOTrans::EPushBody; i<=RWSPCOTrans::EAbortReason; i++) {
				data.Zero();
				TInt ret=event_tx.GetData(data, (RWSPCOTrans::TDataType)i);
				msg8.Format(_L8("\nData %d, ret %d, len %d: "),
					i, ret, data.Length());
				while (ret==RWAPConn::EMoreData) {
					ret=event_tx.GetData(data, (RWSPCOTrans::TDataType)i);
				}
				file.Write(msg8);
				file.Write(data);
			}
			msg.Format(_L("disc %d"), event());
			//current_state=CONNECTED;
			if (iRetryCount==MAX_RETRIES) {
				iRetryCount=0;
				iFileSize=0;
				iOffset=0;
				observer.error(this, event(), msg);
			} else {
				++iRetryCount;
				observer.info(this, _L("retrying"));
				Connect(iIapId, iHost);
			}
			}
			break;
		case RWSPCOConn::EConnect_cnf_s:
			current_state=CONNECTED;
			if (iRetryCount==0) {
				observer.success(this);
			} else {
				HandleFile();
			}
			break;
		case RWSPCOConn::EMethodResult_ind_t:
			{
			iRetryCount=0;
			TBuf8<100> res;
			TInt ret;
			ret=event_tx.GetData(res, RWSPCOTrans::EResultBody);
			event_tx.Acknowledge(_L8(""));
			ReleaseTx();
			current_state=CONNECTED;
			msg8.Format(_L8("GetData: %d\n"), ret);
			file.Write(msg8);
			file.Write(res);
			if (res.Length()>=2 && ! res.Left(2).Compare(_L8("OK")) ) {
				current_state=RESULT_OK_WAIT;
				observer.info(this, _L("Intra-post wait"));
#ifndef __WINS__
				iWait->Wait(10);
#else
				iWait->Wait(3);
#endif
			} else {
				observer.error(this, -1001, _L("error result"));
			}
			}
			break;
		default:
			msg.Format(_L("Event %d"), event());
			observer.info(this, msg);
			connection.GetEvent(event, event_tx, iStatus);
			SetActive();
			break;
		}
		}
		break;
	}
	return;

}

void CWap::expired()
{
	switch(current_state) {
	case RESULT_OK_WAIT:
		current_state=CONNECTED;
		HandleFile();
		break;
	default:
		observer.error(this, -1002, _L("Inconsistent state"));
		break;
	}
}

void CWap::MakeContentLength(TUint size, TDes8& header)
{
	TChar bytes;
	if (size>0xffffff) {
		bytes=4;
	} else if(size>0xffff) {
		bytes=3;
	} else if (size>0xff) {
		bytes=2;
	} else {
		bytes=1;
	}

	header.Append(bytes);

	TChar enc[4];
	TUint mask=256;
	TUint mod=0;
	TInt i=bytes;
	--i;
	while (i>=0) {
		mod=size % mask;
		size-=mod;
		if (mask>256) mod/=(mask/256);
		enc[i]=mod;
		mask*=256;
		--i;
	}
	for (i=0; i<bytes; i++) {
		header.Append(enc[i]);
	}
}

void CWap::Store(const TDesC& filename)
{
	input.Close();
	iFilename=filename;
	User::LeaveIfError(input.Open(Fs(), filename, EFileRead|EFileShareAny));
	TInt size;
	User::LeaveIfError(input.Size(iFileSize));
	iOffset=0;
	iRetryCount=0;
	HandleFile();
}

void CWap::HandleFile()
{
	ReleaseTx();

	TFileName name;
	TParse parse;
	parse.Set(iFilename, 0, 0);
	name=parse.NameAndExt();

	if (iRetryCount==0) {
		if (iOffset>=iFileSize) {
			input.Close();
			observer.success(this);
			return;
		}

		bool first_part=false;
		if (iOffset==0) first_part=true;

		TInt size=iFileSize-iOffset;
		if (size>MAXSIZE) size=MAXSIZE;

		if (!iContents || (iContents->Des().MaxSize()/2<size) ) {
			delete iContents; iContents=0;
			iContents=HBufC8::NewL(size);
		}
		iContents->Des().Zero();
		TPtr8 ptr=iContents->Des();
		input.Read(ptr, size);
		
		head.Zero();
		// Content-type first in POST
		// upload doesn't care what it is, let's use image/gif since it's binary
		head.Append('\x9d');
		// Accept: */*
		head.Append('\x80'); head.Append('\x80');
		// Content-length:
		head.Append('\x8d');
		MakeContentLength(size, head);

		TInt urllen=1;
		urllen+=iHost.Length()+10;
		urllen+=iPath.Length()+2;
		urllen+=name.Length();
		if (!iURL || (iURL->Des().MaxSize()/2<urllen)) {
			delete iURL; iURL=0;
			iURL=HBufC::NewL(urllen);
		}
		iURL->Des().Zero();
		TBuf<1> op;
		if (first_part)
			op=_L("C");			
		else
			op=_L("A");
		iURL->Des().Format(_L("%S?%S;%d;%S/%S"),
			&iHost, &op, iOffset, &iPath, &name);
		iOffset+=size;

	} 
	
	User::LeaveIfError(connection.CreateTransaction(RWAPConn::EPost, 
		*iURL, head, *iContents, transaction));
	iTxOpen=true;

	current_state=TX;
	TBuf<100> msg;
	msg.Format(_L("S %S p %d"), &name, TInt(iOffset/MAXSIZE));
	observer.info(this, msg);
	connection.GetEvent(event, event_tx, iStatus);
	SetActive();
}

void CWap::ConstructL()
{
	iWait=CTimeOut::NewL(*this);

	iAgentOpen=false;
	User::LeaveIfError(server.Connect());
	iCap = CCapCodec::NewL();
	iCap->SetServerSDUSize(MAXSIZE+3000);
	iCap->SetClientSDUSize(MAXSIZE+3000);

	iInitiator=CConnectionOpener::NewL(AppContext(), *this);

	CActiveScheduler::Add(this);
	User::LeaveIfError(file.Replace(Fs(), _L("c:\\wap.txt"), EFileWrite|EFileShareAny));
}

CWap::~CWap()
{
	file.Close();
	Cancel();

	ReleaseTx();
	ReleaseConn();
	server.Close();

	delete iWait;
	if (iAgentOpen) {
		agent.Stop();
		agent.Close();
	}

	delete iInitiator;

	delete iContents;
	delete iCap;
	delete iURL;
}

void CWap::Connect(TUint32 IapID, const TDesC& UrlBase)
{	

	iHost=UrlBase;
	iIapId=IapID;
	current_state=CONNECTING;

	iInitiator->MakeConnectionL(IapID);
}

void CWap::Close()
{
	Cancel();
	ReleaseTx();
	ReleaseConn();
	if (iAgentOpen) {
		agent.Stop();
		agent.Close();
		iAgentOpen=false;
	}
	delete iContents; iContents=0;
	delete iURL; iURL=0;
	if (iInitiator) iInitiator->CloseConnection();
	observer.success(this);
}

void CWap::Cwd(const TDesC& dir)
{
	iPath=dir;
}

TInt CWap::RunError(TInt aError)
{
	TBuf<100> msg;
	msg.Format(_L("%d %S"), aError, &iState);
	observer.error(this, aError, msg);
	return KErrNone;
}

