/*
 *  Copyright (C) 2005 Alo Sarv <madcat_@users.sourceforge.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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

/**
 * \file files.cpp     Implementation of TorrentFile and PartialTorrent classes
 */

#include <hncore/bt/files.h>

namespace Bt {

// TorrentFile class
// -----------------
TorrentFile::TorrentFile(const std::vector<SharedFile*> &files) {
	CHECK_THROW(files.size());

	uint64_t offset = 0;
	for (uint32_t i = 0; i < files.size(); ++i) {
		m_children[offset] = files[i];
		offset += files[i]->getSize();
	}
	m_size = offset + 1;

	logDebug("TorrentFile, childmap:");
	boost::format fmt("[%11d] %s (%d bytes)");
	for (Iter i = m_children.begin(); i != m_children.end(); ++i) {
		logDebug(
			fmt % (*i).first % (*i).second->getName()
			% (*i).second->getSize()
		);
	}
}

TorrentFile::~TorrentFile() {}

std::string TorrentFile::read(uint64_t begin, uint64_t end) {
	Iter i = m_children.upper_bound(begin);
	CHECK_THROW(i != m_children.end());
	begin -= (*i).first;
	end   -= (*i).first;
	std::string tmpData;
	while (end > (*i).second->getSize()) {
		tmpData += (*i).second->read(begin, (*i).second->getSize());
		begin = 0;
		end -= (*i).second->getSize();
		++i;
		assert(i != m_children.end());
	}
	return tmpData;
}

// PartialTorrent class
// --------------------
PartialTorrent::PartialTorrent(const std::vector<PartData*> &files) {
	CHECK_THROW(files.size());

	uint64_t offset = 0;
	for (uint32_t i = 0; i < files.size(); ++i) {
		m_children[offset] = files[i];
		m_childrenReverse[files[i]] = offset;

		offset += files[i]->getSize();
		files[i]->dataAdded.connect(
			boost::bind(&PartialTorrent::onDataAdded,
			this, _1, _2, _3)
		);
		files[i]->onCorruption.connect(
			boost::bind(&PartialTorrent::onChildCorruption,
			this, _1, _2, _3)
		);
	}
	m_size = offset + 1;

	logDebug("PartialTorrent, childmap:");
	boost::format fmt("[%11d] %s (%d bytes)");
	for (Iter i = m_children.begin(); i != m_children.end(); ++i) {
		logDebug(
			fmt % (*i).first % (*i).second->getName()
			% (*i).second->getSize()
		);
	}
}

PartialTorrent::~PartialTorrent() {}

void PartialTorrent::doWrite(uint64_t begin, const std::string &data) {
	Iter i = m_children.lower_bound(begin);
	CHECK_THROW(i != m_children.end());
	begin -= (*i).first;
	uint32_t pos = 0;
	while (begin + data.size() > (*i).second->getSize()) {
		PartData *pd = (*i).second;
		pd->write(begin, data.substr(pos, pd->getSize() - begin));
		pos += pd->getSize() - begin;
		++i;
		begin = 0;
	}
	if (data.size()) {
		assert(i != m_children.end());
		(*i).second->write(0, data);
	}
}

void PartialTorrent::onDataAdded(PartData *f, uint64_t offset, uint32_t amount){
	RIter i = m_childrenReverse.find(f);
	assert(i != m_childrenReverse.end());
	m_complete.merge(offset + (*i).second, offset + (*i).second + amount);
}

void PartialTorrent::verifyRange(Range64 range, const HashBase *ref) {
	boost::shared_ptr<HashWork> c;

	std::vector<boost::filesystem::path> files;
	Iter i = m_children.lower_bound(range.begin());
	files.push_back((*i).second->getLocation());
	range.begin(range.begin() - (*i).first);
	while (++i != m_children.end() && (*i).first < range.end()) {
		files.push_back((*i).second->getLocation());
	}
	range.end(range.end() - (*i).first);

	c.reset(new TorrentHasher(files, range.begin(), range.end(), ref));
	c->getEventTable().addHandler(
		c, dynamic_cast<PartData*>(this), &PartData::onHashEvent
	);

	WorkThread::instance().postWork(c);
}

void PartialTorrent::corruption(uint64_t begin, uint64_t end) {
	Iter i = m_children.lower_bound(begin);
	assert(i != m_children.end());
	begin -= (*i).first;
	while (end - (*i).first > (*i).second->getSize()) {
		(*i).second->corruption(begin, end - (*i).first);
		begin = 0;
		++i;
	}
}

void PartialTorrent::onChildCorruption(
	PartData *file, uint64_t begin, uint64_t end
) {
	RIter i = m_childrenReverse.find(file);
	assert(i != m_childrenReverse.end());
	m_complete.erase(begin + (*i).second, begin + (*i).second + end);
}

// TorrentHasher class
// -------------------
TorrentHasher::TorrentHasher(
	const std::vector<boost::filesystem::path> &files,
	uint64_t begin, uint64_t end, const HashBase *ref
) : HashWork(files[0], begin, end, ref), m_files(files),
m_curFile(m_files.begin()) {}

uint64_t TorrentHasher::readNext(uint64_t pos) {
	uint64_t tmp = HashWork::readNext(pos);
	while (tmp < getBufSize() && ++m_curFile != m_files.end()) {
		m_fileName = *m_curFile;
		openFile();
		tmp += HashWork::readNext(0);
	}
	return tmp;
}

}