y//this file is part of NeoMule
//Copyright (C)2006-2007 David Xanatos ( XanatosDavid@googlemail.com / http://neomule.sourceforge.net )
//
//This program is free software; you can redistribute it and/or
//modify it under the terms of the GNU General Public License
//as published by the Free Software Foundation; either
//version 2 of the License, or (at your option) any later version.
//
//This program is distributed in the hope that it will be useful,
//but WITHOUT ANY WARRANTY; without even the implied warranty of
//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//GNU General Public License for more details.
//
//You should have received a copy of the GNU General Public License
//along with this program; if not, write to the Free Software
//Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

#include "stdafx.h"
#ifdef _DEBUG
#include "DebugHelpers.h"
#endif

#include <io.h>
#include "FilePreferences.h"
#include "Preferences.h"
//#include "NeoPreferences.h"
#include "PartFile.h"
#include "SafeFile.h"
#include "Packets.h"
#include "emule.h"
#include "Log.h"
#include "emuledlg.h"
//#include "Defaults.h"
//#include "NeoOpCodes.h"
#include "Functions.h"
#include "KnownFileList.h"
#include "Neo/Ini2.h" // NEO: INI - [PlusIniClass] <-- Xanatos --
//#include "Ini2.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

// NEO: FCFG - [FileConfiguration] -- Xanatos -->

#define KNOWNPREFS_MET_FILENAME	_T("KnownPrefs.met")

////////////////////////////////////////////////////////////////////////////////////////
// CPartFile
//

bool CPartFile::SaveNeoFile()
{
	CString strNeoFile(m_fullname);
	strNeoFile += PARTNEO_EXT;

	CString strTmpFile(strNeoFile);
	strTmpFile += PARTNEO_TMP_EXT;

	// save tweak to part.neo file
	CSafeBufferedFile file;
	CFileException fexp;
	if (!file.Open(strTmpFile, CFile::modeWrite|CFile::modeCreate|CFile::typeBinary|CFile::shareDenyWrite, &fexp)){
		CString strError;
		strError.Format(GetResString(IDS_X_ERR_SAVENEO), strNeoFile, GetFileName());
		TCHAR szError[MAX_CFEXP_ERRORMSG];
		if (fexp.GetErrorMessage(szError, ARRSIZE(szError))){
			strError += _T(" - ");
			strError += szError;
		}
		ModLogError(LOG_STATUSBAR, _T("%s"), strError);
		return false;
	}
	setvbuf(file.m_pStream, NULL, _IOFBF, 16384);

	try{
		//version
		file.WriteUInt8(NEOFILE_VERSION);

		SaveNeoFile(&file);

		if (thePrefs.GetCommitFiles() >= 2 || (thePrefs.GetCommitFiles() >= 1 && !theApp.emuledlg->IsRunning())){
			file.Flush(); // flush file stream buffers to disk buffers
			if (_commit(_fileno(file.m_pStream)) != 0) // commit disk buffers to disk
				AfxThrowFileException(CFileException::hardIO, GetLastError(), file.GetFileName());
		}
		file.Close();
	}
	catch(CFileException* error){
		CString strError;
		strError.Format(GetResString(IDS_X_ERR_SAVENEO), strNeoFile, GetFileName());
		TCHAR szError[MAX_CFEXP_ERRORMSG];
		if (error->GetErrorMessage(szError, ARRSIZE(szError))){
			strError += _T(" - ");
			strError += szError;
		}
		ModLogError(LOG_STATUSBAR, _T("%s"), strError);
		error->Delete();

		// remove the partially written or otherwise damaged temporary file
		file.Abort(); // need to close the file before removing it. call 'Abort' instead of 'Close', just to avoid an ASSERT.
		(void)_tremove(strTmpFile);
		return false;
	}

	// after successfully writing the temporary part.neo file...
	if (_tremove(strNeoFile) != 0 && errno != ENOENT){
		if (thePrefs.GetVerbose())
			DebugLogError(_T("Failed to remove \"%s\" - %s"), strNeoFile, _tcserror(errno));
	}

	if (_trename(strTmpFile, strNeoFile) != 0){
		int iErrno = errno;
		if (thePrefs.GetVerbose())
			DebugLogError(_T("Failed to move temporary part.met.neo file \"%s\" to \"%s\" - %s"), strTmpFile, strNeoFile, _tcserror(iErrno));

		CString strError;
		strError.Format(GetResString(IDS_X_ERR_SAVENEO), strNeoFile, GetFileName());
		strError += _T(" - ");
		strError += strerror(iErrno);
		ModLogError(_T("%s"), strError);
		return false;
	}

	// create a backup of the successfully written part.met file
	CString BAKName(strNeoFile);
	BAKName.Append(PARTNEO_BAK_EXT);
	if (!::CopyFile(strNeoFile, BAKName, FALSE)){
		if (thePrefs.GetVerbose())
			DebugLogError(_T("Failed to create backup of %s (%s) - %s"), strNeoFile, GetFileName(), GetErrorMessage(GetLastError()));
	}

	return true;
}

bool CPartFile::SaveNeoFile(CFileDataIO* file)
{
	
	/*
	* Save All Neo File settings here
	*/
	if(PartPrefs->IsFilePrefs() && !PartPrefs->IsEmpty()){
		file->WriteUInt8(PARTPREFSFILE_VERSION);

		ULONGLONG pos = file->GetPosition();
		file->WriteUInt16(0);

		PartPrefs->Save(file);
		
		file->Seek(pos, CFile::begin);
		ASSERT((file->GetLength() - (file->GetPosition()+2)) < 0xFFFF);
		file->WriteUInt16((uint16)(file->GetLength() - (file->GetPosition()+2)));
		file->Seek(0, CFile::end);
	}
	if(KnownPrefs->IsFilePrefs() && !KnownPrefs->IsEmpty()){
		file->WriteUInt8(KNOWNPREFSFILE_VERSION);

		ULONGLONG pos = file->GetPosition();
		file->WriteUInt16(0);

		KnownPrefs->Save(file);
		
		file->Seek(pos, CFile::begin);
		ASSERT((file->GetLength() - (file->GetPosition()+2)) < 0xFFFF);
		file->WriteUInt16((uint16)(file->GetLength() - (file->GetPosition()+2)));
		file->Seek(0, CFile::end);
	}



	return true;
}

bool CPartFile::LoadNeoFile()
{
	CString strNeoFile(m_fullname);
	strNeoFile += PARTNEO_EXT;

	uint8 version;
	
	// readfile tweaks form part.neo file
	CSafeBufferedFile file;
	CFileException fexpMet;
	if (!file.Open(strNeoFile, CFile::modeRead|CFile::osSequentialScan|CFile::typeBinary|CFile::shareDenyWrite, &fexpMet)){
		if (fexpMet.m_cause != CFileException::fileNotFound){
			CString strError;
			strError.Format(GetResString(IDS_X_ERR_OPENNEO), strNeoFile, _T(""));
			TCHAR szError[MAX_CFEXP_ERRORMSG];
			if (fexpMet.GetErrorMessage(szError, ARRSIZE(szError))){
				strError += _T(" - ");
				strError += szError;
			}
			ModLogError(LOG_STATUSBAR, _T("%s"), strError);
			return false;
		}
		return false;
	}
	setvbuf(file.m_pStream, NULL, _IOFBF, 16384);

	try{
		version = file.ReadUInt8();
		
		if (version > NEOFILE_VERSION || version <= NEOFILE_VERSION_OLD){
			ModLogError(LOG_STATUSBAR, GetResString(IDS_X_ERR_BADNEOVERSION), strNeoFile, GetFileName());
			file.Close();
			return false;
		}
		
		LoadNeoFile(&file);

		file.Close();
	}
	catch(CFileException* error){
		if (error->m_cause == CFileException::endOfFile){
			ModLogError(LOG_STATUSBAR, GetResString(IDS_X_ERR_NEOMEOCORRUPT), strNeoFile, GetFileName());
		}else{
			TCHAR buffer[MAX_CFEXP_ERRORMSG];
			error->GetErrorMessage(buffer,ARRSIZE(buffer));
			ModLogError(LOG_STATUSBAR, GetResString(IDS_X_ERR_FILEERROR), strNeoFile, GetFileName(), buffer);
		}
		error->Delete();
		return false;
	}
	catch(...){
		ModLogError(LOG_STATUSBAR, GetResString(IDS_ERR_METCORRUPT), strNeoFile, GetFileName());
		ASSERT(0);
		return false;
	}

	return true;
}

bool CPartFile::LoadNeoFile(CFileDataIO* file)
{
	/*
	* Load All Neo File settings here
	*/
	uint8 segment;
	UINT length;
	while (file->GetLength()-file->GetPosition())
	{
		segment = file->ReadUInt8();
		length = file->ReadUInt16();
		if(length == 0xFFFF) // just in case
			length = file->ReadUInt32();

		switch(segment)
		{
			case 0: // kill opcode in case we want append some other data below this list
				ASSERT(length == 0);
				return true;

			case PARTPREFSFILE_VERSION:{
				CPartPreferences* prevPartPrefs = PartPrefs; // can be cat prefs or global prefs, doesn't mater
				ASSERT(PartPrefs->IsFilePrefs() == false);
				PartPrefs = new CPartPreferencesEx(CFP_FILE);
				((CPartPreferencesEx*)PartPrefs)->PartFile = this;
				((CPartPreferencesEx*)PartPrefs)->PartPrefs = prevPartPrefs;
				PartPrefs->Load(file);
				break;
			}
			case KNOWNPREFSFILE_VERSION:{
				CKnownPreferences* prevKnownPrefs = KnownPrefs; // can be cat prefs or global prefs, doesn't mater
				ASSERT(KnownPrefs->IsFilePrefs() == false);
				KnownPrefs = new CKnownPreferencesEx(CFP_FILE);
				((CKnownPreferencesEx*)KnownPrefs)->KnownFile = this;
				((CKnownPreferencesEx*)KnownPrefs)->KnownPrefs = prevKnownPrefs;
				KnownPrefs->Load(file);
				break;
			}

			default:
				if(file->GetPosition() + length > file->GetLength())
					AfxThrowFileException(CFileException::endOfFile, 0, _T("MemoryFile"));
				DebugLog(_T("Unknown NEO File segment ID 0x%02x received"), segment);
				file->Seek(length, CFile::current);
		}
	}

	return true;
}



////////////////////////////////////////////////////////////////////////////////////////
// CKnownFileList
//

bool CKnownFileList::LoadKnownPreferences()
{
	CString fullpath=thePrefs.GetMuleDirectory(EMULE_CONFIGDIR);
	fullpath.Append(KNOWNPREFS_MET_FILENAME);
	CSafeBufferedFile file;
	CFileException fexp;
	if (!file.Open(fullpath,CFile::modeRead|CFile::osSequentialScan|CFile::typeBinary|CFile::shareDenyWrite, &fexp)){
		if (fexp.m_cause != CFileException::fileNotFound){
			CString strError(_T("Failed to load ") KNOWNPREFS_MET_FILENAME _T(" file"));
			TCHAR szError[MAX_CFEXP_ERRORMSG];
			if (fexp.GetErrorMessage(szError, ARRSIZE(szError))){
				strError += _T(" - ");
				strError += szError;
			}
			ModLogError(LOG_STATUSBAR, _T("%s"), strError);
			return false;
		}
		return false;
	}
	setvbuf(file.m_pStream, NULL, _IOFBF, 16384);

	try {
		uint8 version = file.ReadUInt8();
		if (version > KNOWNPREFSFILE_VERSION || version <= KNOWNPREFSFILE_VERSION_OLD){
			ModLogError(LOG_STATUSBAR, GetResString(IDS_X_ERR_KNOWNPREFSMET_UNKNOWN_VERSION));
			file.Close();
			return false;
		}
		
		UINT RecordsNumber = file.ReadUInt32();
		uchar cur_hash[16];
		CKnownFile* cuf_file=NULL;
		for (UINT i = 0; i < RecordsNumber; i++) {
			file.ReadHash16(cur_hash);
			if((cuf_file = FindKnownFileByID(cur_hash)) != NULL){
				CKnownPreferences* prevKnownPrefs = cuf_file->KnownPrefs; // can be cat prefs or global prefs, doesn't mater
				cuf_file->KnownPrefs = new CKnownPreferencesEx(CFP_FILE);
				((CKnownPreferencesEx*)cuf_file->KnownPrefs)->KnownFile = cuf_file;
				((CKnownPreferencesEx*)cuf_file->KnownPrefs)->KnownPrefs = prevKnownPrefs;
				if(!cuf_file->KnownPrefs->Load(&file)){
					ModLogError(GetResString(IDS_X_ERR_KNOWNPREFSMET_ENTRY_CORRUPT), cuf_file->GetFileName());
				}
			}else{
				ModLogError(GetResString(IDS_X_ERR_KNOWNPREFSASYNCHRONIZED), md4str(cur_hash)); 
				ClearPreferencesEntry(&file);
			}
		}
		file.Close();
	}
	catch(CFileException* error){
		if (error->m_cause == CFileException::endOfFile){
			ModLogError(LOG_STATUSBAR, GetResString(IDS_X_ERR_KNOWNPREFSMET_BAD));
		}else{
			TCHAR buffer[MAX_CFEXP_ERRORMSG];
			error->GetErrorMessage(buffer,MAX_CFEXP_ERRORMSG);
			ModLogError(LOG_STATUSBAR, GetResString(IDS_X_ERR_KNOWNPREFSMET_UNKNOWN),buffer);
		}
		error->Delete();
		return false;
	}

	return true;
}

bool CKnownFileList::ClearPreferencesEntry(CFileDataIO* file)
{
	UINT tagcount = file->ReadUInt32();
	for (UINT j = 0; j < tagcount; j++){
		CTag* newtag = new CTag(file, false);
        delete newtag;
	}
	return true;
}

bool CKnownFileList::SaveKnownPreferences()
{
	if (thePrefs.GetLogFileSaving())
		AddDebugLogLine(false,_T("Saving KnownPrefs files list file \"%s\""), KNOWNPREFS_MET_FILENAME);
	CString fullpath=thePrefs.GetMuleDirectory(EMULE_CONFIGDIR);
	fullpath += KNOWNPREFS_MET_FILENAME;
	CSafeBufferedFile file;
	CFileException fexp;
	if (!file.Open(fullpath, CFile::modeWrite|CFile::modeCreate|CFile::typeBinary|CFile::shareDenyWrite, &fexp)){
		CString strError(_T("Failed to save ") KNOWNPREFS_MET_FILENAME _T(" file"));
		TCHAR szError[MAX_CFEXP_ERRORMSG];
		if (fexp.GetErrorMessage(szError, ARRSIZE(szError))){
			strError += _T(" - ");
			strError += szError;
		}
		ModLogError(LOG_STATUSBAR, _T("%s"), strError);
		return false;
	}
	setvbuf(file.m_pStream, NULL, _IOFBF, 16384);

	try{
		file.WriteUInt8(KNOWNPREFSFILE_VERSION);

		uint32 uTagCount = 0;
		ULONG uTagCountFilePos = (ULONG)file.GetPosition();
		file.WriteUInt32(uTagCount);

		POSITION pos = m_Files_map.GetStartPosition();
		while( pos != NULL )
		{
			CKnownFile* pFile;
			CCKey key;
			m_Files_map.GetNextAssoc( pos, key, pFile );
			if(!pFile->KnownPrefs->IsEmpty()){
				file.WriteHash16(pFile->GetFileHash());
				pFile->KnownPrefs->Save(&file);
				uTagCount++;
			}
		}

		file.Seek(uTagCountFilePos, CFile::begin);
		file.WriteUInt32(uTagCount);
		file.Seek(0, CFile::end);

		if (thePrefs.GetCommitFiles() >= 2 || (thePrefs.GetCommitFiles() >= 1 && !theApp.emuledlg->IsRunning())){
			file.Flush(); // flush file stream buffers to disk buffers
			if (_commit(_fileno(file.m_pStream)) != 0) // commit disk buffers to disk
				AfxThrowFileException(CFileException::hardIO, GetLastError(), file.GetFileName());
		}
		file.Close();
	}
	catch(CFileException* error){
		CString strError(_T("Failed to save ") KNOWNPREFS_MET_FILENAME _T(" file"));
		TCHAR szError[MAX_CFEXP_ERRORMSG];
		if (error->GetErrorMessage(szError, ARRSIZE(szError))){
			strError += _T(" - ");
			strError += szError;
		}
		ModLogError(LOG_STATUSBAR, _T("%s"), strError);
		error->Delete();
	}

	return true;
}

////////////////////////////////////////////////////////////////////////////////////////
// CKnownPreferences
//

CKnownPreferences::CKnownPreferences(){

}

CKnownPreferences::~CKnownPreferences(){
	ClearTags();
}

void CKnownPreferences::Save(CFileDataIO* file)
{
	UINT uTagCount = 0;
	ULONG uTagCountFilePos = (ULONG)file->GetPosition();
	file->WriteUInt32(uTagCount);


	for (int j = 0; j < taglist.GetCount(); j++){
		if (taglist[j]->IsStr() || taglist[j]->IsInt()){
			taglist[j]->WriteNewEd2kTag(file);
			uTagCount++;
		}
	}

	file->Seek(uTagCountFilePos, CFile::begin);
	file->WriteUInt32(uTagCount);
	file->Seek(0, CFile::end);
}

void CKnownPreferences::ClearTags()
{
	for (int i = 0; i < taglist.GetSize(); i++)
		delete taglist[i];
	taglist.RemoveAll();
}

bool CKnownPreferences::Load(CFileDataIO* file)
{
	UINT tagcount = file->ReadUInt32();
	for (UINT j = 0; j < tagcount; j++){
		CTag* newtag = new CTag(file, false);
		switch (newtag->GetNameID()){


			default:{
				taglist.Add(newtag);
			}
		}
	}

	CheckTweaks();

	return true;
}

void CKnownPreferences::Save(CIni& ini)
{
}

bool CKnownPreferences::Load(CIni& ini)
{

	CheckTweaks();

	return true;
}

void CKnownPreferences::CheckTweaks(){
}

////////////////////////////////////////////////////////////////////////////////////////
// CPartPreferences
//

CPartPreferences::CPartPreferences(){

}

CPartPreferences::~CPartPreferences(){
	ClearTags();
}

void CPartPreferences::Save(CFileDataIO* file)
{
	UINT uTagCount = 0;
	ULONG uTagCountFilePos = (ULONG)file->GetPosition();
	file->WriteUInt32(uTagCount);


	for (int j = 0; j < taglist.GetCount(); j++){
		if (taglist[j]->IsStr() || taglist[j]->IsInt()){
			taglist[j]->WriteNewEd2kTag(file);
			uTagCount++;
		}
	}

	file->Seek(uTagCountFilePos, CFile::begin);
	file->WriteUInt32(uTagCount);
	file->Seek(0, CFile::end);
}

void CPartPreferences::ClearTags()
{
	for (int i = 0; i < taglist.GetSize(); i++)
		delete taglist[i];
	taglist.RemoveAll();
}

bool CPartPreferences::Load(CFileDataIO* file)
{
	UINT tagcount = file->ReadUInt32();
	for (UINT j = 0; j < tagcount; j++){
		CTag* newtag = new CTag(file, false);
		switch (newtag->GetNameID()){


			default:{
				taglist.Add(newtag);
			}
		}
	}

	CheckTweaks();

	return true;
}

void CPartPreferences::Save(CIni& ini)
{

}

bool CPartPreferences::Load(CIni& ini)
{
	CheckTweaks();

	return true;
}

void CPartPreferences::CheckTweaks(){
}

///////////////////////////////////////////////////////////////////////////////////////
// CKnownPreferencesEx 
//

CKnownPreferencesEx::CKnownPreferencesEx(EFilePrefsLevel Level){
	KnownFile = NULL;
	KnownPrefs = NULL;
	m_Level = Level;

	ResetTweaks();
}

void CKnownPreferencesEx::ResetTweaks(){
}

bool CKnownPreferencesEx::IsEmpty() const {
	return (
		);
}


///////////////////////////////////////////////////////////////////////////////////////
// CPartPreferencesEx 
//

CPartPreferencesEx::CPartPreferencesEx(EFilePrefsLevel Level){
	PartFile = NULL;
	PartPrefs = NULL;
	m_Level = Level;

	ResetTweaks();
}

void CPartPreferencesEx::ResetTweaks()
{
}

bool CPartPreferencesEx::IsEmpty() const {
	return (
		);
}




///////////////////////////////////////////////////////////////////////////////////////
// Helpers
//

int GetRightVal (int &mode, int def, int max, int val1, int val2, int val3)
{
	if(mode > FCFG_BASE)
		return FCFG_DEF;

	if(mode > max)
		mode = def;

	switch(mode){
		case 0:	return val1;
		case 1:	return val2;
		case 2:	return val3;
		default:
			ASSERT(0);
			return 0;
	}
}

CString EncodeFPValue(int Value)
{
	CString Text;
	switch(Value){
	case FCFG_STD:
	case FCFG_UNK:
	case FCFG_DEF:
		Text = FCFG_INI_DEF;
		break;
	case FCFG_GLB:
		Text = FCFG_INI_GLB;
		break;
	case FCFG_AUT:
		Text = FCFG_INI_AUT;
		break;
	default:
		Text.Format(_T("%d"),Value);
	} 
	return Text;
}

int DecodeFPValue(CString Text, int Default)
{
	Text.MakeLower();
	if(Text.Find(FCFG_INI_DEF) != -1)
		return FCFG_DEF;
	else if(Text.Find(FCFG_INI_GLB) != -1)
		return FCFG_GLB;
	else if(Text.Find(FCFG_INI_AUT) != -1)
		return FCFG_AUT;
	else if(Text == _T(""))
		return Default;
	else if(Text == _T("0"))
		return 0;

	int Value = _tstoi(Text);
	if(Value == 0) // means the value is invalid
		return Default;
	return Value;
}

void CheckFPValue(int val, int def, bool glb, bool cat)
{
	if(glb){ // the most special values are not valid for the base
		if(val != FCFG_AUT || def != FCFG_AUT) // the only valid one it AUT, and if it is, it is also the default
			val = def;
	} else {
		if(val != FCFG_DEF || val != FCFG_GLB || !(val == FCFG_AUT && def == FCFG_AUT)) // all the rest is only valid temporary in the prefs tree
			val = FCFG_DEF;
		else if(val == FCFG_GLB && !cat) // glb is not valid for category prefs
			val = FCFG_DEF;
	}
}

bool CheckModes(int &val, int &mod) // this one checks the validiti of limit values that depand on a setting mode
{
	if((val == FCFG_DEF) != (mod == FCFG_DEF)
	|| (val == FCFG_GLB) != (mod == FCFG_GLB)){
		val = FCFG_DEF;
		mod = FCFG_DEF;
		return false;
	}

	return true;
}
// NEO: FCFG END <-- Xanatos --