/**
 *  Copyright (C) 2004-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
 */

#include <hn/partdata.h>
#include <hn/log.h>
#include <boost/lambda/lambda.hpp>
#include <boost/lambda/if.hpp>
#include <boost/lambda/bind.hpp>
#include <fstream>

using namespace boost::lambda;
using namespace boost::multi_index;

/**
 * @name Boost.Lambda (bind) placeholder types
 *
 * Need to redefine placeholders to avoid conflicting with Boost.Bind
 * placeholders, defined in hn/osdep.h
 */
//! @{
boost::lambda::placeholder1_type __1;
boost::lambda::placeholder2_type __2;
boost::lambda::placeholder3_type __3;
//! @}


// PartData::UsedRange class
// -------------------------
template<typename IterType>
PartData::UsedRange::UsedRange(PartData *parent, IterType it)
: Range64((*it).begin(), (*it).end()), m_parent(parent),
m_chunk(parent->m_chunks.project<ID_Avail>(it)) {
	parent->m_chunks.get<ID_Avail>().modify(
		m_chunk, ++bind(&PartData::Chunk::m_useCnt, __1(__1))
	);
}
PartData::UsedRange::~UsedRange() {
	m_parent->m_chunks.get<ID_Avail>().modify(
		m_chunk, --bind(&PartData::Chunk::m_useCnt, __1(__1))
	);
}

// PartData::Chunk class
// ---------------------
PartData::Chunk::Chunk(uint64_t begin, uint64_t end, HashBase *hash)
: Range64(begin, end), m_hash(hash), m_verified(), m_partial(), m_avail(),
m_useCnt() {}

// PartData class
// --------------

PartData::PartData(
	uint64_t size,
	const boost::filesystem::path &loc,
	const boost::filesystem::path &dest
) : m_size(size), m_location(loc), m_destination(dest) {
	std::ofstream o(loc.string().c_str());
	o.flush();
}

PartData::PartData(const boost::filesystem::path &) {
	//! \todo Implement reloading
}

void PartData::addSourceMask(
	uint32_t chunkSize, const std::vector<bool> &chunks
) {
	CHECK_THROW(chunks.size() == getChunkCount(chunkSize));
	checkAddChunkMap(chunkSize);
	int i = 0;
	typedef ChunkMap::index<ID_Pos>::type PosIndex;
	typedef PosIndex::iterator Iter;
	PosIndex& pi = m_chunks.get<ID_Pos>();
	for (Iter j = pi.begin(); j != pi.end(); ++j) {
		pi.modify(j, bind(&Chunk::m_avail, __1(__1)) += chunks[i++]);
	}
}

void PartData::addFullSource(uint32_t chunkSize) {
	checkAddChunkMap(chunkSize);
	typedef ChunkMap::index<ID_Pos>::type PosIndex;
	PosIndex& pi = m_chunks.get<ID_Pos>();
	for (PosIndex::iterator i = pi.begin(); i != pi.end(); ++i) {
		pi.modify(i, ++bind(&Chunk::m_avail, __1(__1)));
	}
}

UsedRangePtr PartData::getRange(uint32_t size) {
	logDebug(boost::format("PartData::getRange(%d)") % size);

	boost::shared_ptr<PartData::UsedRange> ret;

/**
 * \todo This stuff needs also to work well along with UsedRange concept, which
 *       it doesn't do right now, and is disabled for that reason. Alternative
 *       approach would be to incorporate this functionality to the other search
 *       methods, which are already UsedRange-aware.
 */
	// Round 1: First and last chunks
// 	logDebug("Round 1: First and last chunks.");
// 	if (!isComplete(0, size)) {
// 		if (!m_complete.size()) {
// 			ret.reset(new UsedRange(this, 0, size - 1));
// 		} else if ((*m_complete.begin()).begin() > size - 1) {
// 			ret.reset(new UsedRange(this, 0, size - 1));
// 		} else if ((*m_complete.begin()).begin() > 0) {
// 			uint64_t end = (*m_complete.begin()).begin() - 1;
// 			ret.reset(new UsedRange(this, 0, end));
// 		}
// 		logDebug("Got first.");
// 	} else if (!isComplete(m_size - 1 - size, m_size - 1)) {
// 		if (!m_complete.size()) {
// 			ret.reset(new UsedRange(this, m_size-1-size, m_size-1));
// 		} else if ((*--m_complete.end()).end() < m_size - 1 - size) {
// 			ret.reset(new UsedRange(this, m_size-1-size, m_size-1));
// 		} else if ((*--m_complete.end()).end() != m_size - 1) {
// 			uint64_t begin = (*--m_complete.end()).end() + 1;
// 			ret.reset(new UsedRange(this, begin, m_size - 1));
// 		}
// 		logDebug("Got last.");
// 	}

	// Round 2: Incomplete chunks
	if (!ret) {
		logDebug("Round 2: Incomplete chunks.");
		typedef ChunkMap::index<ID_Partial>::type PartialIndex;
		typedef PartialIndex::iterator PIter;
		PartialIndex &pi = m_chunks.get<ID_Partial>();
		std::pair<PIter, PIter> r = pi.range(__1 == true, unbounded);
		PIter i = pi.end();
		for (PIter j = r.first; j != r.second; ++j) {
			if (!(*j).m_useCnt) {
				i = j;
			}
		}
		// partial chunk with useCnt == 0
		if (i != pi.end()) {
			ret.reset(new UsedRange(this, i));
			logDebug("Got.");
		}
	}
	// Round 3: Lowest available unused chunk
	if (!ret) {
		logDebug("Round 3: Lowest available unused chunk.");
		typedef ChunkMap::index<ID_Avail>::type AvailIndex;
		typedef AvailIndex::iterator AIter;
		AvailIndex &ai = m_chunks.get<ID_Avail>();
		AIter i = ai.end();
		AIter r = ai.upper_bound(0);
		while (r != ai.end()) {
			if (!(*r).getUseCnt()) {
				i = r;
				break;
			}
			++r;
		}
		if (i != ai.end()) {
			ret.reset(new UsedRange(this, i));
			logDebug("Got.");
		}
	}
	// Round 4: Least used chunk
	if (!ret) {
		logDebug("Round 4: Least used chunk.");
		typedef ChunkMap::index<ID_UseCnt>::type UseIndex;
		typedef UseIndex::iterator UIter;
		UseIndex &ui = m_chunks.get<ID_UseCnt>();
		UIter r = ui.upper_bound(0);
		if (r != ui.end()) try {
			logDebug("Reusing existing.");
			ret = (*r).getUsePtr();
		} catch (boost::bad_weak_ptr&) {
			logDebug("Internal PartData error. Compensating...");
			ret.reset(new UsedRange(this, r));
		}
	}
	CHECK_THROW_MSG(ret, "Failed to generate chunk request.");
	logDebug(boost::format("Result: %d..%d") % ret->begin() % ret->end());
	return ret;
}

uint32_t PartData::getChunkCount(uint32_t chunkSize) const {
	return m_size / chunkSize + (m_size % chunkSize ? 0 : 1);
}

void PartData::checkAddChunkMap(uint32_t cs) {
	typedef ChunkMap::index<ID_Length>::type::iterator LIter;
	std::pair<LIter, LIter> ret = m_chunks.get<ID_Length>().equal_range(cs);
	if (ret.first == m_chunks.get<ID_Length>().end()) {
		logDebug(boost::format("Adding ChunkSet(%d)") % cs);
		for (uint32_t i = 0; i < getChunkCount(cs); ++i) {
			m_chunks.insert(Chunk(i * cs, (i + 1) * cs - 1));
		}
	}
}

bool PartData::isComplete() const {
	return m_complete.size() == 1 &&
		!(*m_complete.begin()).begin() &&
		(*m_complete.begin()).end() == m_size - 1;
}
bool PartData::isComplete(const Range64 &r) const {
	return m_complete.contains(r);
}
bool PartData::isComplete(uint64_t begin, uint64_t end) const {
	return m_complete.contains(begin, end);
}