//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(){
}

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(); // readd partfiles
	}
	AddFilesFromDirectory(app_prefs->GetIncomingDir());
	for (POSITION pos = app_prefs->shareddir_list.GetHeadPosition();pos != 0;app_prefs->shareddir_list.GetNext(pos))
		AddFilesFromDirectory(app_prefs->shareddir_list.GetAt(pos).GetBuffer());
	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;
	//ff.FindNextFile();
	while (!end){
		end = !ff.FindNextFile();
		if (ff.IsDirectory() || ff.IsDots() || ff.IsSystem() || ff.IsTemporary())
			continue;
		CTime lwtime;
		ff.GetLastWriteTime(lwtime);
		uint32 fdate = mktime(lwtime.GetLocalTm());
		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;
	CMemFile* files = new CMemFile();
	uint32 filecount = m_Files_map.GetCount();
	files->Write(&filecount,4);
	
	CCKey bufKey;
	CKnownFile* cur_file;
	for (POSITION pos = m_Files_map.GetStartPosition();pos != 0;){
		m_Files_map.GetNextAssoc(pos,bufKey,cur_file);
		CreateOfferedFilePacket(cur_file,files);
	}
	Packet* packet = new Packet(files);
	packet->opcode = OP_OFFERFILES;
	delete files;
	theApp.uploadqueue->AddUpDataOverheadServer(packet->size);
	server->SendPacket(packet,true);
}

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
}

uint64 CSharedFileList::GetDatasize() {
	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();
	}
	return fsize;
}

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

short CSharedFileList::GetFilePriorityByID(uchar* filehash){
	CKnownFile* tocheck = GetFileByID(filehash);
	if (tocheck)
		return tocheck->GetPriority();
	else
		return -10;	// file doesn't exist
}

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(){
	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)){
		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::UpdateItem(CKnownFile* toupdate) {output->UpdateItem(toupdate);}
