//this file is part of eMule
//Copyright (C)2002 Merkur ( merkur-@users.sourceforge.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 "sharedfilelist.h"
#include "knownfilelist.h"
#include "packets.h"
#include <time.h>

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


CSharedFileList::CSharedFileList(CPreferences* in_prefs,CServerConnect* in_server,CKnownFileList* in_filelist){
	app_prefs = in_prefs;
	server = in_server;
	filelist = in_filelist;
	output = 0;
	m_Files_map.InitHashTable(1024);
	FindSharedFiles();
}

CSharedFileList::~CSharedFileList(){
	while (!waitingforhash_list.IsEmpty()){
		UnknownFile_Struct* nextfile = waitingforhash_list.RemoveHead();
		delete[] nextfile->directory; 
		delete[] nextfile->name;
		delete nextfile;
	}
}

void CSharedFileList::FindSharedFiles(){
	if (!m_Files_map.IsEmpty()){
		CSingleLock sLock1(&list_mut,true); // list thread safe
		m_Files_map.RemoveAll();
		sLock1.Unlock();
		theApp.downloadqueue->AddPartFilesToShare(); // read partfiles
	}

	// khaos::kmod+ Fix: Shared files loaded multiple times.
	// TODO: Use a different list or array or something other which does not force use finally produce lowercased directory names
	CStringList l_sAdded;

	l_sAdded.AddHead( CString( _strlwr( app_prefs->GetIncomingDir() ) ) );
	AddFilesFromDirectory(l_sAdded.GetHead().GetBuffer());
	if (theApp.glob_prefs->GetCatCount()>1) {
		for (int ix=1;ix<theApp.glob_prefs->GetCatCount();ix++)
		{
			CString tempDir( _strlwr(theApp.glob_prefs->GetCatPath(ix)) );
			if( l_sAdded.Find( tempDir ) ==NULL ) {
				l_sAdded.AddHead( tempDir );
				AddFilesFromDirectory( tempDir.GetBuffer() );
			}
		}
	}

	for (POSITION pos = app_prefs->shareddir_list.GetHeadPosition();pos != 0;app_prefs->shareddir_list.GetNext(pos))
	{
		CString tempDir = app_prefs->shareddir_list.GetAt(pos).MakeLower();
		if( l_sAdded.Find( tempDir ) ==NULL ) {
			l_sAdded.AddHead( tempDir );
			AddFilesFromDirectory(tempDir.GetBuffer());
		}
	}
	// khaos::kmod-
	if (waitingforhash_list.IsEmpty())
		theApp.emuledlg->AddLogLine(false,GetResString(IDS_SHAREDFOUND), m_Files_map.GetCount());
	else
		theApp.emuledlg->AddLogLine(false,GetResString(IDS_SHAREDFOUNDHASHING), m_Files_map.GetCount(), waitingforhash_list.GetCount());
	HashNextFile();
}

void CSharedFileList::AddFilesFromDirectory(char* directory){
	CFileFind ff;
	
	char* searchpath = new char[strlen(directory)+3];
	sprintf(searchpath,"%s\\*",directory);
	
	bool end = !ff.FindFile(searchpath,0);
	delete[] searchpath;

	if (end)
		return;

	while (!end){
		end = !ff.FindNextFile();
		if (ff.IsDirectory() || ff.IsDots() || ff.IsSystem() || ff.IsTemporary() || ff.GetLength()>=4294967295 )
			continue;
		CTime lwtime;
		if (!ff.GetLastWriteTime(lwtime))
			theApp.emuledlg->AddDebugLogLine(false, "Failed to get file date of %s - %s", ff.GetFileName(), GetErrorMessage(GetLastError()));
		uint32 fdate = mktime(lwtime.GetLocalTm());
		if (fdate == -1)
			theApp.emuledlg->AddDebugLogLine(false, "Failed to convert file date of %s", ff.GetFileName());
		CKnownFile* toadd = filelist->FindKnownFile(ff.GetFileName().GetBuffer(),fdate,(uint32)ff.GetLength());
		if (toadd){
			toadd->SetPath(directory);
			CSingleLock sLock(&list_mut,true);
			m_Files_map.SetAt(CCKey(toadd->GetFileHash()),toadd);
			sLock.Unlock();
		}
		else{
			//not in knownfilelist - start adding thread to hash file
			UnknownFile_Struct* tohash = new UnknownFile_Struct;
			tohash->directory = nstrdup(directory);
			tohash->name = nstrdup(ff.GetFileName().GetBuffer());
			waitingforhash_list.AddTail(tohash);
		}
	}
	ff.Close();
}

void CSharedFileList::SafeAddKFile(CKnownFile* toadd, bool bOnlyAdd){
	// TODO: Check if the file is already known - only with another date
	CSingleLock sLock(&list_mut,true);
	m_Files_map.SetAt(CCKey(toadd->GetFileHash()),toadd);
	sLock.Unlock();
	if (bOnlyAdd)
		return;
	if (output)
		output->ShowFile(toadd);
	HashNextFile();
	// offer new file to server
	if (!server->IsConnected())
		return;
	CMemFile* files = new CMemFile(100);
	uint32 filecount = 1;
	files->Write(&filecount,4);
	CreateOfferedFilePacket(toadd,files);
	Packet* packet = new Packet(files);
	packet->opcode = OP_OFFERFILES;
	delete files;
	theApp.uploadqueue->AddUpDataOverheadServer(packet->size);
	server->SendPacket(packet,true);
	
}

// removes first occurrence of 'toremove' in 'list'
void CSharedFileList::RemoveFile(CKnownFile* toremove){
	output->RemoveFile(toremove);
	m_Files_map.RemoveKey(CCKey(toremove->GetFileHash()));
}

void CSharedFileList::Reload(bool sendtoserver){
	this->FindSharedFiles();
	if (output)
		output->ShowFileList(this);
	if (sendtoserver)
		SendListToServer();
}

void CSharedFileList::SetOutputCtrl(CSharedFilesCtrl* in_ctrl){
	output = in_ctrl;
	output->ShowFileList(this);
}

void CSharedFileList::SendListToServer(){
	if (m_Files_map.IsEmpty() || !server->IsConnected())
		return;
	CSafeMemFile files(1024);
	CCKey bufKey;
	CKnownFile* cur_file,cur_file2;
	POSITION pos,pos2;

	// send all files
	if (server->GetCurrentServer()->GetSoftFiles() && (uint32)m_Files_map.GetCount()<=server->GetCurrentServer()->GetSoftFiles() ) {
		uint32 filecount = m_Files_map.GetCount();
		files.Write(&filecount,4);
		for (pos = m_Files_map.GetStartPosition();pos != 0;){
			m_Files_map.GetNextAssoc(pos,bufKey,cur_file);
			CreateOfferedFilePacket(cur_file,&files);
		}
	} else { // send subset
		CTypedPtrList<CPtrList, CKnownFile*> sortedList;
		bool added=false;
		for(pos=m_Files_map.GetStartPosition(); pos!=0;)
		{
			m_Files_map.GetNextAssoc(pos, bufKey, cur_file);
			added=false;

			//insertsort into sortedList
			for (pos2 = sortedList.GetHeadPosition();pos2 != 0 && !added;sortedList.GetNext(pos2)){
				if (GetRealPrio(sortedList.GetAt(pos2)->GetUpPriority()) <= GetRealPrio(cur_file->GetUpPriority()) ) {
					sortedList.InsertBefore(pos2,cur_file);
					added=true;
				}
			}
			if (!added)
				sortedList.AddTail(cur_file);
		}
		// add to packet
		uint32 filecount = server->GetCurrentServer()->GetSoftFiles();
		if (filecount == 0) // if we don't know the soft limit, we send all our files
			filecount = sortedList.GetCount();
		files.Write(&filecount,4);
		uint32 count=0;
		for (pos2 = sortedList.GetHeadPosition();pos2 != 0 && count<filecount ;sortedList.GetNext(pos2)){
			count++;
			CreateOfferedFilePacket(sortedList.GetAt(pos2),&files);
		}
		sortedList.RemoveAll();
	}

	Packet* packet = new Packet(&files);
	packet->opcode = OP_OFFERFILES;
	theApp.uploadqueue->AddUpDataOverheadServer(packet->size);
	server->SendPacket(packet,true);
}

CKnownFile* CSharedFileList::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;
}

void CSharedFileList::CreateOfferedFilePacket(CKnownFile* cur_file,CMemFile* files){
	files->Write(cur_file->GetFileHash(),16);
	char* buffer = new char[6];
	memset(buffer,0,6);
	files->Write(buffer,6);
	delete[] buffer;
	files->Write(cur_file->GetFileTypePtr(),4);
	CTag* nametag = new CTag(FT_FILENAME,cur_file->GetFileName());
	nametag->WriteTagToFile(files);
	delete nametag;
	CTag* sizetag = new CTag(FT_FILESIZE,cur_file->GetFileSize());
	sizetag->WriteTagToFile(files);
	delete sizetag;
	//TODO add tags for documents mp3 etc
}

// -khaos--+++> New param:  pbytesLargest, pointer to uint64.
//				Various other changes to accomodate our new statistic...
//				Point of this is to find the largest file currently shared.
uint64 CSharedFileList::GetDatasize(uint64 &pbytesLargest) {
	pbytesLargest=0;
	// <-----khaos-
	uint64 fsize;
	fsize=0;

	CCKey bufKey;
	CKnownFile* cur_file;
	for (POSITION pos = m_Files_map.GetStartPosition();pos != 0;){
		m_Files_map.GetNextAssoc(pos,bufKey,cur_file);
		fsize+=cur_file->GetFileSize();
		// -khaos--+++> If this file is bigger than all the others...well duh.
		if (cur_file->GetFileSize() > pbytesLargest) pbytesLargest = cur_file->GetFileSize();
		// <-----khaos-
	}
	return fsize;
}

CKnownFile*	CSharedFileList::GetFileByID(uchar* filehash){
	CKnownFile* result;
	CCKey tkey(filehash);
	if (filehash && m_Files_map.Lookup(tkey,result))
		return result;
	else
		return 0;
}

void CSharedFileList::HashNextFile(){
	if (waitingforhash_list.IsEmpty())
		return;
	UnknownFile_Struct* nextfile = waitingforhash_list.RemoveHead();
	CAddFileThread* addfilethread = (CAddFileThread*) AfxBeginThread(RUNTIME_CLASS(CAddFileThread), THREAD_PRIORITY_BELOW_NORMAL,0, CREATE_SUSPENDED);
	addfilethread->SetValues(this,nextfile->directory,nextfile->name);
	addfilethread->ResumeThread();
	delete[] nextfile->directory; 
	delete[] nextfile->name;
	delete nextfile;
	
}

IMPLEMENT_DYNCREATE(CAddFileThread, CWinThread)
CAddFileThread::CAddFileThread(){
	m_pOwner = 0;
	filename = 0;
	directory = 0;
}
void CAddFileThread::SetValues(CSharedFileList* pOwner, char* in_directory, char* in_filename, CPartFile* in_partfile_Owner){
	 m_pOwner = pOwner;
	 directory = nstrdup(in_directory);
	 filename = nstrdup(in_filename);
	 partfile_Owner = in_partfile_Owner;
}

int CAddFileThread::Run(){
	DbgSetThreadName("Hashing %s", filename);
	if (!(m_pOwner || partfile_Owner) || !filename)
		AfxEndThread(0,true);
	CSingleLock sLock1(&(theApp.hashing_mut)); // only one filehash at a time
	sLock1.Lock();
	CKnownFile* newrecord = new CKnownFile();
	if (newrecord->CreateFromFile(directory,filename)){
		if (theApp.emuledlg && theApp.emuledlg->IsRunning())
			PostMessage(theApp.emuledlg->m_hWnd,TM_FINISHEDHASHING,(m_pOwner ? 0:(WPARAM)partfile_Owner),(LPARAM)newrecord);
	}
	else{
		delete newrecord;
	}

	delete[] filename;
	if (directory)
		delete[] directory;
	sLock1.Unlock();
	AfxEndThread(0,true);
	return 0;
}

void CSharedFileList::UpdateFile(CKnownFile* toupdate) {output->UpdateFile(toupdate);}
