/*
* Copyright (c) 2007-2010 Sebastian Brannstrom, Lars Persson, EmbedDev AB
*
* All rights reserved.
* This component and the accompanying materials are made available
* under the terms of the License "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:
* EmbedDev AB - initial contribution.
*
* Contributors:
*
* Description:
*
*/

// HttpEventHandler.cpp
#include <e32debug.h>
#include <httperr.h>
#include <sysutil.h>
#include "HttpEventHandler.h"
#include "bautils.h"
#include "Httpclient.h"
#include "Podcatcher.pan"

const TInt64 KMinDiskSpace = 1024 * 1024; // at least 1 MB must remain

void CHttpEventHandler::ConstructL()
	{
	//iVerbose = ETrue;	
	}


CHttpEventHandler::CHttpEventHandler(CHttpClient* aClient, MHttpClientObserver &aCallbacks, RFs& aFs): 
	iFileServ(aFs), iHttpClient(aClient), iCallbacks(aCallbacks)
	{
	}


CHttpEventHandler::~CHttpEventHandler()
	{	
	}


CHttpEventHandler* CHttpEventHandler::NewLC(CHttpClient* aClient, MHttpClientObserver &aCallbacks, RFs& aFs)
	{
	CHttpEventHandler* me = new(ELeave)CHttpEventHandler(aClient, aCallbacks, aFs);
	CleanupStack::PushL(me);
	me->ConstructL();
	return me;
	}


CHttpEventHandler* CHttpEventHandler::NewL(CHttpClient* aClient, MHttpClientObserver &aCallbacks, RFs& aFs)
	{
	CHttpEventHandler* me = NewLC(aClient, aCallbacks, aFs);
	CleanupStack::Pop(me);
	return me;
	}

void CHttpEventHandler::MHFRunL(RHTTPTransaction aTransaction, const THTTPEvent& aEvent)
	{
	//DP1("CHttpEventHandler::MHFRunL, aEvent.iStatus=%d", aEvent.iStatus);
	switch (aEvent.iStatus)
		{
		case THTTPEvent::EGotResponseHeaders:
			{
			// HTTP response headers have been received. We can determine now if there is
			// going to be a response body to save.
			RHTTPResponse resp = aTransaction.Response();
			iLastStatusCode = resp.StatusCode();
			DP1("Status: %d", iLastStatusCode);

			// Dump the headers if we're being verbose
			//DumpRespHeadersL(aTransaction);

			if (resp.HasBody() && (iLastStatusCode >= 200) && (iLastStatusCode < 300) && (iLastStatusCode != 204))
				{
				TInt dataSize = resp.Body()->OverallDataSize();
				if (dataSize >= 0) {
					DP1("Response body size is %d", dataSize);
					iBytesTotal = dataSize;	
				} else {
					DP("Response body size is unknown");
					iBytesTotal = -1;
				}
				iCallbacks.DownloadInfo(iHttpClient, dataSize);

				}

			DP1("iFileOpen=%d", iFileOpen);
			if (!iFileOpen)
				{
				iFileServ.Parse(iFileName, iParsedFileName);
				TInt valid = iFileServ.IsValidName(iFileName);
				
				if (!valid)
					{
					DP("The specified filename is not valid!.");
					iHttpClient->ClientRequestCompleteL(KErrBadName);
					}
				else
					{
					if (iContinue) {
						TInt err = iRespBodyFile.Open(iFileServ, iParsedFileName.FullName(),EFileWrite);
						if (err)
							{
							DP2("There was an error=%d opening file '%S'", err, &iParsedFileName.FullName());
							iHttpClient->ClientRequestCompleteL(KErrInUse);
							User::Leave(err);
							} 
						else
							{
							iFileOpen = ETrue;
							int pos = -KByteOverlap;
							if((err=iRespBodyFile.Seek(ESeekEnd, pos)) != KErrNone)
								{
								DP("Failed to set position!");
								iHttpClient->ClientRequestCompleteL(KErrWrite);
								User::Leave(err);
								}
							iBytesDownloaded = (pos > 0) ? pos : 0;
							iBytesTotal += iBytesDownloaded;
							DP1("Total bytes is now %u", iBytesTotal);
							DP1("Seeking end: %d", pos);
							}
						}
					else 
						{
						TInt err = iRespBodyFile.Replace(iFileServ,
														 iParsedFileName.FullName(),
														 EFileWrite);
						if (err)
							{
							DP("There was an error replacing file");
							User::Leave(err);
							}
						else
							{
							iFileOpen = ETrue;
							}
						}
					}
				}
			} break;
		case THTTPEvent::EGotResponseBodyData:
			{
			// Get the body data supplier
			iRespBody = aTransaction.Response().Body();

			// Some (more) body data has been received (in the HTTP response)
			//DumpRespBody(aTransaction);
			//DP1("Saving: %d", iSavingResponseBody);
			
			// check if we have enough disk space
			if (iDriveNo != -1 && SysUtil::DiskSpaceBelowCriticalLevelL( &iFileServ, KMinDiskSpace, iDriveNo ))
				{
					TInt error = KErrDiskFull;
					iFileOpen = EFalse;
					iRespBodyFile.Close();
					iCallbacks.FileError(error);
					iHttpClient->ClientRequestCompleteL(error);
					return;
				}
			
			// Append to the output file if we're saving responses
			if (iFileOpen)
				{
				TPtrC8 bodyData;
				iRespBody->GetNextDataPart(bodyData);
				iBytesDownloaded += bodyData.Length();
				TInt error = iRespBodyFile.Write(bodyData);

				// on writing error we close connection 
				if (error != KErrNone) {
					iFileOpen = EFalse;
					iRespBodyFile.Close();
					iCallbacks.FileError(error);
					iHttpClient->ClientRequestCompleteL(error);
					return;
				}

				if (!iSilent) {
					iCallbacks.Progress(iHttpClient, iBytesDownloaded, iBytesTotal);
				}
				}

			// Done with that bit of body data
			iRespBody->ReleaseData();
			} break;
		case THTTPEvent::EResponseComplete:
			{
			// The transaction's response is complete
			DP("Transaction Complete");
			DP("Closing file");
			iFileOpen = EFalse;
			iRespBodyFile.Close();
			} break;
		case THTTPEvent::ESucceeded:
			{
			DP("Transaction Successful");
			iFileOpen = EFalse;
			iRespBodyFile.Close();
			aTransaction.Close();
			iHttpClient->ClientRequestCompleteL(KErrNone);
			} break;
		case THTTPEvent::EFailed:
			{
			DP("Transaction Failed");
			iFileOpen = EFalse;
			iRespBodyFile.Close();
			aTransaction.Close();
			
			if(iLastStatusCode == HTTPStatus::EOk || iLastStatusCode == HTTPStatus::ECreated || iLastStatusCode == HTTPStatus::EAccepted)
				{
				iLastStatusCode = KErrNone;
				}
			
			iHttpClient->ClientRequestCompleteL(iLastStatusCode);
			} break;
		case THTTPEvent::ERedirectedPermanently:
			{
			DP("Permanent Redirection");
			} break;
		case THTTPEvent::ERedirectedTemporarily:
			{
			DP("Temporary Redirection");
			} break;
		default:
			{
			DP1("<unrecognised event: %d>", aEvent.iStatus);
			// close off the transaction if it's an error
			if (aEvent.iStatus < 0)
				{
				iFileOpen = EFalse;
				iRespBodyFile.Close();
				aTransaction.Close();
				iHttpClient->ClientRequestCompleteL(aEvent.iStatus);
				}
			} break;
		}
	}

TInt CHttpEventHandler::MHFRunError(TInt aError, RHTTPTransaction aTransaction, const THTTPEvent& /*aEvent*/)
	{
	DP1("MHFRunError fired with error code %d", aError);
	aTransaction.Close();
	TRAP_IGNORE(iHttpClient->ClientRequestCompleteL(aError));
	return KErrNone;
	}

void CHttpEventHandler::SetSaveFileName(const TDesC &fName, TBool aContinue)
	{
	DP1("CHttpEventHandler::SetSaveFileName, aContinue=%d", aContinue);
	iFileName.Copy(fName);
	iContinue = aContinue;
	
	switch(fName[0])
		{
		case 'C':
		case '\\':
			iDriveNo = EDriveC;
			break;
		case 'E':
			iDriveNo = EDriveE;
			break;
		case 'F':
			iDriveNo = EDriveF;
			break;
		case 'G':
			iDriveNo = EDriveG;
			break;
		default:
			Panic(EPodcatcherDownloadDrive);
			break;
		}
	DP1("iDriveNo set to %d", iDriveNo);
	}

void CHttpEventHandler::DumpRespHeadersL(RHTTPTransaction& aTrans)
	{
	RHTTPResponse resp = aTrans.Response();
	RStringPool strP = aTrans.Session().StringPool();
	RHTTPHeaders hdr = resp.GetHeaderCollection();
	THTTPHdrFieldIter it = hdr.Fields();

	TBuf<KMaxHeaderNameLen>  fieldName16;
	TBuf<KMaxHeaderValueLen> fieldVal16;

	while (it.AtEnd() == EFalse)
		{
		RStringTokenF fieldName = it();
		RStringF fieldNameStr = strP.StringF(fieldName);
		THTTPHdrVal fieldVal;
		if (hdr.GetField(fieldNameStr,0,fieldVal) == KErrNone)
			{
			const TDesC8& fieldNameDesC = fieldNameStr.DesC();
			fieldName16.Copy(fieldNameDesC.Left(KMaxHeaderNameLen));
			switch (fieldVal.Type())
				{
			case THTTPHdrVal::KTIntVal:
				DP2("%S: %d", &fieldName16, fieldVal.Int());
				break;
			case THTTPHdrVal::KStrFVal:
				{
				RStringF fieldValStr = strP.StringF(fieldVal.StrF());
				const TDesC8& fieldValDesC = fieldValStr.DesC();
				fieldVal16.Copy(fieldValDesC.Left(KMaxHeaderValueLen));
				DP2("%S: %S", &fieldName16, &fieldVal16);
				}
				break;
			case THTTPHdrVal::KStrVal:
				{
				RString fieldValStr = strP.String(fieldVal.Str());
				const TDesC8& fieldValDesC = fieldValStr.DesC();
				fieldVal16.Copy(fieldValDesC.Left(KMaxHeaderValueLen));
				DP2("%S: %S", &fieldName16, &fieldVal16);
				}
				break;
			case THTTPHdrVal::KDateVal:
				{
				TDateTime date = fieldVal.DateTime();
				} 
				break;
			default:
				DP1("%S: <unrecognised value type>", &fieldName16);
				break;
				}

			// Display realm for WWW-Authenticate header
			RStringF wwwAuth = strP.StringF(HTTP::EWWWAuthenticate,RHTTPSession::GetTable());
			if (fieldNameStr == wwwAuth)
				{
				// check the auth scheme is 'basic'
				RStringF basic = strP.StringF(HTTP::EBasic,RHTTPSession::GetTable());
				RStringF realm = strP.StringF(HTTP::ERealm,RHTTPSession::GetTable());
				THTTPHdrVal realmVal;
				if ((fieldVal.StrF() == basic) && 
					(!hdr.GetParam(wwwAuth, realm, realmVal)))
					{
					RStringF realmValStr = strP.StringF(realmVal.StrF());
					fieldVal16.Copy(realmValStr.DesC());
					DP1("Realm is: %S", &fieldVal16);
					}
				}
			}
		++it;
		}
	}

void CHttpEventHandler::DumpRespBody(RHTTPTransaction& aTrans)
	{
	MHTTPDataSupplier* body = aTrans.Response().Body();
	TPtrC8 dataChunk;
	TBool isLast = body->GetNextDataPart(dataChunk);
	DumpIt(dataChunk);
	if (isLast)
		DP("Got last data chunk.");
	}


void CHttpEventHandler::DumpIt(const TDesC8& aData)
//Do a formatted dump of binary data
	{
	// Iterate the supplied block of data in blocks of cols=80 bytes
	const TInt cols=16;
	TInt pos = 0;
	TBuf<KMaxFileName - 2> logLine;
	TBuf<KMaxFileName - 2> anEntry;
	const TInt dataLength = aData.Length();

	while (pos < dataLength)
		{
		//start-line hexadecimal( a 4 digit number)
		anEntry.Format(TRefByValue<const TDesC>_L("%04x : "), pos);
		logLine.Append(anEntry);

		// Hex output
		TInt offset;
		for (offset = 0; offset < cols; ++offset)
			{
			if (pos + offset < aData.Length())
				{
				TInt nextByte = aData[pos + offset];
				anEntry.Format(TRefByValue<const TDesC>_L("%02x "), nextByte);
				logLine.Append(anEntry);
				}
			else
				{
				//fill the remaining spaces with blanks untill the cols-th Hex number 
				anEntry.Format(TRefByValue<const TDesC>_L("   "));
				logLine.Append(anEntry);
				}
			}
			anEntry.Format(TRefByValue<const TDesC>_L(": "));
			logLine.Append(anEntry);

		// Char output
		for (offset = 0; offset < cols; ++offset)
			{
			if (pos + offset < aData.Length())
				{
				TInt nextByte = aData[pos + offset];
				if ((nextByte >= ' ') && (nextByte <= '~'))
					{
					anEntry.Format(TRefByValue<const TDesC>_L("%c"), nextByte);
					logLine.Append(anEntry);
					}
				else
					{
					anEntry.Format(TRefByValue<const TDesC>_L("."));
					logLine.Append(anEntry);
					}
				}
			else
				{
				anEntry.Format(TRefByValue<const TDesC>_L(" "));
				logLine.Append(anEntry);
				}
			}
			logLine.Zero();

		// Advance to next  byte segment (1 seg= cols)
		pos += cols;
		}
	}

void CHttpEventHandler::SetSilent(TBool aSilent)
	{
	iSilent = aSilent;
	}

void CHttpEventHandler::CloseSaveFile()
{
	DP("CHttpEventHandler::CloseSaveFile BEGIN");
	if(iRespBody != NULL)
	{		
		if(iRespBodyFile.SubSessionHandle() != 0)
			{
			TInt size;
			iRespBodyFile.Size(size);
			DP2("Closing file at size %d, bytes downloaded %d", size, iBytesDownloaded);
			iRespBodyFile.Close();
			}
	}
	DP("CHttpEventHandler::CloseSaveFile BEGIN");
}

