//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 "sourcesaver.h"
#include "PartFile.h"
#include "emule.h"
#include "Log.h" // for AddLogLine()
#include "Preferences.h" // for thePrefs
#include "emuledlg.h" // for theApp.emuledlg
#include "DownloadQueue.h" // for theApp.downloadqueue
#include "Clientlist.h" // for theApp.clientlist
#include "OtherFunctions.h" // for ipstr()

//modified by sivka
//#define RELOADTIME	3600000 //60 minutes	
#define RESAVETIME	 600000 //10 minutes

#define SOURCESTOSAVE	36
#define EXPIREIN	360  //modified by sivka  6h => 360 min
//#define SOURCESTOSAVE	350
//#define EXPIREIN	720  //modified by sivka 12h => 720 min

CSourceSaver::CSourceSaver(void){ //modified by sivka
	m_LoadSourcesOnStart = true;
	m_dwLastTimeSaved = ::GetTickCount() + (rand() * 30000 / RAND_MAX) - 15000 - RESAVETIME;
}
CSourceSaver::~CSourceSaver(void){}

void CSourceSaver::ManuellSave(CPartFile* file){
	SourceList srcs;
	CString slsfilepath;
	slsfilepath.Format(_T("%s\\%s.txtsrc"), thePrefs.GetTempDir(), file->GetPartMetFileName());
	LoadSourcesFromFile(&srcs, slsfilepath);
	SaveSourcesToFile(&srcs, slsfilepath, file);
	while(!srcs.IsEmpty()) delete srcs.RemoveHead();
	m_dwLastTimeSaved = ::GetTickCount()+(rand()*30000/RAND_MAX)-15000;
}

void CSourceSaver::ManuellLoad(CPartFile* file){
	SourceList srcs;
	CString slsfilepath;
	slsfilepath.Format(_T("%s\\%s.txtsrc"), thePrefs.GetTempDir(), file->GetPartMetFileName());
	LoadSourcesFromFile(&srcs, slsfilepath);
	AddSourcesToDownload(&srcs, file);
	while(!srcs.IsEmpty()) delete srcs.RemoveHead();
}

void CSourceSaver::Process(CPartFile* file){
	if ((int)(::GetTickCount() - m_dwLastTimeSaved) > RESAVETIME)
	{
		SourceList srcs;
		CString slsfilepath;
		slsfilepath.Format(_T("%s\\%s.txtsrc"), thePrefs.GetTempDir(), file->GetPartMetFileName());
		LoadSourcesFromFile(&srcs, slsfilepath);
		SaveSourcesToFile(&srcs, slsfilepath, file);
		if( m_LoadSourcesOnStart ){
			m_LoadSourcesOnStart = false;
			AddSourcesToDownload(&srcs, file);
		}
		while(!srcs.IsEmpty()) delete srcs.RemoveHead();
		m_dwLastTimeSaved = ::GetTickCount() + (rand() * 30000 / RAND_MAX) - 15000;
	}
}

void CSourceSaver::DeleteFile(CPartFile* file)
{
	CString slsfilepath;
	slsfilepath.Format(_T("%s\\%s.txtsrc"), thePrefs.GetTempDir(), file->GetPartMetFileName());
	if (_tremove(slsfilepath)) if (errno != ENOENT)
		AddLogLine(true,_T("Failed to delete %s, you will need to do this by hand"),slsfilepath);
}

void CSourceSaver::LoadSourcesFromFile(SourceList* sources, CString& slsfile)
{
	CString strLine;
	CStdioFile f;
	if (!f.Open(slsfile, CFile::modeRead | CFile::typeText))
		return;
	while(f.ReadString(strLine)) {
		if (strLine.GetAt(0) == '#')
			continue;
		int pos = strLine.Find(_T(':'));
		if (pos == -1)
			continue;
		CString strIP = strLine.Left(pos);
		strLine = strLine.Mid(pos+1);
		UINT uID = inet_addr(CStringA(strIP));
		if (uID == INADDR_NONE) 
			continue;
		pos = strLine.Find(_T(','));
		if (pos == -1)
			continue;
		CString strPort = strLine.Left(pos);
		strLine = strLine.Mid(pos+1);
		uint16 uPort = (uint16)_ttoi(strPort);
		if (!uPort)
			continue;
		pos = strLine.Find(_T(';'));
		if (pos == -1)
			continue;
		CString strExpirationTemp = strLine.Left(pos);

		//added by sivka [-bugfix-]
		for(int i=strExpirationTemp.GetLength(); i<10; )
			 i=strExpirationTemp.Insert(i,'0');

		if (IsExpired(strExpirationTemp))
			continue;

		CSourceData* newsource = new CSourceData(uID,uPort,strExpirationTemp);
		sources->AddTail(newsource);
		
	}
    f.Close();
}

void CSourceSaver::AddSourcesToDownload(SourceList* sources, CPartFile* file) 
{
	for (POSITION pos = sources->GetHeadPosition();pos;){
		if (file->GetMaxSourcesPerFile() <= file->GetSourceCount()) return;
		CSourceData* cur_src = sources->GetNext(pos);
		CUpDownClient* newclient = new CUpDownClient(file, cur_src->sourcePort, cur_src->sourceID, 0, 0, false);
 		if(theApp.downloadqueue->CheckAndAddSource(file,newclient))
	 		newclient->SetSourceFrom(SF_SAVER); // added by sivka
	}
}

void CSourceSaver::SaveSourcesToFile(SourceList* prevsources, CString& slsfile, CPartFile* file)
{
	SourceList srcstosave;
	CSourceData* sourcedata;

	ASSERT(srcstosave.IsEmpty());

	// Choose best sources for the file
	for(POSITION pos1 = file->srclist.GetHeadPosition(); pos1; )
	{
		CUpDownClient* cur_src = file->srclist.GetNext(pos1);

		ASSERT(theApp.clientlist->IsValidClient(cur_src));

		if( cur_src->HasLowID() || !cur_src->IsValidSource() || cur_src->IsLanClient() ) // modified by sivka - LanCast and non Valid SRCs, no need to save
			continue;
		if (srcstosave.IsEmpty()) {
			sourcedata = new CSourceData(cur_src,CalcExpiration(EXPIREIN));
			srcstosave.AddHead(sourcedata);
			continue;
		}
		// Skip also Required Obfuscation, because we don't save the userhash (and we don't know if all settings are still valid on next restart)
		if (cur_src->RequiresCryptLayer() ||thePrefs.IsClientCryptLayerRequired())
			continue;
		if ((srcstosave.GetCount() < SOURCESTOSAVE) || (cur_src->GetAvailablePartCount() > srcstosave.GetTail()->partsavailable)) 
		{
			if (srcstosave.GetCount() == SOURCESTOSAVE)
				delete srcstosave.RemoveTail();
			ASSERT(srcstosave.GetCount() < SOURCESTOSAVE);
			bool bInserted = false;
			for(POSITION pos3, pos2 = srcstosave.GetTailPosition(); (pos3=pos2)!=NULL; ){
				CSourceData* cur_srctosave = srcstosave.GetPrev(pos2);
				if (cur_srctosave->partsavailable > cur_src->GetAvailablePartCount()) {
					sourcedata = new CSourceData(cur_src,CalcExpiration(EXPIREIN));
					srcstosave.InsertAfter(pos3, sourcedata);
					bInserted = true;
					break;
				}
			}
			if (!bInserted) {
				sourcedata = new CSourceData(cur_src,CalcExpiration(EXPIREIN));
				srcstosave.AddHead(sourcedata);
			}
		}
	}
	
	// Add previously saved sources if found sources does not reach the limit
	for(POSITION pos4 = prevsources->GetHeadPosition(); pos4; ) {
		CSourceData* cur_sourcedata = prevsources->GetNext(pos4);
		if (srcstosave.GetCount() == SOURCESTOSAVE)
			break;
		ASSERT(srcstosave.GetCount() <= SOURCESTOSAVE);
		bool bFound = false;
		for(POSITION pos5 = srcstosave.GetHeadPosition(); pos5; ) {
			if (srcstosave.GetNext(pos5)->Compare(cur_sourcedata)) {
				bFound = true;
				break;
			}
		}
		if (!bFound) {
			srcstosave.AddTail(new CSourceData(cur_sourcedata));
		}
			
	}

	CString strLine;
	CStdioFile f;
	if (!f.Open(slsfile, CFile::modeCreate | CFile::modeWrite | CFile::typeText))
		return;
	f.WriteString(_T("#format: a.b.c.d:port,expirationdate(yymmddhhmm);\n"));
	while (!srcstosave.IsEmpty()) {
		CSourceData* cur_src = srcstosave.RemoveHead();
		UINT uID = cur_src->sourceID;
		uint16 uPort = cur_src->sourcePort;
		strLine.Format(_T("%u.%u.%u.%u:%u,%s;\n"), (uint8)uID,(uint8)(uID>>8),(uint8)(uID>>16),(uint8)(uID>>24),uPort,cur_src->strExpiration);
		delete cur_src;
		f.WriteString(strLine);
	}
	f.Close();
}

CString CSourceSaver::CalcExpiration(int nMinutes) //modified by sivka days -> hours
{
	CTime tExpirationTemp = CTime::GetCurrentTime();
	CTimeSpan timediff(0, 0, nMinutes, 0); //modified by sivka
	tExpirationTemp += timediff;

	return tExpirationTemp.Format(_T("%y%m%d%H%M")); //modified by sivka
}

bool CSourceSaver::IsExpired(CString strExpirationDate)
{
	int year = _ttoi(strExpirationDate.Mid(0, 2)) + 2000;
	int month = _ttoi(strExpirationDate.Mid(2, 2));
	int day = _ttoi(strExpirationDate.Mid(4, 2));
	int hour = _ttoi(strExpirationDate.Mid(6, 2)); //added by sivka
	int minute = _ttoi(strExpirationDate.Mid(8, 2)); //added by sivka

	CTime tExpirationTemp(year, month, day, hour, minute, 0); //modified by sivka
	return (tExpirationTemp < CTime::GetCurrentTime());
}
