//this file is part of eMule
//Copyright (C)2002 Merkur ( devs@emule-project.net / http://www.emule-project.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"
#include <io.h>
#include "emule.h"
#include "SharedFileList.h"
#include "KnownFileList.h"
#include "KnownFile.h"
#include "opcodes.h"
#include "Preferences.h"
#include "SafeFile.h"
#include "OtherFunctions.h"
#include "UpDownClient.h"
#include "DownloadQueue.h"
#include "emuledlg.h"
#include "TransferWnd.h"
#include "Log.h"
#include "packets.h"
#include "Neo/NeoOpCodes.h" // NEO: XC - [ExtendedComments] <-- Xanatos --
#include "Neo/EMBackup.h" // NEO: NB - [NeoBackup] <-- Xanatos --
// NEO: XCk - [ExtendedKnownComments] -- Xanatos -->
#include "clientlist.h"
#include "Kademlia/Kademlia/Kademlia.h"
#include "kademlia/kademlia/search.h"
#include "kademlia/kademlia/SearchManager.h"
#include "kademlia/kademlia/prefs.h"
// NEO: XCk END <-- Xanatos --

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


#define KNOWN_MET_FILENAME		_T("known.met")
#define CANCELLED_MET_FILENAME	_T("cancelled.met")

#define COMMENTS_MET_FILENAME	_T("Comments.met") // NEO: XC - [ExtendedComments] <-- Xanatos --

CKnownFileList::CKnownFileList()
{
	m_Files_map.InitHashTable(2063);
	m_mapCancelledFiles.InitHashTable(1031);
	accepted = 0;
	requested = 0;
	transferred = 0;
	m_nLastSaved = ::GetTickCount();
	m_bPartTrafficLoaded = false; // NEO: NPT - [NeoPartTraffic] <-- Xanatos --
	m_bCommentsLoaded = false; // NEO: XC - [ExtendedComments] <-- Xanatos --
	m_currFileNotes = 0; // NEO: XCk - [ExtendedKnownComments] <-- Xanatos --
	Init();
}

CKnownFileList::~CKnownFileList()
{
	Clear();
}

bool CKnownFileList::Init()
{
	// NEO: NB - [NeoBackup] -- Xanatos -->
	bool bRet = true;

retry: 
	if (!LoadKnownFiles())
		if (theApp.BackupEngine->RestoreFile(KnownMet)) {
			Clear();
			goto retry; // Not very nice but it works
		}else bRet = false;

retryC: 
	if (!LoadCancelledFiles())
		if (theApp.BackupEngine->RestoreFile(CancelledMet)) {
			m_mapCancelledFiles.RemoveAll();
			goto retryC; // Not very nice but it works
		}else bRet = false;

retryKP: 
	if (!LoadKnownPreferences())	// NEO: FCFG - [FileConfiguration]
		if (theApp.BackupEngine->RestoreFile(KnownPrefsMet)) {
			goto retryKP; // Not very nice but it works
		}else bRet = false;

	if (thePrefs.UsePartTraffic()){
retryPT: 
		if (!LoadPartTraffic())	// NEO: NPT - [NeoPartTraffic]
			if (theApp.BackupEngine->RestoreFile(TrafficMet)) {
				goto retryPT; // Not very nice but it works
			}else bRet = false;
	}

	if (thePrefs.UseSaveComments()){
retrySC: 
		if (!LoadComments()) // NEO: XC - [ExtendedComments]
			if (theApp.BackupEngine->RestoreFile(CommentsMet)) {
				goto retrySC; // Not very nice but it works
			}else bRet = false;
	}

	return bRet;
	// NEO: NB END <-- Xanatos --
}

bool CKnownFileList::LoadKnownFiles(){
	CString fullpath=thePrefs.GetConfigDir();
	fullpath.Append(KNOWN_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 ") KNOWN_MET_FILENAME _T(" file"));
			TCHAR szError[MAX_CFEXP_ERRORMSG];
			if (fexp.GetErrorMessage(szError, ARRSIZE(szError))){
				strError += _T(" - ");
				strError += szError;
			}
			LogError(LOG_STATUSBAR, _T("%s"), strError);
			theApp.BackupEngine->SetLastErrorState(EMB_ERROR_STATE_UNRECOVERABLE); // NEO: NB - [NeoBackup] <-- Xanatos --
			return false;
		}
		theApp.BackupEngine->SetLastErrorState(EMB_ERROR_STATE_RECOVERABLE); // NEO: NB - [NeoBackup] <-- Xanatos --
		return false;
	}
	setvbuf(file.m_pStream, NULL, _IOFBF, 16384);

	CKnownFile* pRecord = NULL;
	try {
		uint8 header = file.ReadUInt8();
		if (header != MET_HEADER && header != MET_HEADER_I64TAGS){
			file.Close();
			theApp.BackupEngine->SetLastErrorState(EMB_ERROR_STATE_RECOVERABLE); // NEO: NB - [NeoBackup] <-- Xanatos --
			return false;
		}
		AddDebugLogLine(false, _T("Known.met file version is %u (%s support 64bit tags)"), header, (header == MET_HEADER) ? _T("doesn't") : _T("does")); 

		UINT RecordsNumber = file.ReadUInt32();
		for (UINT i = 0; i < RecordsNumber; i++) {
			pRecord = new CKnownFile();
			if (!pRecord->LoadFromFile(&file)){
				TRACE(_T("*** Failed to load entry %u (name=%s  hash=%s  size=%I64u  parthashs=%u expected parthashs=%u) from known.met\n"), i, 
					pRecord->GetFileName(), md4str(pRecord->GetFileHash()), pRecord->GetFileSize(), pRecord->GetHashCount(), pRecord->GetED2KPartHashCount());
				delete pRecord;
				pRecord = NULL;
				continue;
			}
			SafeAddKFile(pRecord);
			pRecord = NULL;
		}
		file.Close();
	}
	catch(CFileException* error){
		if (error->m_cause == CFileException::endOfFile){
			LogError(LOG_STATUSBAR, GetResString(IDS_ERR_SERVERMET_BAD));
			theApp.BackupEngine->SetLastErrorState(EMB_ERROR_STATE_RECOVERABLE); // NEO: NB - [NeoBackup] <-- Xanatos --
		}else{
			TCHAR buffer[MAX_CFEXP_ERRORMSG];
			error->GetErrorMessage(buffer, ARRSIZE(buffer));
			LogError(LOG_STATUSBAR, GetResString(IDS_ERR_SERVERMET_UNKNOWN),buffer);
			theApp.BackupEngine->SetLastErrorState(EMB_ERROR_STATE_UNRECOVERABLE); // NEO: NB - [NeoBackup] <-- Xanatos --
		}
		error->Delete();
		delete pRecord;
		return false;
	}

	theApp.BackupEngine->SetLastErrorState(EMB_ERROR_STATE_CORRECT); // NEO: NB - [NeoBackup] <-- Xanatos --
	return true;
}

bool CKnownFileList::LoadCancelledFiles(){
	if (!thePrefs.IsRememberingCancelledFiles())
		return true;
	CString fullpath=thePrefs.GetConfigDir();
	fullpath.Append(CANCELLED_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 ") CANCELLED_MET_FILENAME _T(" file"));
			TCHAR szError[MAX_CFEXP_ERRORMSG];
			if (fexp.GetErrorMessage(szError, ARRSIZE(szError))){
				strError += _T(" - ");
				strError += szError;
			}
			LogError(LOG_STATUSBAR, _T("%s"), strError);
			theApp.BackupEngine->SetLastErrorState(EMB_ERROR_STATE_UNRECOVERABLE); // NEO: NB - [NeoBackup] <-- Xanatos --
			return false;
		}
		theApp.BackupEngine->SetLastErrorState(EMB_ERROR_STATE_RECOVERABLE); // NEO: NB - [NeoBackup] <-- Xanatos --
		return false;
	}
	setvbuf(file.m_pStream, NULL, _IOFBF, 16384);
	uchar ucHash[16];
	try {
		uint8 header = file.ReadUInt8();
		if (header != MET_HEADER){
			file.Close();
			theApp.BackupEngine->SetLastErrorState(EMB_ERROR_STATE_RECOVERABLE); // NEO: NB - [NeoBackup] <-- Xanatos --
			return false;
		}

		UINT RecordsNumber = file.ReadUInt32();
		for (UINT i = 0; i < RecordsNumber; i++) {
			file.ReadHash16(ucHash);
			uint8 nCount = file.ReadUInt8();
			// for compatibility with future versions which may add more data than just the hash
			for (UINT j = 0; j < nCount; j++) {
				CTag tag(&file, false);
			}
			m_mapCancelledFiles.SetAt(CSKey(ucHash), 1);
		}
		file.Close();
	}
	catch(CFileException* error){
		if (error->m_cause == CFileException::endOfFile){
			LogError(LOG_STATUSBAR, GetResString(IDS_ERR_CONFIGCORRUPT), CANCELLED_MET_FILENAME);
			theApp.BackupEngine->SetLastErrorState(EMB_ERROR_STATE_RECOVERABLE); // NEO: NB - [NeoBackup] <-- Xanatos --
		}else{
			TCHAR buffer[MAX_CFEXP_ERRORMSG];
			error->GetErrorMessage(buffer, ARRSIZE(buffer));
			LogError(LOG_STATUSBAR, GetResString(IDS_ERR_FAILEDTOLOAD), CANCELLED_MET_FILENAME, buffer);
			theApp.BackupEngine->SetLastErrorState(EMB_ERROR_STATE_UNRECOVERABLE); // NEO: NB - [NeoBackup] <-- Xanatos --
		}
		error->Delete();
		return false;
	}

	theApp.BackupEngine->SetLastErrorState(EMB_ERROR_STATE_CORRECT); // NEO: NB - [NeoBackup] <-- Xanatos --
	return true;
}

void CKnownFileList::Save()
{
	if (thePrefs.GetLogFileSaving())
		AddDebugLogLine(false, _T("Saving known files list file \"%s\""), KNOWN_MET_FILENAME);
	m_nLastSaved = ::GetTickCount(); 
	CString fullpath=thePrefs.GetConfigDir();
	fullpath += KNOWN_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 ") KNOWN_MET_FILENAME _T(" file"));
		TCHAR szError[MAX_CFEXP_ERRORMSG];
		if (fexp.GetErrorMessage(szError, ARRSIZE(szError))){
			strError += _T(" - ");
			strError += szError;
		}
		LogError(LOG_STATUSBAR, _T("%s"), strError);
	}
	else{
		setvbuf(file.m_pStream, NULL, _IOFBF, 16384);

		try{
			file.WriteUInt8(0); // we will write the version tag later depending if any large files are on the list
			UINT nRecordsNumber = 0;
			bool bContainsAnyLargeFiles = false;
			file.WriteUInt32(nRecordsNumber);
			POSITION pos = m_Files_map.GetStartPosition();
			while( pos != NULL )
			{
				CKnownFile* pFile;
				CCKey key;
				m_Files_map.GetNextAssoc( pos, key, pFile );
				// NEO: MKN - [MergeKnown] -- Xanatos -->
				if (theApp.sharedfiles->IsFilePtrInList(pFile))
					pFile->SetLastSeen();
				// NEO: MKN END <-- Xanatos --
				if (!thePrefs.IsRememberingDownloadedFiles() && !theApp.sharedfiles->IsFilePtrInList(pFile)){
					continue;
				}
				else{
					pFile->WriteToFile(&file);
					nRecordsNumber++;
					if (pFile->IsLargeFile())
						bContainsAnyLargeFiles = true;
				}
			}
			file.SeekToBegin();
			file.WriteUInt8(bContainsAnyLargeFiles ? MET_HEADER_I64TAGS : MET_HEADER);
			file.WriteUInt32(nRecordsNumber);

			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 ") KNOWN_MET_FILENAME _T(" file"));
			TCHAR szError[MAX_CFEXP_ERRORMSG];
			if (error->GetErrorMessage(szError, ARRSIZE(szError))){
				strError += _T(" - ");
				strError += szError;
			}
			LogError(LOG_STATUSBAR, _T("%s"), strError);
			error->Delete();
		}
	}


	if (thePrefs.GetLogFileSaving())
		AddDebugLogLine(false, _T("Saving known files list file \"%s\""), CANCELLED_MET_FILENAME);
 	fullpath=thePrefs.GetConfigDir();
	fullpath += CANCELLED_MET_FILENAME;
	if (!file.Open(fullpath, CFile::modeWrite|CFile::modeCreate|CFile::typeBinary|CFile::shareDenyWrite, &fexp)){
		CString strError(_T("Failed to save ") CANCELLED_MET_FILENAME _T(" file"));
		TCHAR szError[MAX_CFEXP_ERRORMSG];
		if (fexp.GetErrorMessage(szError, ARRSIZE(szError))){
			strError += _T(" - ");
			strError += szError;
		}
		LogError(LOG_STATUSBAR, _T("%s"), strError);
	}
	else{
		setvbuf(file.m_pStream, NULL, _IOFBF, 16384);

		try{
			file.WriteUInt8(MET_HEADER);
			if (!thePrefs.IsRememberingCancelledFiles()){
				file.WriteUInt32(0);
			}
			else{
				UINT nRecordsNumber = m_mapCancelledFiles.GetCount();
				file.WriteUInt32(nRecordsNumber);
				POSITION pos = m_mapCancelledFiles.GetStartPosition();
				while( pos != NULL )
				{
					int dwDummy;
					CSKey key;
					m_mapCancelledFiles.GetNextAssoc( pos, key, dwDummy );
					file.WriteHash16(key.m_key);
					file.WriteUInt8(0);
				}
			}

			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 ") CANCELLED_MET_FILENAME _T(" file"));
			TCHAR szError[MAX_CFEXP_ERRORMSG];
			if (error->GetErrorMessage(szError, ARRSIZE(szError))){
				strError += _T(" - ");
				strError += szError;
			}
			LogError(LOG_STATUSBAR, _T("%s"), strError);
			error->Delete();
		}
	}

	// NEO: NPT - [NeoPartTraffic] -- Xanatos -->
	if (thePrefs.UsePartTraffic() || m_bPartTrafficLoaded)
		SavePartTraffic();
	// NEO: NPT END <-- Xanatos --

	// NEO: XC - [ExtendedComments] -- Xanatos -->
	if(thePrefs.UseSaveComments() || m_bCommentsLoaded)
		SaveComments();
	// NEO: XC END <-- Xanatos --

	SaveKnownPreferences(); // NEO: FCFG - [FileConfiguration] <-- Xanatos --
}

void CKnownFileList::Clear()
{
	POSITION pos = m_Files_map.GetStartPosition();
	while( pos != NULL )
	{
		CKnownFile* pFile;
		CCKey key;
		m_Files_map.GetNextAssoc( pos, key, pFile );
	    delete pFile;
	}
	m_Files_map.RemoveAll();
}

void CKnownFileList::Process()
{
	// NEO: XCk - [ExtendedKnownComments] -- Xanatos -->
	if(thePrefs.UseKnownComments())
		Publish();
	// NEO: XCk END <-- Xanatos --

	if (::GetTickCount() - m_nLastSaved > MIN2MS(11))
		Save();
}

// NEO: XCk - [ExtendedKnownComments] -- Xanatos -->
void CKnownFileList::Publish()
{
	// Variables to save cpu.
	UINT tNow = time(NULL);
	bool isFirewalled = theApp.IsFirewalled();

	if( Kademlia::CKademlia::IsConnected() && ( !isFirewalled || ( isFirewalled && theApp.clientlist->GetBuddyStatus() == Connected)) && GetCount() && Kademlia::CKademlia::GetPublish())
	{ 
		if( Kademlia::CKademlia::GetTotalStoreNotes() < KADEMLIATOTALSTORENOTES)
		{
			if(tNow >= theApp.sharedfiles->GetLastPublishKadNotes())
			{
				if(m_currFileNotes >= GetCount())// NEO: FIX <-- Xanatos --
					m_currFileNotes = 0;
				CKnownFile* pCurKnownFile = GetFileByIndex(m_currFileNotes);
				if(pCurKnownFile
				&& !theApp.sharedfiles->IsFilePtrInList(pCurKnownFile)) // file whitch are shared are published by the normal routine
				{
					if(pCurKnownFile->PublishNotes())
					{
						if(Kademlia::CSearchManager::PrepareLookup(Kademlia::CSearch::STORENOTES, true, Kademlia::CUInt128(pCurKnownFile->GetFileHash()))==NULL)
							pCurKnownFile->SetLastPublishTimeKadNotes(0);
					}	
				}
				m_currFileNotes++;

				// even if we did not publish a source, reset the timer so that this list is processed
				// only every KADEMLIAPUBLISHTIME seconds.
				theApp.sharedfiles->SetLastPublishKadNotes(KADEMLIAPUBLISHTIME+tNow); // we cound use a separate counter but // X?
			}
		}
	}
}
CKnownFile* CKnownFileList::GetFileByIndex(int index){
	int count=0;
	CKnownFile* cur_file;
	CCKey bufKey;

	for (POSITION pos = m_Files_map.GetStartPosition();pos != 0;){
		m_Files_map.GetNextAssoc(pos,bufKey,cur_file);
		if (index==count)
			return cur_file;
		count++;
	}
	return 0;
}
// NEO: XCk END <-- Xanatos --

bool CKnownFileList::SafeAddKFile(CKnownFile* toadd)
{
	CCKey key(toadd->GetFileHash());
	CKnownFile* pFileInMap;
	if (m_Files_map.Lookup(key, pFileInMap))
	{
		TRACE(_T("%hs: File already in known file list: %s \"%s\" \"%s\"\n"), __FUNCTION__, md4str(pFileInMap->GetFileHash()), pFileInMap->GetFileName(), pFileInMap->GetFilePath());
		TRACE(_T("%hs: Old entry replaced with:         %s \"%s\" \"%s\"\n"), __FUNCTION__, md4str(toadd->GetFileHash()), toadd->GetFileName(), toadd->GetFilePath());
#if 1
		// if we hash files which are already in known file list and add them later (when the hashing thread is finished),
		// we can not delete any already available entry from known files list. that entry can already be used by the
		// shared file list -> crash.

		m_Files_map.RemoveKey(CCKey(pFileInMap->GetFileHash()));
		//This can happen in a couple situations..
		//File was renamed outside of eMule.. 
		//A user decided to redownload a file he has downloaded and unshared..
		//RemovingKeyWords I believe is not thread safe if I'm looking at this right.
		//Not sure of a good solution yet..
		if (theApp.sharedfiles && theApp.sharedfiles->IsFilePtrInList(pFileInMap)) // NEO: FIX <-- Xanatos --
		{
			theApp.sharedfiles->RemoveFile(pFileInMap);
			ASSERT( !theApp.sharedfiles->IsFilePtrInList(pFileInMap) );
		}
		//Double check to make sure this is the same file as it's possible that a two files have the same hash.
		//Maybe in the furture we can change the client to not just use Hash as a key throughout the entire client..
		ASSERT( toadd->GetFileSize() == pFileInMap->GetFileSize() );
		ASSERT( toadd != pFileInMap );
		if (toadd->GetFileSize() == pFileInMap->GetFileSize())
			toadd->statistic.MergeFileStats(&pFileInMap->statistic);

		ASSERT( theApp.sharedfiles==NULL || !theApp.sharedfiles->IsFilePtrInList(pFileInMap) );
		ASSERT( theApp.downloadqueue==NULL || !theApp.downloadqueue->IsPartFile(pFileInMap) );

		// Quick fix: If we downloaded already downloaded files again and if those files all had the same file names
		// and were renamed during file completion, we have a pending ptr in transfer window.
		if (theApp.emuledlg && theApp.emuledlg->transferwnd && theApp.emuledlg->transferwnd->downloadlistctrl.m_hWnd)
			theApp.emuledlg->transferwnd->downloadlistctrl.RemoveFile((CPartFile*)pFileInMap);

		delete pFileInMap;

#else
		// if the new entry is already in list, update the stats and return false, but do not delete the entry which is
		// alreay in known file list!
		ASSERT( toadd->GetFileSize() == pFileInMap->GetFileSize() );
		ASSERT( toadd != pFileInMap );
		if (toadd->GetFileSize() == pFileInMap->GetFileSize() && toadd != pFileInMap)
		{
			pFileInMap->statistic.MergeFileStats(&toadd->statistic);
			pFileInMap->SetFileName(toadd->GetFileName(), false);
			pFileInMap->SetPath(toadd->GetPath());
			pFileInMap->SetFilePath(toadd->GetFilePath());
			pFileInMap->date = toadd->date;
		}
		ASSERT( !theApp.sharedfiles->IsFilePtrInList(pFileInMap) );
		ASSERT( theApp.sharedfiles->IsFilePtrInList(toadd) );
		return false;
#endif
	}
	m_Files_map.SetAt(key, toadd);
	return true;
}

CKnownFile* CKnownFileList::FindKnownFile(LPCTSTR filename, uint32 date, uint64 size) const
{
	POSITION pos = m_Files_map.GetStartPosition();
	while (pos != NULL)
	{
		CKnownFile* cur_file;
		CCKey key;
		m_Files_map.GetNextAssoc(pos, key, cur_file);
		if (cur_file->GetUtcFileDate() == date && cur_file->GetFileSize() == size && !_tcscmp(filename, cur_file->GetFileName(true))) // NEO: PP - [PasswordProtection] <-- Xanatos --
			return cur_file;
	}
	return NULL;
}

CKnownFile* CKnownFileList::FindKnownFileByPath(const CString& sFilePath) const
{
	POSITION pos = m_Files_map.GetStartPosition();
	while (pos != NULL)
	{
		CKnownFile* cur_file;
		CCKey key;
		m_Files_map.GetNextAssoc(pos, key, cur_file);
		if (!cur_file->GetFilePath().CompareNoCase(sFilePath))
			return cur_file;
	}
	return NULL;
}

CKnownFile* CKnownFileList::FindKnownFileByID(const uchar* hash) const
{
	if (hash)
	{
		CKnownFile* found_file;
		CCKey key(hash);
		if (m_Files_map.Lookup(key, found_file))
			return found_file;
	}
	return NULL;
}

bool CKnownFileList::IsKnownFile(const CKnownFile* file) const
{
	if (file)
		return FindKnownFileByID(file->GetFileHash()) != NULL;
	return false;
}

bool CKnownFileList::IsFilePtrInList(const CKnownFile* file) const
{
	if (file)
	{
		POSITION pos = m_Files_map.GetStartPosition();
		while (pos)
		{
			CCKey key;
			CKnownFile* cur_file;
			m_Files_map.GetNextAssoc(pos, key, cur_file);
			if (file == cur_file)
				return true;
		}
	}
	return false;
}

void CKnownFileList::AddCancelledFileID(const uchar* hash){
	if (thePrefs.IsRememberingCancelledFiles()){
		m_mapCancelledFiles.SetAt(CSKey(hash), 1);	
	}
}

bool CKnownFileList::IsCancelledFileByID(const uchar* hash) const
{
	if (thePrefs.IsRememberingCancelledFiles()){
		int dwDummy;
		if (m_mapCancelledFiles.Lookup(CSKey(hash), dwDummy)){
			return true;
		}
	}
	return false;
}

void CKnownFileList::CopyKnownFileMap(CMap<CCKey,const CCKey&,CKnownFile*,CKnownFile*> &Files_Map)
{
	if (!m_Files_map.IsEmpty())
	{
		POSITION pos = m_Files_map.GetStartPosition();
		while (pos)
		{
			CCKey key;
			CKnownFile* cur_file;
			m_Files_map.GetNextAssoc(pos, key, cur_file);
			Files_Map.SetAt(key, cur_file);
		}
	}
}

// NEO: AKF - [AllKnownFiles] -- Xanatos -->
bool CKnownFileList::RemoveFile(CKnownFile* toRemove)
{
	BOOL ret = m_Files_map.RemoveKey(CCKey(toRemove->GetFileHash()));
	if(ret)
		delete toRemove;
	return I2B(ret);
}
// NEO: AKF END <-- Xanatos --

#ifdef A4AF_CATS // NEO: MAC - [MorphA4AFCategories] -- Xanatos -->
//Xman [MoNKi: -Check already downloaded files-]
// returns:
//		1 if a file was found
//		2 if more than 1 file found or only the name is equal.
//		0 if no file found
//		3 if cancelled file
int CKnownFileList::CheckAlreadyDownloadedFile(const uchar* hash, CString filename, CArray<CKnownFile*,CKnownFile*> *files)
{
	files->RemoveAll();
	if ( hash != NULL )
	{
		CKnownFile* curFile = FindKnownFileByID(hash);
		if ( curFile && !curFile->IsPartFile() )	
		{
			files->Add(curFile);
			return 1;
		}
		else if(curFile==NULL && IsCancelledFileByID(hash) )
		{
				return 3; 
		}
		else
			return 0;
	}
	else if ( !filename.IsEmpty() )
	{
		POSITION pos = m_Files_map.GetStartPosition();
		while ( pos )
		{
			CCKey key;
			CKnownFile* curFile;
			m_Files_map.GetNextAssoc(pos, key, curFile);
			if ( filename == curFile->GetFileName() && !curFile->IsPartFile() )
				files->Add(curFile);
		}
		if ( files->IsEmpty() )
			return 0;
		else
			return 2;
	}
	return 0;
}

//Returns:
//	true if you can download it
bool CKnownFileList::CheckAlreadyDownloadedFileQuestion(const uchar* hash, CString filename){
	CArray<CKnownFile *,CKnownFile*> filesFound;
	int ret;

	if(theApp.downloadqueue->IsFileExisting(hash)){
		return false;
	}

	ret=CheckAlreadyDownloadedFile(hash, _T(""), &filesFound);
	if(ret==0)
		ret=CheckAlreadyDownloadedFile(NULL,filename, &filesFound);

	if(ret!=0)
	{
		//CKnownFile* curFile=NULL;
		CString msg;
		if(ret==1){
			msg = GetResString(IDS_X_DOWNHISTORY_CHECK1);
		}
		else if(ret==3)
		{
			msg= GetResString(IDS_X_DOWNHISTORY_CHECK4);
		}
		else {
			msg.Format(GetResString(IDS_X_DOWNHISTORY_CHECK2), filesFound.GetCount(), filename);
		}

		for(int i=0;i<filesFound.GetCount();i++){
			CKnownFile *cur_file = filesFound.GetAt(i);
			CString sData;

			msg+=cur_file->GetFileName() + _T("\n");
			sData.Format(GetResString(IDS_DL_SIZE) + _T(": %I64u, ") + GetResString(IDS_FILEID) + _T(": %s"), cur_file->GetFileSize(), EncodeBase16(cur_file->GetFileHash(),16));
			msg+=sData;
			if(!cur_file->GetFileComment().IsEmpty()) //Add comment
				msg+=_T("\n") + GetResString(IDS_COMMENT) + _T(": \"") + cur_file->GetFileComment() + _T("\"");
			msg+="\n\n";
		}
		msg += GetResString(IDS_X_DOWNHISTORY_CHECK3);
		if(MessageBox(NULL, msg, GetResString(IDS_X_DOWNHISTORY),MB_YESNO|MB_ICONQUESTION)==IDYES)
			return true;
		else
			return false;
	}
	else
	{
		return true;
	}
}	
#endif // A4AF_CATS // NEO: MAC END <-- Xanatos --

// NEO: NSC - [NeoSharedCategories] -- Xanatos -->
void CKnownFileList::ResetCatParts(int cat)
{
	POSITION pos = m_Files_map.GetStartPosition();
	while( pos != NULL )
	{
		CKnownFile* pFile;
		CCKey key;
		m_Files_map.GetNextAssoc( pos, key, pFile );

		if (pFile->GetCategory()==cat)
			pFile->SetCategory(0);
		else if (pFile->GetCategory() > cat)
			pFile->SetCategory(pFile->GetCategory() - 1);
	}
}

void CKnownFileList::MoveCat(int from, int to)
{
	if (from < to)
		--to;

	POSITION pos = m_Files_map.GetStartPosition();
	while( pos != NULL )
	{
		CKnownFile* pFile;
		CCKey key;
		m_Files_map.GetNextAssoc( pos, key, pFile );

		if (!pFile)
			continue;

		int mycat = pFile->GetCategory();
		if ((mycat>=min(from,to) && mycat<=max(from,to)))
		{
			//if ((from<to && (mycat<from || mycat>to)) || (from>to && (mycat>from || mycat<to)) )	continue; //not affected

			if (mycat == from)
				pFile->SetCategory(to);
			else{
				if (from < to)
					pFile->SetCategory(mycat - 1);
				else
					pFile->SetCategory(mycat + 1);
			}
		}
	}
}
// NEO: NSC END <-- Xanatos --

// NEO: XC - [ExtendedComments] -- Xanatos -->
bool CKnownFileList::LoadComments()
{
	m_bCommentsLoaded = true;

	CString fullpath=thePrefs.GetConfigDir();
	fullpath.Append(COMMENTS_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 ") COMMENTS_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);
			theApp.BackupEngine->SetLastErrorState(EMB_ERROR_STATE_UNRECOVERABLE); // NEO: NB - [NeoBackup]
			return false;
		}
		theApp.BackupEngine->SetLastErrorState(EMB_ERROR_STATE_RECOVERABLE); // NEO: NB - [NeoBackup]
		return false;
	}
	setvbuf(file.m_pStream, NULL, _IOFBF, 16384);

	try {
		uint8 version = file.ReadUInt8();
		if (version != COMMENTSFILE_VERSION /*version < COMMENTSFILE_VERSION_OLD || version > COMMENTSFILE_VERSION*/){
			ModLogError(GetResString(IDS_X_ERR_COMMENTSMET_UNKNOWN_VERSION));
			file.Close();
			theApp.BackupEngine->SetLastErrorState(EMB_ERROR_STATE_RECOVERABLE); // NEO: NB - [NeoBackup]
			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){
				if(!cuf_file->LoadComments(&file)){
					ModLogError(GetResString(IDS_X_ERR_COMMENTSMET_ENTRY_CORRUPT), cuf_file->GetFileName());
				}
			}else{
				ModLogError(GetResString(IDS_X_ERR_COMMENTSASYNCHRONIZED), md4str(cur_hash)); 
				ClearCommentsEntry(&file);
			}
		}
		file.Close();
	}
	catch(CFileException* error){
		if (error->m_cause == CFileException::endOfFile){
			ModLogError(LOG_STATUSBAR, GetResString(IDS_X_ERR_COMMENTSMET_BAD));
			theApp.BackupEngine->SetLastErrorState(EMB_ERROR_STATE_RECOVERABLE); // NEO: NB - [NeoBackup]
		}else{
			TCHAR buffer[MAX_CFEXP_ERRORMSG];
			error->GetErrorMessage(buffer,MAX_CFEXP_ERRORMSG);
			ModLogError(LOG_STATUSBAR, GetResString(IDS_X_ERR_COMMENTSMET_UNKNOWN),buffer);
			theApp.BackupEngine->SetLastErrorState(EMB_ERROR_STATE_UNRECOVERABLE); // NEO: NB - [NeoBackup]
		}
		error->Delete();
		return false;
	}

	theApp.BackupEngine->SetLastErrorState(EMB_ERROR_STATE_CORRECT); // NEO: NB - [NeoBackup]
	return true;
}

bool CKnownFileList::ClearCommentsEntry(CFileDataIO* file){
	uint16 count = file->ReadUInt16();

	ULONG str = 0;

	for(uint16 i = 0; i < count; i++)
	{
		file->Seek(16,CFile::current); // UH

		str=file->ReadUInt16();
		file->Seek(str,CFile::current); // UN
		str=file->ReadUInt16();
		file->Seek(str,CFile::current); // FN
		
		file->Seek(1,CFile::current); // R
		str=file->ReadUInt16();
		file->Seek(str,CFile::current); // C
	}

	return true;
}

bool CKnownFileList::SaveComments()
{
	if (thePrefs.GetLogFileSaving())
		AddDebugLogLine(false,_T("Saving Comments files list file \"%s\""), COMMENTS_MET_FILENAME);
	CString fullpath=thePrefs.GetConfigDir();
	fullpath += COMMENTS_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 ") COMMENTS_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(COMMENTSFILE_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->HasComments()){

				file.WriteHash16(pFile->GetFileHash());
				pFile->SaveComments(&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 ") COMMENTS_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;
}
// NEO: XC END <-- Xanatos --
