/**
 *  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/range.h>
#include <boost/filesystem/path.hpp>
#include <boost/weak_ptr.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/multi_index_container.hpp>
#include <boost/multi_index/key_extractors.hpp>
#include <boost/multi_index/ordered_index.hpp>
#include <map>
#include <list>

class SharedFile;
class HashBase;

class PartData {
public:
	class UsedRange;
private:
	class Chunk : public Range64 {
	public:
		Chunk(uint64_t begin, uint64_t end, HashBase *h = 0);

		//! \name Getters
		//! @{
		bool getVerified() const { return m_verified; }
		bool getPartial() const { return m_partial; }
		uint32_t getAvail() const { return m_avail; }
		uint32_t getUseCnt() const { return m_useCnt; }
		//! May throw boost::bad_weak_ptr
		boost::shared_ptr<UsedRange> getUsePtr() const {
			return boost::shared_ptr<UsedRange>(m_used);
		}
		//! @}

		//! \name Setters
		//! @{
		void setVerified(bool v) { m_verified = v; }
		void setPartial(bool p) { m_partial = p; }
		void setAvail(uint32_t a) { m_avail = a; }
		void setUseCnt(uint32_t u) { m_useCnt = u; }
		void setUsePtr(boost::weak_ptr<UsedRange> p) { m_used = p; }
		//! @}

		//! Need to declare this here too for Boost.MultiIndex to work
		uint64_t length() const { return Range64::length(); }
	public:
		HashBase *m_hash;  //!< Optional
		bool m_verified;   //!< Whether it's verified.
		bool m_partial;    //!< Whether it's only partially downloaded
		uint32_t m_avail;  //!< Availability count
		uint32_t m_useCnt; //!< Use count
		//! If m_useCnt > 0, this points to the relevant UsedRange
		boost::weak_ptr<UsedRange> m_used;
	};
	struct ID_Pos;
	struct ID_Verified;
	struct ID_Partial;
	struct ID_Avail;
	struct ID_UseCnt;
	struct ID_Length;
	typedef boost::multi_index_container<
		Chunk,
		boost::multi_index::indexed_by<
			boost::multi_index::ordered_non_unique<
				boost::multi_index::tag<ID_Pos>,
				boost::multi_index::identity<Chunk>
			>,
			boost::multi_index::ordered_non_unique<
				boost::multi_index::tag<ID_Verified>,
				boost::multi_index::member<
					Chunk, bool, &Chunk::m_verified
				>
			>,
			boost::multi_index::ordered_non_unique<
				boost::multi_index::tag<ID_Partial>,
				boost::multi_index::member<
					Chunk, bool, &Chunk::m_partial
				>
			>,
			boost::multi_index::ordered_non_unique<
				boost::multi_index::tag<ID_Avail>,
				boost::multi_index::member<
					Chunk, uint32_t, &Chunk::m_avail
				>
			>,
			boost::multi_index::ordered_non_unique<
				boost::multi_index::tag<ID_UseCnt>,
				boost::multi_index::member<
					Chunk, uint32_t, &Chunk::m_useCnt
				>
			>,
			boost::multi_index::ordered_non_unique<
				boost::multi_index::tag<ID_Length>,
				boost::multi_index::const_mem_fun<
					Chunk, uint64_t, &Chunk::length
				>
			>
		>
	> ChunkMap;
	typedef ChunkMap::index<ID_Pos     >::type CMPosIndex;
	typedef ChunkMap::index<ID_Verified>::type CMVerifiedIndex;
	typedef ChunkMap::index<ID_Partial >::type CMPartialIndex;
	typedef ChunkMap::index<ID_Avail   >::type CMAvailIndex;
	typedef ChunkMap::index<ID_UseCnt  >::type CMUseIndex;
	typedef ChunkMap::index<ID_Length  >::type CMLenIndex;
public:
	class UsedRange;
	class LockedRange;

	/**
	 * \brief Construct a NEW temporary file
	 *
	 * Using this constructor, a new temporary file is constructed, at
	 * specified location with specified size.
	 *
	 * @param size      Size of the resulting file.
	 * @param loc       Location on disk where to store the temp file
	 * @param dest      Destination where to write the complete file
	 *
	 * \note The disk space indicated by @param size is not allocated on
	 *       actual disk right away. Instead, the size is allocated
	 *       dynamically as the file grows. This can be changed from global
	 *       application preferences though.
	 */
	PartData(
		uint64_t size,
		const boost::filesystem::path &loc,
		const boost::filesystem::path &dest
	);

	/**
	 * \brief Load a previously constructed temporary file.
	 *
	 * This method can be used to resume a previously started download, by
	 * reading the necessery data from @param loc.
	 *
	 * @param loc    Path to PartData reference file, which contains the
	 *               data required to resume the download.
	 */
	PartData(const boost::filesystem::path &loc);

	/**
	 * \brief Add an availability chunk mask
	 *
	 * Allows modules to register chunk availability maps, so PartData
	 * can decide the lowest-available chunk to be returned from get*
	 * methods
	 *
	 * @param chunkSize     Size of one chunk
	 * @param chunks        Boolean vector, where each true indicates
	 *                      the source having the part, and false the source
	 *                      not having the part.
	 */
	void addSourceMask(uint32_t chunkSize, const std::vector<bool> &chunks);

	/**
	 * \brief Optimized version of addSourceMask(), adds a full source.
	 *
	 * Similar to addSourceMask(), this adds a source which has the entire
	 * file.
	 *
	 * @param chunkSize    Size of one chunk
	 */
	void addFullSource(uint32_t chunkSize);

	/**
	 * Gets a free range that PartData considers most important. First/last
	 * ranges are considered important, as well as rare ranges, and
	 * incomplete ranges.
	 *
	 * @param size      Size of the range to be aquired.
	 * @return          Pointer to a range marked as 'used'.
	 *
	 * \note The size of the given range my be smaller than was requested.
	 */
	boost::shared_ptr<UsedRange> getRange(uint32_t size);

	//! Write method simply writes data starting at specified offset. If
	//! there are no locks currently on the range, and the range is indeed
	//! incomplete, everything works. If something goes wrong, exceptions
	//! will be generated.
	void write(uint64_t beginOffset, const std::string &data);

	//! Check for completeness, either the entire file (used internally), or
	//! a specific subrange (also used internally). Public only for
	//! completeness.
	bool isComplete() const;
	bool isComplete(const Range64 &subRange) const;
	bool isComplete(uint64_t begin, uint64_t end) const;

	uint32_t getChunkCount(uint32_t chunkSize) const;



	//! UsedRange concept is similar to many thread libraries lock object
	//! concepts - you retrieve one via get() methods in PartData, and when
	//! it is destroyed, it takes care that all used/locked ranges do get
	//! freed properly. This object may only be used when wrapped in
	//! boost::shared_ptr.
	class UsedRange : public Range64 {
	public:
		std::auto_ptr<LockedRange> getLock(uint32_t size);

		~UsedRange();
	private:
		friend class PartData;

		//! Allowed only by PartData. UsedRange keeps a pointer back to
		//! its parent object, and also sets up event handers as
		//! neccesery to ensure the pointer remains valid.
		//! \note The template may be left undefined here since it's
		//!       only called from inside partdata.cpp
		template<typename IterType>
		UsedRange(PartData *parent, IterType it);

		//! copying is not allowed
		UsedRange(const UsedRange&);
		UsedRange& operator=(const UsedRange&);

		//! Ranges that are locked within this usedrange
		RangeList64 m_locked;

		//! Parent PartData
		PartData *m_parent;

		//! Chunk this UsedRange refers to
		CMAvailIndex::iterator m_chunk;
	};

	class LockedRange : public Range64 {
	public:
		//! Identical method as in PartData class, this is provided for
		//! completeness.
		void write(uint64_t beginOffset, const std::string &data);
	};
private:
	friend class SharedFile;
	friend int test_main(int, char[]);

	//! Copying part files is not allowed
	PartData(const PartData&);
	PartData& operator=(const PartData&);

	//! Only allowed by SharedFile
	~PartData();

	void checkAddChunkMap(uint32_t chunkSize);

	//! All that are complete
	RangeList64 m_complete;

	ChunkMap m_chunks;
	uint64_t m_size;
	boost::filesystem::path m_location;
	boost::filesystem::path m_destination;
};

typedef boost::shared_ptr<PartData::UsedRange> UsedRangePtr;
typedef std::auto_ptr<PartData::LockedRange> LockedRangePtr;