/***************************************************************************
 *   Copyright (C) 2005 by Joris Guisson                                   *
 *   joris.guisson@gmail.com                                               *
 *                                                                         *
 *   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.,                                       *
 *   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.             *
 ***************************************************************************/
#include <tqdir.h>
#include <tqfileinfo.h>
#include <tdelocale.h>
#include <time.h>
#include <util/error.h>
#include <ktversion.h>
#include "torrentcontrol.h"
#include "torrentcreator.h"
#include "bencoder.h"
#include <util/file.h>
#include <util/sha1hash.h>
#include <util/fileops.h>
#include <util/log.h>
#include <util/array.h>
#include <util/functions.h>
#include "globals.h"
#include "chunkmanager.h"
#include "statsfile.h"

namespace bt
{

	TorrentCreator::TorrentCreator(const TQString & tar,
								   const TQStringList & track,
								   Uint32 cs,
								   const TQString & name,
								   const TQString & comments,bool priv, bool decentralized)
	: target(tar),trackers(track),chunk_size(cs),
	name(name),comments(comments),cur_chunk(0),priv(priv),tot_size(0), decentralized(decentralized)
	{
		this->chunk_size *= 1024;
		TQFileInfo fi(target);
		if (fi.isDir())
		{
			if (!this->target.endsWith(bt::DirSeparator()))
				this->target += bt::DirSeparator();
			
			tot_size = 0;
			buildFileList("");
			num_chunks = tot_size / chunk_size;
			if (tot_size % chunk_size > 0)
				num_chunks++;
			last_size = tot_size % chunk_size;
			Out() << "Tot Size : " << tot_size << endl;
		}
		else
		{
			tot_size = bt::FileSize(target);
			num_chunks = tot_size / chunk_size;
			if (tot_size % chunk_size > 0)
				num_chunks++;
			last_size = tot_size % chunk_size;
			Out() << "Tot Size : " << tot_size << endl;
		}

		if (last_size == 0)
			last_size = chunk_size;

		Out() << "Num Chunks : " << num_chunks << endl;
		Out() << "Chunk Size : " << chunk_size << endl;
		Out() << "Last Size : " << last_size << endl;
	}


	TorrentCreator::~TorrentCreator()
	{}

	void TorrentCreator::buildFileList(const TQString & dir)
	{
		TQDir d(target + dir);
		// first get all files (we ignore symlinks)
		TQStringList dfiles = d.entryList(TQDir::Files|TQDir::NoSymLinks);
		Uint32 cnt = 0; // counter to keep track of file index
		for (TQStringList::iterator i = dfiles.begin();i != dfiles.end();++i)
		{
			// add a TorrentFile to the list
			Uint64 fs = bt::FileSize(target + dir + *i);
			TorrentFile f(cnt,dir + *i,tot_size,fs,chunk_size);
			files.append(f);
			// update total size
			tot_size += fs;
			cnt++;
		}

		// now for each subdir do a buildFileList 
		TQStringList subdirs = d.entryList(TQDir::Dirs|TQDir::NoSymLinks);
		for (TQStringList::iterator i = subdirs.begin();i != subdirs.end();++i)
		{
			if (*i == "." || *i == "..")
				continue;
			
			TQString sd = dir + *i;
			if (!sd.endsWith(bt::DirSeparator()))
				sd += bt::DirSeparator();
			buildFileList(sd);
		}
	}


	void TorrentCreator::saveTorrent(const TQString & url)
	{
		File fptr;
		if (!fptr.open(url,"wb"))
			throw Error(i18n("Cannot open file %1: %2").arg(url).arg(fptr.errorString()));

		BEncoder enc(&fptr);
		enc.beginDict(); // top dict
		
		if(!decentralized)
		{
			enc.write(TQString("announce")); enc.write(trackers[0]);
			if (trackers.count() > 1)
			{
				enc.write(TQString("announce-list"));
				enc.beginList();
				enc.beginList();
				for (Uint32 i = 0;i < trackers.count();i++)
					enc.write(trackers[i]);
				enc.end();
				enc.end();
			
			}
		}
		
		
		if (comments.length() > 0)
		{
			enc.write(TQString("comments"));
			enc.write(comments);
		}
		enc.write(TQString("created by"));enc.write(TQString("KTorrent %1").arg(kt::VERSION_STRING));
		enc.write(TQString("creation date"));enc.write((Uint64)time(0));
		enc.write(TQString("info"));
		saveInfo(enc);
		// save the nodes list after the info hash, keys must be sorted !
		if (decentralized)
		{
			//DHT torrent
			enc.write(TQString("nodes"));
			enc.beginList();
			
			for(int i=0; i < trackers.count(); ++i)
			{
				TQString t = trackers[i];
				enc.beginList();
				enc.write(t.section(',',0,0));
				enc.write((Uint32)t.section(',',1,1).toInt());
				enc.end();
			}
			enc.end();
		}
		
		enc.end();
	}

	void TorrentCreator::saveInfo(BEncoder & enc)
	{
		enc.beginDict();
		
		TQFileInfo fi(target);
		if (fi.isDir())
		{
			enc.write(TQString("files"));
			enc.beginList();
			TQValueList<TorrentFile>::iterator i = files.begin();
			while (i != files.end())
			{
				saveFile(enc,*i);
				i++;
			}
			enc.end();
		}
		else
		{
			enc.write(TQString("length")); enc.write(bt::FileSize(target));
		}
		enc.write(TQString("name")); enc.write(name);
		enc.write(TQString("piece length")); enc.write((Uint64)chunk_size);
		enc.write(TQString("pieces")); savePieces(enc);
		if (priv)
		{
			enc.write(TQString("private")); 
			enc.write((Uint64)1);
		}
		enc.end();
	}

	void TorrentCreator::saveFile(BEncoder & enc,const TorrentFile & file)
	{
		enc.beginDict();
		enc.write(TQString("length"));enc.write(file.getSize());
		enc.write(TQString("path"));
		enc.beginList();
		TQStringList sl = TQStringList::split(bt::DirSeparator(),file.getPath());
		for (TQStringList::iterator i = sl.begin();i != sl.end();i++)
			enc.write(*i);
		enc.end();
		enc.end();
	}

	void TorrentCreator::savePieces(BEncoder & enc)
	{
		if (hashes.empty())
			while (!calculateHash())
				;

		Array<Uint8> big_hash(num_chunks*20);
		for (Uint32 i = 0;i < num_chunks;++i)
		{
			memcpy(big_hash+(20*i),hashes[i].getData(),20);
		}
		enc.write(big_hash,num_chunks*20);
	}

	bool TorrentCreator::calcHashSingle()
	{
		Array<Uint8> buf(chunk_size);
		File fptr;
		if (!fptr.open(target,"rb"))
			throw Error(i18n("Cannot open file %1: %2")
					.arg(target).arg(fptr.errorString()));

		Uint32 s = cur_chunk != num_chunks - 1 ? chunk_size : last_size;
		fptr.seek(File::BEGIN,(Int64)cur_chunk*chunk_size);
			
		fptr.read(buf,s);
		SHA1Hash h = SHA1Hash::generate(buf,s);
		hashes.append(h);
		cur_chunk++;
		return cur_chunk >= num_chunks;
	}
	
	bool TorrentCreator::calcHashMulti()
	{	
		Uint32 s = cur_chunk != num_chunks - 1 ? chunk_size : last_size;
		// first find the file(s) the chunk lies in
		Array<Uint8> buf(s);
		TQValueList<TorrentFile> file_list;
		Uint32 i = 0;
		while (i < files.size())
		{
			const TorrentFile & tf = files[i];
			if (cur_chunk >= tf.getFirstChunk() && cur_chunk <= tf.getLastChunk())
			{
				file_list.append(tf);
			}
				
			i++;
		}

		Uint32 read = 0;
		for (i = 0;i < file_list.count();i++)
		{
			const TorrentFile & f = file_list[i];
			File fptr;
			if (!fptr.open(target + f.getPath(),"rb"))
			{
				throw Error(i18n("Cannot open file %1: %2")
						.arg(f.getPath()).arg(fptr.errorString()));
			}

			// first calculate offset into file
			// only the first file can have an offset
			// the following files will start at the beginning
			Uint64 off = 0;
			if (i == 0)
				off = f.fileOffset(cur_chunk,chunk_size);
			
			Uint32 to_read = 0;
			// then the amount of data we can read from this file
			if (file_list.count() == 1)
				to_read = s;
			else if (i == 0)
				to_read = f.getLastChunkSize();
			else if (i == file_list.count() - 1)
				to_read = s - read;
			else
				to_read = f.getSize();
						
			// read part of data
			fptr.seek(File::BEGIN,(Int64)off);
			fptr.read(buf + read,to_read);
			read += to_read;
		}

		// generate hash
		SHA1Hash h = SHA1Hash::generate(buf,s);
		hashes.append(h);

		cur_chunk++;
	//	Out() << "=============================================" << endl;
		return cur_chunk >= num_chunks;
	}
	
	bool TorrentCreator::calculateHash()
	{
		if (cur_chunk >= num_chunks)
			return true;
		if (files.empty())
			return calcHashSingle();
		else
			return calcHashMulti();
	}
	
	TorrentControl* TorrentCreator::makeTC(const TQString & data_dir)
	{
		TQString dd = data_dir;
		if (!dd.endsWith(bt::DirSeparator()))
			dd += bt::DirSeparator();

		// make data dir if necessary
		if (!bt::Exists(dd))
			bt::MakeDir(dd);

		// save the torrent
		saveTorrent(dd + "torrent");		
		// write full index file
		File fptr;
		if (!fptr.open(dd + "index","wb"))
			throw Error(i18n("Cannot create index file: %1").arg(fptr.errorString()));

		for (Uint32 i = 0;i < num_chunks;i++)
		{
			NewChunkHeader hdr;
			hdr.index = i;
			fptr.write(&hdr,sizeof(NewChunkHeader));
		}
		fptr.close();

		// now create the torrentcontrol object
		TorrentControl* tc = new TorrentControl();
		try
		{
			// get the parent dir of target
			TQFileInfo fi = TQFileInfo(target);
			
			TQString odir;
			StatsFile st(dd + "stats");
			if (fi.fileName() == name)
			{
				st.write("OUTPUTDIR", fi.dirPath(true));
				odir = fi.dirPath(true);
			}
			else
			{
				st.write("CUSTOM_OUTPUT_NAME","1");
				st.write("OUTPUTDIR", target);
				odir = target;
			}
			st.write("UPLOADED", "0");
			st.write("RUNNING_TIME_DL","0");
			st.write("RUNNING_TIME_UL","0");
			st.write("PRIORITY", "0");
			st.write("AUTOSTART", "1");
			st.write("IMPORTED", TQString::number(tot_size));
			st.writeSync();
			
			tc->init(0,dd + "torrent",dd,odir,TQString());
			tc->createFiles();
		}
		catch (...)
		{
			delete tc;
			throw;
		}
		
		return tc;
	}
}
