// parts of this file are based on work from pan One (http://home-3.tiscali.nl/~meost/pms/)

//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 "knownfile.h"
#include "opcodes.h"
#include <io.h>
#include <sys/types.h>
#include <sys/stat.h>
#include "emule.h"
#include "ini2.h"
	
void CFileStatistic::AddRequest(){
	requested++;
	alltimerequested++;
	theApp.knownfiles->requested++;
	theApp.sharedfiles->UpdateItem(fileParent);
}
	
void CFileStatistic::AddAccepted(){
	accepted++;
	alltimeaccepted++;
	theApp.knownfiles->accepted++;
	theApp.sharedfiles->UpdateItem(fileParent);
}
	
void CFileStatistic::AddTransferd(uint32 bytes){
	transfered += bytes;
	alltimetransfered += bytes;
	theApp.knownfiles->transfered += bytes;
	theApp.sharedfiles->UpdateItem(fileParent);
}

CKnownFile::CKnownFile(){
	filetype = 2;
	directory = 0;
	filename = 0;
	filesize = 0;
	date = 0;
	m_iPriority = PR_NORMAL;
	m_iPermissions = PERM_ALL;
	statistic.fileParent=this;
	m_bAutoPriority = false ; //UAP Hunter
	m_bCommentLoaded=false;
	m_iRate=0;
	m_strComment="";
}

CKnownFile::~CKnownFile(){
	for (int i = 0; i != hashlist.GetSize(); i++)
		if (hashlist[i])
			delete[] hashlist[i];
	for (int i = 0; i != taglist.GetSize(); i++)
		safe_delete(taglist[i]);
//	if (filename)	// done by CAbstractFile destructor
//		delete[] filename;
	if (directory)
		delete[] directory;
}

void CKnownFile::SetPath(char* path){
	if (directory)
		delete[] directory;
	directory = nstrdup(path);
}

bool CKnownFile::CreateFromFile(char* in_directory,char* in_filename){
	// TODO Errorhandling
	//first create the filehashset
	// open file
	directory = nstrdup(in_directory);

	char* namebuffer = new char[strlen(in_directory)+strlen(in_filename)+2];
	sprintf(namebuffer,"%s\\%s",in_directory,in_filename);
	FILE* file = fopen(namebuffer,"rbS");
	delete namebuffer;
	if (!file)
		return false;
	// set filesize + name
	filename = nstrdup(in_filename);
	filesize =_filelength(file->_file);
	// create hashset
	uint32 togo = filesize;
	for (uint16 hashcount = 0;togo >= PARTSIZE;) {
		uchar* newhash = new uchar[16];
		CreateHashFromFile(file,PARTSIZE,newhash);
		hashlist.Add(newhash);
		togo -= PARTSIZE;
		hashcount++;
	}
	uchar* lasthash = new uchar[16];
	memset(lasthash,0,16);
	CreateHashFromFile(file,togo,lasthash);
	if (!hashcount){
		memcpy(filehash,lasthash,16);
		delete[] lasthash; // i_a: memleak 
	} 
	else {
		hashlist.Add(lasthash);
		uchar* buffer = new uchar[hashlist.GetCount()*16];
		for (int i = 0;i != hashlist.GetCount();i++)
			memcpy(buffer+(i*16),hashlist[i],16);
		CreateHashFromString(buffer,hashlist.GetCount()*16,filehash);
		delete[] buffer;
	}
	// TODO: Add filetags

	// set lastwrite date
	struct _stat fileinfo;
	_fstat(file->_file,&fileinfo);
	date = fileinfo.st_mtime;
	//finished
	fclose(file);
	return true;	
}


// needed for memfiles. its probably better to switch everything to CFile...
bool CKnownFile::LoadHashsetFromFile(CFile* file, bool checkhash){
	uchar checkid[16];
	file->Read(&checkid,16);
	uint16	parts;
	file->Read(&parts,2);
	for (int i = 0; i != parts; i++){
		uchar* cur_hash = new uchar[16];
		file->Read(cur_hash,16);
		hashlist.Add(cur_hash);
	}
	if (!checkhash){
		memcpy(filehash,checkid,16);
		return true;
	}
	// trust noone ;-)

	if (!hashlist.IsEmpty()){
		uchar* buffer = new uchar[hashlist.GetCount()*16];
		for (int i = 0;i != hashlist.GetCount();i++)
			memcpy(buffer+(i*16),hashlist[i],16);
		CreateHashFromString(buffer,hashlist.GetCount()*16,checkid);
		delete[] buffer;
	}
	if (!memcmp(filehash,checkid,16))
		return true;
	else{
		for (int i = 0; i != hashlist.GetSize(); i++)
			delete[] hashlist[i];
		hashlist.RemoveAll();
		return false;
	}
}

bool CKnownFile::LoadTagsFromFile(CFile* file){
	uint32 tagcount;
	file->Read(&tagcount,4);
	for (uint32 j = 0; j != tagcount;j++){
		CTag* newtag = new CTag(file);
		switch(newtag->tag->specialtag){
			case FT_FILENAME:{
				filename = nstrdup(newtag->tag->stringvalue);
				delete newtag;
				break;
			}
			case FT_FILESIZE:{
				filesize = newtag->tag->intvalue;
				delete newtag;
				break;
			}
			case FT_ATTRANSFERED:{
				statistic.alltimetransfered = newtag->tag->intvalue;
				delete newtag;
				break;
			}
			case FT_ATREQUESTED:{
				statistic.alltimerequested = newtag->tag->intvalue;
				delete newtag;
				break;
			}
 			case FT_ATACCEPTED:{
				statistic.alltimeaccepted = newtag->tag->intvalue;
				delete newtag;
				break;
			}
			case FT_PRIORITY:{
				m_iPriority = newtag->tag->intvalue;
				delete newtag;
				break;
			}
			case FT_PERMISSIONS:{
				m_iPermissions = newtag->tag->intvalue;
				delete newtag;
				break;
			}
			default:
				taglist.Add(newtag);
		}	
	}
	return true;
}

bool CKnownFile::LoadDateFromFile(CFile* file){
	file->Read(&date,4);
	return true;
}

bool CKnownFile::LoadFromFile(CFile* file){
	return (LoadDateFromFile(file) && LoadHashsetFromFile(file,false) && LoadTagsFromFile(file)); 
}

bool CKnownFile::WriteToFile(FILE* file){
	// date
	fwrite(&date,4,1,file); 
	// hashset
	fwrite(&filehash,16,1,file);
	uint16 parts = hashlist.GetCount();
	fwrite(&parts,2,1,file);
	for (int i = 0; i != parts; i++)
		fwrite(hashlist[i],16,1,file);
	//tags
	uint32 tagcount = taglist.GetCount()+7;
	// standard tags
	fwrite(&tagcount,4,1,file);
	CTag* nametag = new CTag(FT_FILENAME,filename);
	nametag->WriteTagToFile(file);
	delete nametag;
	CTag* sizetag = new CTag(FT_FILESIZE,filesize);
	sizetag->WriteTagToFile(file);
	delete sizetag;
	// statistic
	CTag attag1(FT_ATTRANSFERED,statistic.GetAllTimeTransfered());
	attag1.WriteTagToFile(file);
	CTag attag2(FT_ATREQUESTED,statistic.GetAllTimeRequests());
	attag2.WriteTagToFile(file);
	CTag attag3(FT_ATACCEPTED,statistic.GetAllTimeAccepts());
	attag3.WriteTagToFile(file);
	// priority N permission
	CTag priotag(FT_PRIORITY, m_iPriority);
	priotag.WriteTagToFile(file);
	CTag permtag(FT_PERMISSIONS, m_iPermissions);
	permtag.WriteTagToFile(file);
		//other tags
	for (uint32 j = 0; j != tagcount-7;j++)
		taglist[j]->WriteTagToFile(file);
	return ferror(file);
}

void CKnownFile::CreateHashFromInput(FILE* file,CFile* file2, int Length, uchar* Output, uchar* in_string) { 
	// time critial
	bool PaddingStarted = false;
	uint32 Hash[4];
	Hash[0] = 0x67452301;
	Hash[1] = 0xEFCDAB89;
	Hash[2] = 0x98BADCFE;
	Hash[3] = 0x10325476;
	CFile* data = 0;
	if (in_string)
		data = new CMemFile(in_string,Length);
	uint32 Required = Length;
	uchar   X[64*128];  
	while (Required >= 64){
        uint32 len = Required / 64; 
        if (len > sizeof(X)/(64 * sizeof(X[0]))) 
             len = sizeof(X)/(64 * sizeof(X[0])); 
		if (in_string)
			data->Read(&X,len*64);
		else if (file)
            fread(&X,len*64,1,file); 
		else if (file2)
			file2->Read(&X,len*64);
		for (uint32 i = 0; i < len; i++) 
        { 
           MD4Transform(Hash, (uint32*)(X + i*64)); 
        }
		Required -= len*64;
	}
	// bytes to read
	Required = Length % 64;
	if (Required != 0){
		if (in_string)
			data->Read(&X,Required);
		else if (file)
			fread(&X,Required,1,file);
		else if (file2)
			file2->Read(&X,Required);
	}
	// in byte scale 512 = 64, 448 = 56
	if (Required >= 56){
		X[Required] = 0x80;
		PaddingStarted = TRUE;
		memset(&X[Required + 1], 0, 63 - Required);
		MD4Transform(Hash, (uint32*)X);
		Required = 0;
	}
	if (!PaddingStarted)
		X[Required++] = 0x80;
	memset(&X[Required], 0, 64 - Required);
	// add size (convert to bits)
	uint32 Length2 = Length >> 29;
	Length <<= 3;
	memcpy(&X[56], &Length, 4);
	memcpy(&X[60], &Length2, 4);
	MD4Transform(Hash, (uint32*)X);
	memcpy(Output, Hash, 16);
	safe_delete(data);
}
uchar* CKnownFile::GetPartHash(uint16 part){
	if (part >= hashlist.GetCount())
		return 0;
	return hashlist[part];
}

uint16 CKnownFile::GetPartCount(){
	return (filesize/PARTSIZE) + ((filesize % PARTSIZE)? 1:0);
}

static void MD4Transform(uint32 Hash[4], uint32 x[16])
{
  uint32 a = Hash[0];
  uint32 b = Hash[1];
  uint32 c = Hash[2];
  uint32 d = Hash[3];

  /* Round 1 */
  MD4_FF(a, b, c, d, x[ 0], S11); // 01
  MD4_FF(d, a, b, c, x[ 1], S12); // 02
  MD4_FF(c, d, a, b, x[ 2], S13); // 03
  MD4_FF(b, c, d, a, x[ 3], S14); // 04
  MD4_FF(a, b, c, d, x[ 4], S11); // 05
  MD4_FF(d, a, b, c, x[ 5], S12); // 06
  MD4_FF(c, d, a, b, x[ 6], S13); // 07
  MD4_FF(b, c, d, a, x[ 7], S14); // 08
  MD4_FF(a, b, c, d, x[ 8], S11); // 09
  MD4_FF(d, a, b, c, x[ 9], S12); // 10
  MD4_FF(c, d, a, b, x[10], S13); // 11
  MD4_FF(b, c, d, a, x[11], S14); // 12
  MD4_FF(a, b, c, d, x[12], S11); // 13
  MD4_FF(d, a, b, c, x[13], S12); // 14
  MD4_FF(c, d, a, b, x[14], S13); // 15
  MD4_FF(b, c, d, a, x[15], S14); // 16

  /* Round 2 */
  MD4_GG(a, b, c, d, x[ 0], S21); // 17
  MD4_GG(d, a, b, c, x[ 4], S22); // 18
  MD4_GG(c, d, a, b, x[ 8], S23); // 19
  MD4_GG(b, c, d, a, x[12], S24); // 20
  MD4_GG(a, b, c, d, x[ 1], S21); // 21
  MD4_GG(d, a, b, c, x[ 5], S22); // 22
  MD4_GG(c, d, a, b, x[ 9], S23); // 23
  MD4_GG(b, c, d, a, x[13], S24); // 24
  MD4_GG(a, b, c, d, x[ 2], S21); // 25
  MD4_GG(d, a, b, c, x[ 6], S22); // 26
  MD4_GG(c, d, a, b, x[10], S23); // 27
  MD4_GG(b, c, d, a, x[14], S24); // 28
  MD4_GG(a, b, c, d, x[ 3], S21); // 29
  MD4_GG(d, a, b, c, x[ 7], S22); // 30
  MD4_GG(c, d, a, b, x[11], S23); // 31
  MD4_GG(b, c, d, a, x[15], S24); // 32

  /* Round 3 */
  MD4_HH(a, b, c, d, x[ 0], S31); // 33
  MD4_HH(d, a, b, c, x[ 8], S32); // 34
  MD4_HH(c, d, a, b, x[ 4], S33); // 35
  MD4_HH(b, c, d, a, x[12], S34); // 36
  MD4_HH(a, b, c, d, x[ 2], S31); // 37
  MD4_HH(d, a, b, c, x[10], S32); // 38
  MD4_HH(c, d, a, b, x[ 6], S33); // 39
  MD4_HH(b, c, d, a, x[14], S34); // 40
  MD4_HH(a, b, c, d, x[ 1], S31); // 41
  MD4_HH(d, a, b, c, x[ 9], S32); // 42
  MD4_HH(c, d, a, b, x[ 5], S33); // 43
  MD4_HH(b, c, d, a, x[13], S34); // 44
  MD4_HH(a, b, c, d, x[ 3], S31); // 45
  MD4_HH(d, a, b, c, x[11], S32); // 46
  MD4_HH(c, d, a, b, x[ 7], S33); // 47
  MD4_HH(b, c, d, a, x[15], S34); // 48

  Hash[0] += a;
  Hash[1] += b;
  Hash[2] += c;
  Hash[3] += d;
}

// Adde by Tarod [Juanjo]
void CAbstractFile::SetFileName(char* NewName) 
{ 
   if (filename != NULL) { 
      delete[] filename; 
      filename = new char[strlen(NewName) + 1]; 
      
	  CString filenametemp;
	  filenametemp=NewName;//.Format("%s",NewName);
	  filenametemp.Replace('/','-');
	  filenametemp.Replace('>','-');
	  filenametemp.Replace('<','-');
	  filenametemp.Replace('*','-');
	  filenametemp.Replace(':','-');
	  filenametemp.Replace('?','-');

	  sprintf(filename, "%s", filenametemp);
   } 
} 
//End by Tarod

Packet*	CKnownFile::CreateSrcInfoPacket(CUpDownClient* forClient)
{
	CTypedPtrList<CPtrList, CUpDownClient*> srclist;
	theApp.uploadqueue->FindSourcesForFileById(&srclist, forClient->reqfileid); //should we use "filehash"?

	if(srclist.IsEmpty())
		return 0;

	CMemFile data;
	uint16 nCount = 0;

	data.Write(forClient->reqfileid, 16);
	data.Write(&nCount, 2);

	uint32 lastRequest = forClient->GetLastSrcReqTime();
	for (POSITION pos = srclist.GetHeadPosition(); pos != NULL; srclist.GetNext(pos)) {
		CUpDownClient *cur_src = srclist.GetAt(pos);

		// Sometimes pointer is gone before it's sent
		try {

			// Modified by Tarod -sends sources with LowID that are in same sever
			// that client is requesting sources
			if (cur_src != forClient 
				&& (cur_src->HasLowID() && (cur_src->GetServerIP() == forClient->GetServerIP() && cur_src->GetServerPort() == forClient->GetServerPort()))) {

				nCount++;
				uint32 dwID = cur_src->GetUserID();
				uint16 nPort = cur_src->GetUserPort();
				uint32 dwServerIP = cur_src->GetServerIP();
				uint16 nServerPort = cur_src->GetServerPort();
				data.Write(&dwID, 4);
				data.Write(&nPort, 2);
				data.Write(&dwServerIP, 4);
				data.Write(&nServerPort, 2);
			}

			srclist.RemoveAt(pos);
			if(srclist.GetSize() == 0 || nCount > 500)
				break;
		
		// Something was wrong, so abort sendind sources
		} catch(...) {
			nCount = 0;				// Fakes no sources found
			break;					// Take us out of here
		}
	}

	// Be sure that we clean list and do not leak memory
	srclist.RemoveAll();

	if (!nCount)
		return 0;
	data.Seek(16,0);
	data.Write(&nCount,2);

	Packet* result = new Packet(&data, OP_EMULEPROT);
	result->opcode = OP_ANSWERSOURCES;
	if (nCount > 28)
		result->PackPacket();

	return result;
}

// Updates priority of file if autopriority is activated
void CKnownFile::UpdateUploadAutoPriority(void)
{
	if (this->IsAutoPrioritized()) {
		if (!theApp.glob_prefs->IsUAPEnabled()){
			this->SetAutoPriority(false);
			this->SetPriority(PR_NORMAL);
			return;
		}

		CPartFile* partfile = theApp.downloadqueue->GetFileByID(this->GetFileHash()) ;
		if (partfile == NULL) return ;

		if (partfile->IsPartFile()) {
			if (partfile->GetSourceCount() <= RARE_FILE)
				this->SetPriority(PR_VERYHIGH);
			else if (partfile->GetSourceCount() < RARE_FILE*25)
				this->SetPriority(PR_HIGH);
			else
				this->SetPriority(PR_NORMAL);
		}
	}
}

//For File Comment // 
void CKnownFile::LoadComment(){ 
   char buffer[100]; 
   char* fullpath = new char[strlen(theApp.glob_prefs->GetAppDir())+13]; 
   sprintf(fullpath,"%sfileinfo.ini",theApp.glob_prefs->GetAppDir()); 
   
   buffer[0] = 0;
   for (uint16 i = 0;i != 16;i++) 
      sprintf(buffer,"%s%02X",buffer,filehash[i]); 
    
   CIni ini( fullpath, buffer); 
   m_strComment = ini.GetString("Comment"); 
   m_iRate = ini.GetInt("Rate", 0);//For rate
   m_bCommentLoaded=true;
   delete fullpath;
    
}    

void CKnownFile::SetFileComment(CString strNewComment){ 
   char buffer[100]; 
   char* fullpath = new char[strlen(theApp.glob_prefs->GetAppDir())+13]; 
   sprintf(fullpath,"%sfileinfo.ini",theApp.glob_prefs->GetAppDir()); 
       
   buffer[0] = 0; 
   for (uint16 i = 0;i != 16;i++) 
      sprintf(buffer,"%s%02X",buffer,filehash[i]); 
    
   CIni ini( fullpath, buffer ); 
    
   ini.WriteString ("Comment", strNewComment); 
   m_strComment = strNewComment;
   delete fullpath;
   
   CTypedPtrList<CPtrList, CUpDownClient*> srclist;
   theApp.uploadqueue->FindSourcesForFileById(&srclist, this->GetFileHash());

   for (POSITION pos = srclist.GetHeadPosition();pos != 0;srclist.GetNext(pos)){
	CUpDownClient *cur_src = srclist.GetAt(pos);
	cur_src->SetCommentDirty();
   }
   
}
// For File rate 
void CKnownFile::SetFileRate(int8 iNewRate){ 
   char buffer[100]; 
   char* fullpath = new char[strlen(theApp.glob_prefs->GetAppDir())+13]; 
   sprintf(fullpath,"%sfileinfo.ini",theApp.glob_prefs->GetAppDir()); 
       
   buffer[0] = 0; 
   for (uint16 i = 0;i != 16;i++) 
      sprintf(buffer,"%s%02X",buffer,filehash[i]); 
    
   CIni ini( fullpath, buffer ); 
    
   ini.WriteInt ("Rate", iNewRate); 
   m_iRate = iNewRate; 
   delete fullpath;

  CTypedPtrList<CPtrList, CUpDownClient*> srclist;
  theApp.uploadqueue->FindSourcesForFileById(&srclist, this->GetFileHash());
  for (POSITION pos = srclist.GetHeadPosition();pos != 0;srclist.GetNext(pos)){
	CUpDownClient *cur_src = srclist.GetAt(pos);
	cur_src->SetCommentDirty();
  }
} 

// Added by Tarod to push RF files
float CKnownFile::GetFileRatio(){
	// MatzeHH formula I think is better than herbert's one (Tarod)
	if (statistic.GetRequests() == 0) return 2.0f ;

	float ratio ;
	ratio = ((float)statistic.GetRequests()) / ((float)theApp.knownfiles->GetTotalRequested()) ;
	ratio = -4.0f * log10f(((float)theApp.knownfiles->GetCount())) * ratio ;
	ratio = 2.0f * expf(ratio) ;

	if (ratio < 1.0f) {
		ratio = 1.0f;
	} else if (ratio > 2.0f)
		ratio = 2.0f;

	return ratio ;
}
// End by Tarod
