/*
* Copyright (c) 1995-2009 Nokia Corporation and/or its subsidiary(-ies).
* 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:
* Nokia Corporation - initial contribution.
*
* Contributors:
*
* Description: 
*
*/

#include <new>
#include <cstring>
#include <string>
#include <iostream>
#include <fstream>
#include <queue>
#include <map>

#include "e32image.h"

#include <filesystem.hpp>
#include <thread/thread.hpp>
#include <thread/mutex.hpp>
#include <thread/condition_variable.hpp>

#include "cache/cacheexception.hpp"
#include "cache/cacheentry.hpp"
#include "cache/cacheablelist.hpp"
#include "cache/cache.hpp"
#include "cache/cachegenerator.hpp"
#include "cache/cachevalidator.hpp"
#include "cache/cachemanager.hpp"
#include <malloc.h>
#ifdef __LINUX__
#define _alloca alloca
#endif

Cache* Cache::Only = (Cache*)0;


Cache* Cache::GetInstance(void) throw (CacheException)
{
	if(! Cache::Only)
	{
		Cache::Only = new (std::nothrow) Cache();
		if(! Cache::Only)
			throw CacheException(CacheException::RESOURCE_ALLOCATION_FAILURE);
	}

	return Cache::Only;
}


void Cache::Initialize(void) throw (CacheException)
{
	//create and open cache meta data file.
	 
	 
	metafile = CacheManager::GetInstance()->GetCacheRoot();
	metafile +=  "/.rofsmeta"; 
	boost::filesystem::path metafilepath(metafile.c_str());
	if(!exists(metafilepath))
	{
		//create cache root directory if it's not present.
		boost::filesystem::path createcacheroot(CacheManager::GetInstance()->GetCacheRoot());
		create_directory(createcacheroot);

		//create cache index file.
		ofstream openmetafile(metafilepath.file_string().c_str(), ios_base::app | ios_base::out);
		if(! openmetafile.is_open())
			throw CacheException(CacheException::CACHE_INVALID);
		openmetafile.close();
	}
	printf("Loading cache meta file : %s\r\n", metafilepath.file_string().c_str());
	ifstream metafileref(metafilepath.file_string().c_str(), ios_base::in);
	if(! metafileref.is_open())
		throw CacheException(CacheException::HARDDRIVE_FAILURE);

	//read ROFS meta file and construct entry map.
	string inboundbuffer;
	while(getline(metafileref, inboundbuffer))
	{
		//validate cache index record.
		if(! ValidateEntry(inboundbuffer))
			throw CacheException(CacheException::CACHE_INVALID);

		//instantiate a new instance of class CacheEntry.
		CacheEntry* entryref = new (nothrow) CacheEntry();
		if(!entryref)
			throw CacheException(CacheException::RESOURCE_ALLOCATION_FAILURE);

		//set entry's attributes.
		
		char* attrwalker  = (char*)_alloca(inboundbuffer.length() + 1);
		memcpy(attrwalker,inboundbuffer.c_str(),inboundbuffer.length() + 1);
		 
		char* start = attrwalker ;
		while(*start != ';')
			start++;
		*start++ = 0;
		entryref->SetOriginalFilename(attrwalker);
		attrwalker = start;
		while(*start != ';')
			start++;
		*start++ = 0;
		entryref->SetOriginalFileCreateTime(attrwalker);
		attrwalker = start;
		while(*start != ';')
			start++;
		*start++ = 0;
		entryref->SetOriginalFileCompression(attrwalker);
		attrwalker = start;
		while(*start != ';')
			start++;
		*start++ = 0;
		entryref->SetCachedFilename(attrwalker);
		attrwalker = start;
		entryref->SetCachedFileCompression(attrwalker);

		//add newly created entry into entry-map.
		string newentrystring(entryref->GetOriginalFilename());
		CacheEntry* existentryref = entrymap[newentrystring];
		if(existentryref) {
			while(existentryref->GetNextEntry())
				existentryref = existentryref->GetNextEntry();
			existentryref->AppendEntry(entryref);
		}
		else {
			entrymap[newentrystring] = entryref;
		}

		//reinitialize inbound buffer.
		inboundbuffer.clear(); 
	}

	return;
}


CacheEntry* Cache::GetEntryList(const char* OriginalFilename)
{
	//retrieval could be performed concurrently.
	boost::lock_guard<boost::mutex> lock(cachemutex);
	string originalfile(OriginalFilename);
	CacheEntry* resultentries = entrymap[originalfile];

	return resultentries;
}


void Cache::AddEntry(const char* OriginalFilename, CacheEntry* EntryRef)
{
	string originalfile(OriginalFilename);

	//addtions could be performed concurrently.
	boost::lock_guard<boost::mutex> lock(cachemutex);

	entrymap[originalfile] = EntryRef;

	return;
}


void Cache::CloseCache(void) throw (CacheException)
{
	//open up the cache meta file.
	boost::filesystem::path metafilepath(metafile);
	ofstream metafileref;
	if(! exists(metafilepath))
		metafileref.open(metafilepath.file_string().c_str(), ios_base::out | ios_base::app);
	else
		metafileref.open(metafilepath.file_string().c_str(), ios_base::out | ios_base::trunc);
	if(! metafileref.is_open())
		throw CacheException(CacheException::HARDDRIVE_FAILURE);

	//save cache meta onto hard drive along with changed cache files.
	char* delimiter = ";";
	map<string, CacheEntry*>::iterator mapitem;
	for(mapitem=entrymap.begin(); mapitem != entrymap.end(); mapitem++)
	{
		CacheEntry* concreteentryref = (*mapitem).second;
		while(concreteentryref)
		{
			metafileref.write(concreteentryref->GetOriginalFilename(), strlen(concreteentryref->GetOriginalFilename()));
			metafileref.write(delimiter, strlen(delimiter));
			metafileref.write(concreteentryref->GetOriginalFileCreateTime(), strlen(concreteentryref->GetOriginalFileCreateTime()));
			metafileref.write(delimiter, strlen(delimiter));
			metafileref.write(concreteentryref->GetOriginalFileCompressionID(), strlen(concreteentryref->GetOriginalFileCompressionID()));
			metafileref.write(delimiter, strlen(delimiter));
			metafileref.write(concreteentryref->GetCachedFilename(), strlen(concreteentryref->GetCachedFilename()));
			metafileref.write(delimiter, strlen(delimiter));
			metafileref.write(concreteentryref->GetCachedFileCompressionID(), strlen(concreteentryref->GetCachedFileCompressionID()));
			metafileref.write("\n", strlen("\n"));

//			CacheEntry* tobedeletedentryref = concreteentryref;
			concreteentryref = concreteentryref->GetNextEntry();
//			delete tobedeletedentryref;
		}
	}

	//close cache meta file.
	metafileref.close();

	return;
}


bool Cache::ValidateEntry(string& EntryRawText)
{
	//an entry is formed as original_filename;original_file_create_time;original_file_compression_id;cached_filename;cached_file_compression_id(end of line - '\n')

	//format validation.
	int    semicolon         = 0;
	size_t semicolonposition = 0;
	while(1) {
		semicolonposition = EntryRawText.find(';', semicolonposition);
		if(semicolonposition != string::npos) {
			semicolonposition++;
			semicolon++;
		}
		else
			break;
	}
	if(semicolon != 4)
		return false;

	return true;
}


Cache::Cache(void)
{
	 return;
}
 
