//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 "downloadqueue.h"
#include "updownclient.h"
#include "partfile.h"
#include "ed2klink.h"

CDownloadQueue::CDownloadQueue(CPreferences* in_prefs,CSharedFileList* in_sharedfilelist){
	app_prefs = in_prefs;
	sharedfilelist = in_sharedfilelist;
	filesrdy = 0;
	datarate = 0;
	cur_udpserver = 0;
	lastfile = 0;
	lastudpsearchtime = 0;
	lastudpstattime = 0;
	udcounter = 0;
	m_nDownDataRateMSOverhead = 0;
	m_nDownDatarateOverhead = 0;
}

void CDownloadQueue::AddPartFilesToShare(){
	for (POSITION pos = filelist.GetHeadPosition();pos != 0;filelist.GetNext(pos)){
		CPartFile* cur_file = filelist.GetAt(pos);
		if (cur_file->GetStatus(true) == PS_READY)
			sharedfilelist->SafeAddKFile(cur_file,true);
	}
}

void CDownloadQueue::CompDatarateOverhead(){
	m_AvarageDDRO_list.AddTail(m_nDownDataRateMSOverhead);
	if (m_AvarageDDRO_list.GetCount() > 150)
		m_AvarageDDRO_list.RemoveAt(m_AvarageDDRO_list.GetHeadPosition());
	
	m_nDownDatarateOverhead = 0;
	m_nDownDataRateMSOverhead = 0;
	for (POSITION pos = m_AvarageDDRO_list.GetHeadPosition();pos != 0;m_AvarageDDRO_list.GetNext(pos))
		m_nDownDatarateOverhead += m_AvarageDDRO_list.GetAt(pos);

	if(m_AvarageDDRO_list.GetCount() > 10)
		m_nDownDatarateOverhead /= (m_AvarageDDRO_list.GetCount()/10);
	else
		m_nDownDatarateOverhead = 0;
	return;
}

void CDownloadQueue::Init(){
	//////////////////////////////////////////////////
	//for saving/loading sources mr fry
	
	//this indicates, if a permanent open CStoredSources-object for each
	//part-file should be used...
	//means, additons of sources and loading sources are made to/from this
	//open CStoredSources-object and saved to disk only on closing of eMule
	//
	//otherwise on each saving-/loading- session the *.src-file on disk is
	//opened before the save-/load- operation and closed again afterwards
	//
	//I think the difference is:
	//set to true: more memory is used (I think, it took about 20 kB to save 300 sources to my hard-drive)
	//set to false: more cpu-power is used
	this->m_bKeepStoredSourcesOpened	= true;

	this->SetSourcesLoading(true);
	//////////////////////////////////////////////////

	// find all part files, read & hash them if needed and store into a list
	CFileFind ff;
	int count = 0;

	//char* searchpath = new char[strlen(app_prefs->GetTempDir())+MAX_PATH];
	CString searchPath(app_prefs->GetTempDir());
	searchPath += "\\*.part.met";
	//sprintf(searchpath,"%s\\*.part.met",app_prefs->GetTempDir());
	//delete[] searchpath;

	//check all part.met files
	bool end = !ff.FindFile(searchPath, 0);
	while (!end){
		end = !ff.FindNextFile();
		if (ff.IsDirectory())
			continue;
		CPartFile* toadd = new CPartFile();
		if (toadd->LoadPartFile(app_prefs->GetTempDir(),ff.GetFileName().GetBuffer())){
			count++;
			// Added by Tarod -check if is an A4AF auto file, correct place?
			if (memcmp(toadd->GetFileHash(), theApp.glob_prefs->GetA4AFHash(), 16) == 0) {
				toadd->SetA4AFAuto(true);
			}
			filelist.AddTail(toadd);			// to downloadqueue
			if (toadd->GetStatus(true) == PS_READY)
				sharedfilelist->SafeAddKFile(toadd); // part files are always shared files
			theApp.emuledlg->transferwnd.downloadlistctrl.AddFile(toadd);// show in downloadwindow
			//////////////////////////////////////////////////
			//for loading sources mr fry
			if(toadd->GetStatus() < 4 && this->IsSourcesLoadingEnabled()){
							
				
				//load source for the part-file
				toadd->LoadAndAddSources(this->KeepStoredSourcesOpen(),false,toadd->GetLoadSourcesAtOnceLimit(),true,false,false);	//to restore sources from file (by Mr.Fry)
				toadd->SetLoadedSourcesCompletely(false);

				//do load sources for part-files with different time-offsets
				//means: do not load the sources of all part-files at one time
				//(offset: (No. of part-file) multiplied by (1 second) )
				toadd->SetLoadSourcesSlowTimeInterval((toadd->GetLoadSourcesSlowTimeInterval() + (count * 1000)));
			}
			//////////////////////////////////////////////////
		}
		else
			delete toadd;
	}
	ff.Close();

	//try recovering any part.met files
	searchPath += ".backup";
	end = !ff.FindFile(searchPath, 0);
	while (!end){
		end = !ff.FindNextFile();
		if (ff.IsDirectory())
			continue;
		CPartFile* toadd = new CPartFile();
		if (toadd->LoadPartFile(app_prefs->GetTempDir(),ff.GetFileName().GetBuffer())){
			toadd->SavePartFile(); // resave backup
			count++;
			filelist.AddTail(toadd);			// to downloadqueue
			if (toadd->GetStatus(true) == PS_READY)
				sharedfilelist->SafeAddKFile(toadd); // part files are always shared files
			theApp.emuledlg->transferwnd.downloadlistctrl.AddFile(toadd);// show in downloadwindow

			theApp.emuledlg->AddLogLine(false, GetResString(IDS_RECOVERED_PARTMET), toadd->GetFileName());
		}
		else {
			delete toadd;
		}
	}
	ff.Close();

	if(count == 0) {
		theApp.emuledlg->AddLogLine(false,GetResString(IDS_NOPARTSFOUND));
	} else {
		theApp.emuledlg->AddLogLine(false,GetResString(IDS_FOUNDPARTS),count);
		SortByPriority();
	}
}

CDownloadQueue::~CDownloadQueue(){
	for (POSITION pos =filelist.GetHeadPosition();pos != 0;filelist.GetNext(pos)){
		filelist.GetAt(pos)->m_hpartfile.Flush() ; // Flush before close app [Tarod]
		filelist.GetAt(pos)->SavePartFile();
		delete filelist.GetAt(pos);
	}
	
}

void CDownloadQueue::AddSearchToDownload(CSearchFile* toadd){
	if (IsFileExisting(toadd->GetFileHash()))
		return;
	CPartFile* newfile = new CPartFile(toadd);
	if (newfile->GetStatus() == PS_ERROR){
		delete newfile;
		return;
	}
	AddDownload(newfile);
}

void CDownloadQueue::AddSearchToDownload(CString link){
	CPartFile* newfile = new CPartFile(link);
	if (newfile->GetStatus() == PS_ERROR){
		delete newfile;
		return;
	}
	AddDownload(newfile);
}

void CDownloadQueue::StartNextFile(){
	if( !theApp.glob_prefs->StartNextFile() )
		return;
	for (POSITION pos = filelist.GetHeadPosition();pos != 0;filelist.GetNext(pos)){
		if (filelist.GetAt(pos)->GetStatus() == PS_PAUSED && filelist.GetAt(pos)->GetPriority() == PR_HIGH ){
			filelist.GetAt(pos)->ResumeFile();
			return;
		}
	}
	for (POSITION pos = filelist.GetHeadPosition();pos != 0;filelist.GetNext(pos)){
		if (filelist.GetAt(pos)->GetStatus() == PS_PAUSED && filelist.GetAt(pos)->GetPriority() == PR_NORMAL ){
			filelist.GetAt(pos)->ResumeFile();
			return;
		}
	}
	for (POSITION pos = filelist.GetHeadPosition();pos != 0;filelist.GetNext(pos)){
		if (filelist.GetAt(pos)->GetStatus() == PS_PAUSED && filelist.GetAt(pos)->GetPriority() == PR_LOW ){
			filelist.GetAt(pos)->ResumeFile();
			return;
		}
	}
}

void CDownloadQueue::AddFileLinkToDownload(CED2KFileLink* pLink)
{
	CPartFile* newfile = new CPartFile(pLink);
	if (newfile->GetStatus() == PS_ERROR){
		delete newfile;
		newfile=NULL;
	}
	else AddDownload(newfile);
	if(pLink->HasValidSources()) {
		if (newfile) newfile->AddClientSources(pLink->SourcesList);
		else{
				CPartFile* partfile = GetFileByID((uchar*)pLink->GetHashKey());
				if (partfile) partfile->AddClientSources(pLink->SourcesList);
		}

	}
}

// Deactivates all downloading files A4AF auto setting
void CDownloadQueue::DisableAllA4AFAuto(void)
{
	for (POSITION pos = filelist.GetHeadPosition(); pos != NULL; filelist.GetNext(pos)) {
		CPartFile* cur_file = (CPartFile*)filelist.GetAt(pos);
		if (cur_file != NULL) cur_file->SetA4AFAuto(false);
	}
}

// Returns A4AF auto file
//
// [Return]
//      Pointer fo file that is A4AF or NULL in case that there is no A4AF auto file.
CPartFile* CDownloadQueue::GetA4AFAutoFile(void)
{
	CPartFile* A4AF_auto_file = NULL;
	POSITION pos = filelist.GetHeadPosition();
	while (A4AF_auto_file == NULL && pos != NULL) {
		if (((CPartFile*)filelist.GetAt(pos))->IsA4AFAuto()) {
			A4AF_auto_file = (CPartFile*)filelist.GetAt(pos);
		}
		filelist.GetNext(pos);
	}

	return A4AF_auto_file;
}

void CDownloadQueue::AddDownload(CPartFile* newfile) {
	// Barry - Add in paused mode if required
	if (theApp.glob_prefs->AddNewFilesPaused())
		newfile->PauseFile();
	else // Added by Tarod -in case we exceed XS limits sources must be loaded
		newfile->SetLoadSourcesTimeInterval(2 * 60 * 1000);

	filelist.AddTail(newfile);
	SortByPriority();
	theApp.emuledlg->transferwnd.downloadlistctrl.AddFile(newfile);
	theApp.emuledlg->AddLogLine(true,GetResString(IDS_NEWDOWNLOAD),newfile->GetFileName());
	CString msgTemp;
	msgTemp.Format(GetResString(IDS_NEWDOWNLOAD)+"\n",newfile->GetFileName());
	theApp.emuledlg->ShowNotifier(msgTemp, TBN_DLOAD);
}

bool CDownloadQueue::IsFileExisting(uchar* fileid){
	if (CKnownFile* file = sharedfilelist->GetFileByID((uchar*)fileid)){
		if (file->IsPartFile())
			theApp.emuledlg->AddLogLine(true, GetResString(IDS_ERR_ALREADY_DOWNLOADING), file->GetFileName() );
		else
			theApp.emuledlg->AddLogLine(true, GetResString(IDS_ERR_ALREADY_DOWNLOADED), file->GetFileName() );
		return true;
	}
	else if ( file = this->GetFileByID((uchar*)fileid)){
		theApp.emuledlg->AddLogLine(true, GetResString(IDS_ERR_ALREADY_DOWNLOADING), file->GetFileName() );
		return true;
	}
	return false;
}


void CDownloadQueue::Process(){
	sint16 downspeed = 0;
	if (app_prefs->GetMaxDownload() != UNLIMITED && datarate > 1500){
		downspeed =  (uint16)((float)((float)(app_prefs->GetMaxDownload()*1024)/(datarate+1)) * 100);
		if (downspeed < 50)
			downspeed = 50;
		else if (downspeed > 200)
			downspeed = 200;
		//if (app_prefs->GetMaxDownload()*1024 < datarate)
		//	downspeed = 0xFFF;
	}
	datarate = 0;
	for (POSITION pos =filelist.GetHeadPosition();pos != 0;filelist.GetNext(pos)){
		CPartFile* cur_file =  filelist.GetAt(pos);
		if (cur_file->GetStatus() == PS_READY || cur_file->GetStatus() == PS_EMPTY){
			datarate += cur_file->Process(downspeed);
		}
	}
	udcounter++;
	if (udcounter == 5){
		if((!lastudpstattime) || (::GetTickCount() - lastudpstattime) > UDPSERVERSTATTIME){
			lastudpstattime = ::GetTickCount();
			theApp.serverlist->ServerStats();
		}
	}
	if (udcounter == 10){
		udcounter = 0;
		if ((!lastudpsearchtime) || (::GetTickCount() - lastudpsearchtime) > UDPSERVERREASKTIME)
			SendNextUDPPacket();
	}
}

CPartFile*	CDownloadQueue::GetFileByID(uchar* filehash){
	for (POSITION pos = filelist.GetHeadPosition();pos != 0;filelist.GetNext(pos)){
		if (!memcmp(filehash,filelist.GetAt(pos)->GetFileHash(),16))
			return filelist.GetAt(pos);
	}
	return 0;
}

bool CDownloadQueue::IsPartFile(void* totest){
	for (POSITION pos = filelist.GetHeadPosition();pos != 0;filelist.GetNext(pos))
		if (totest == filelist.GetAt(pos))
			return true;
	return false;
}

void CDownloadQueue::CheckAndAddSource(CPartFile* sender,CUpDownClient* source, bool saveSources){
	// if we block loopbacks at this point it should prevent us from connecting to ourself
	if(!source->HasLowID() && (source->GetUserID() & 0xFF) == 0x7F) {
		delete source;
		return;
	}

	// uses this only for temp. clients
	for (POSITION pos = filelist.GetHeadPosition();pos != 0;filelist.GetNext(pos)){
		CPartFile* cur_file = filelist.GetAt(pos);
		for (POSITION pos2 = cur_file->srclist.GetHeadPosition();pos2 != 0; cur_file->srclist.GetNext(pos2)){
			if (cur_file->srclist.GetAt(pos2)->Compare(source)){
				if (cur_file == sender){ // this file has already this source
					delete source;
					return;
				}
				// set request for this source
				if (cur_file->srclist.GetAt(pos2)->AddRequestForAnotherFile(sender)){
					// add it to uploadlistctrl
					theApp.emuledlg->transferwnd.downloadlistctrl.AddSource(sender,cur_file->srclist.GetAt(pos2),true);
					delete source;
					return;
				}
				else{
					delete source;
					return;
				}
			}
		}
	}
	//our new source is real new but maybe it is already uploading to us?
	//if yes the known client will be attached to the var "source"
	//and the old sourceclient will be deleted
	if (theApp.clientlist->AttachToAlreadyKnown(&source,0))
		source->reqfile = sender;
	else
		theApp.clientlist->AddClient(source,true);
	sender->srclist.AddTail(source);
	theApp.emuledlg->transferwnd.downloadlistctrl.AddSource(sender,source,false);
	theApp.emuledlg->transferwnd.downloadlistctrl.UpdateItem(this);
	//////////////////////////////////////////////////
	//code for saving the sources to disk

	if(saveSources && this->m_bKeepStoredSourcesOpened){
		//store the new source immediately to the CStoredSources-object of the part-file
		sender->SaveToStoredSources(source);
	}//end of modification by Mr.Fry
	//////////////////////////////////////////////////

}

void CDownloadQueue::CheckAndAddKnownSource(CPartFile* sender,CUpDownClient* source){
	if(!source->HasLowID() && (source->GetUserID() & 0xFF) == 0x7F)
		return;

	// use this for client which are already know (downloading for example)
	for (POSITION pos = filelist.GetHeadPosition();pos != 0;filelist.GetNext(pos)){
		CPartFile* cur_file = filelist.GetAt(pos);
		if (cur_file->srclist.Find(source)){
			if (cur_file == sender)
				return;
			if (source->AddRequestForAnotherFile(sender))
				theApp.emuledlg->transferwnd.downloadlistctrl.AddSource(sender,source,true);
			return;
		}
	}
	source->reqfile = sender;
	sender->srclist.AddTail(source);
	theApp.emuledlg->transferwnd.downloadlistctrl.AddSource(sender,source,false);
	theApp.emuledlg->transferwnd.downloadlistctrl.UpdateItem(this);
	//////////////////////////////////////////////////
	//code for saving the sources to disk

	if(this->m_bKeepStoredSourcesOpened){
		//store the new source immediately to the CStoredSources-object of the part-file
		sender->SaveToStoredSources(source);
	}//end of modification by Mr.Fry
	//////////////////////////////////////////////////

}

bool CDownloadQueue::RemoveSource(CUpDownClient* toremove, bool	updatewindow){
	bool removed = false;
	for (POSITION pos = filelist.GetHeadPosition();pos != 0;filelist.GetNext(pos)){
		CPartFile* cur_file = filelist.GetAt(pos);
		for (POSITION pos2 = cur_file->srclist.GetHeadPosition();pos2 != 0; cur_file->srclist.GetNext(pos2)){
			if (toremove == cur_file->srclist.GetAt(pos2)){
				cur_file->srclist.RemoveAt(pos2);
				cur_file->NewSrcPartsInfo();
				removed = true;
				break;
			}
		}
		cur_file->UpdateAvailablePartsCount();
	}
	if (updatewindow){
		toremove->SetDownloadState(DS_NONE);
		theApp.emuledlg->transferwnd.downloadlistctrl.RemoveSource(toremove,0);
	}
	toremove->reqfile = 0;
	return removed;
}

void CDownloadQueue::RemoveFile(CPartFile* toremove){
	for (POSITION pos = filelist.GetHeadPosition();pos != 0;filelist.GetNext(pos)){
		if (toremove == filelist.GetAt(pos)){
			filelist.RemoveAt(pos);
			return;
		}
	}
}

void CDownloadQueue::DeleteAll(){
	POSITION pos;
	for (pos = filelist.GetHeadPosition();pos != 0;filelist.GetNext(pos)){
		CPartFile* cur_file = filelist.GetAt(pos);
		cur_file->srclist.RemoveAll();
		// Barry - Should also remove all requested blocks
		// Don't worry about deleting the blocks, that gets handled 
		// when CUpDownClient is deleted in CClientList::DeleteAll()
		cur_file->RemoveAllRequestedBlocks();
	}
}

bool CDownloadQueue::SendNextUDPPacket(){
	if (filelist.IsEmpty() || !theApp.serverconnect->IsConnected())
		return false;
	if (!cur_udpserver)
		if (!(cur_udpserver = theApp.serverlist->GetNextServer(cur_udpserver))){
			TRACE("ERROR:SendNextUDPPacket() no server found");
			StopUDPRequests();
		};

	// get nextfile
	CPartFile* nextfile = 0;
	while (!(nextfile && (nextfile->GetStatus() == PS_READY ||nextfile->GetStatus() == PS_EMPTY))){
		if (lastfile == 0){
			nextfile = filelist.GetHead();
			lastfile = nextfile;
		}
		else{
			POSITION pos = filelist.Find(lastfile);
			if (!pos){
				TRACE("Error: CDownloadQueue::SendNextUDPPacket()");
				nextfile = filelist.GetHead();
				lastfile = nextfile;
			}
			else{
				filelist.GetNext(pos);
				if (pos == 0){
					cur_udpserver = theApp.serverlist->GetNextServer(cur_udpserver);
					if (cur_udpserver == 0){
						//TRACE("finished");
						lastudpsearchtime = ::GetTickCount();
						lastfile = 0;
						return false; // finished (processed all file & all servers)
					}
					nextfile = filelist.GetHead();
					lastfile = nextfile;
				}
				else{
					nextfile = filelist.GetAt(pos);
					lastfile = nextfile;
				}
			}
		}
	}
	if( (theApp.glob_prefs->GetMaxSourcePerFileUDP()) < nextfile->GetSourceCount()) //<<--
		return true; 
	Packet packet(OP_GLOBGETSOURCES,16);
	memcpy(packet.pBuffer,nextfile->GetFileHash(),16);
	if (cur_udpserver != theApp.serverlist->GetServerByAddress(theApp.serverconnect->GetCurrentServer()->GetAddress(),theApp.serverconnect->GetCurrentServer()->GetPort()))
		theApp.serverconnect->SendUDPPacket(&packet,cur_udpserver,false);
	return true;
}

void CDownloadQueue::StopUDPRequests(){
	cur_udpserver = 0;
	lastudpsearchtime = ::GetTickCount();
	lastfile = 0;
}

void CDownloadQueue::SortByPriority(){
   POSITION pos1, pos2;
   uint16 i = 0;
   for( pos1 = filelist.GetHeadPosition(); ( pos2 = pos1 ) != NULL; ){
	   filelist.GetNext(pos1);
	   CPartFile* cur_file = filelist.GetAt(pos2);
	   if (cur_file->GetPriority() == PR_HIGH){
		   filelist.AddHead(cur_file);
		   filelist.RemoveAt(pos2);
	   }
	   else if (cur_file->GetPriority() == PR_LOW){
		   filelist.AddTail(cur_file);
		   filelist.RemoveAt(pos2);
	   }
	   i++;
	   if (i == filelist.GetCount())
		   break;
   }
}

void CDownloadQueue::GetDownloadStats(int results[]) {
	
	results[0]=0;
	results[1]=0;

	for (POSITION pos =theApp.downloadqueue->filelist.GetHeadPosition();pos != 0;theApp.downloadqueue->filelist.GetNext(pos)){
		CPartFile* cur_file =  filelist.GetAt(pos);
		results[0]+=cur_file->GetSourceCount();
		results[1]+=cur_file->GetTransferingSrcCount();
	}
}

CUpDownClient* CDownloadQueue::GetDownloadClientByIP(uint32 dwIP){
	for (POSITION pos = filelist.GetHeadPosition();pos != 0;filelist.GetNext(pos)){
		CPartFile* cur_file = filelist.GetAt(pos);
		for (POSITION pos2 = cur_file->srclist.GetHeadPosition();pos2 != 0; cur_file->srclist.GetNext(pos2)){
			if (dwIP == cur_file->srclist.GetAt(pos2)->GetIP()){
				return cur_file->srclist.GetAt(pos2);
			}
		}
	}
	return NULL;
}

/////////////////////////////////////////////////////////////////////////////////////
//Storing Sources:

bool CDownloadQueue::SaveAllSourcesToFile(bool debug)
{
	bool errorOccured=false;
	for (POSITION pos =filelist.GetHeadPosition();pos != 0;filelist.GetNext(pos)){
		CPartFile* partFile = filelist.GetAt(pos);
		if(!partFile->SaveSources()){
			TRACE("Could not save sources (clients) for one downloading file\n\t(probably there are no sources for this file)\n");			
			errorOccured = true;
		}
	}
	if(errorOccured) return false;
	return true;
}

bool CDownloadQueue::IsMaxClientsExceeded()
{
	return (theApp.clientlist->GetClientCount() > (int)theApp.glob_prefs->GetMaxConnections());
}

