/**
 *  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 <map>
#include <list>

class SharedFile;
class HashBase;

class PartData {
	class UsedRange;
	class LockedRange;
public:
	/**
	 * \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;

	uint32_t getChunkCount(uint32_t chunkSize) const;
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();

	//! 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);
	private:
		friend struct boost::checked_deleter<UsedRange>;
		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.
		UsedRange(PartData *parent);

		//! Destruction is only allowed by PartData (not used) and
		//! boost::checked_deleter, which is used by shared_ptr wrapper.
		~UsedRange();

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

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

	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);
	};

	class HashRange : public Range64 {
	public:
		HashRange(uint64_t begin, uint64_t end, HashBase *hash);
	private:
		HashBase *m_hash;
		bool m_verified;
	};

	void checkAddChunkMap(uint32_t chunkSize);

	//! All that are complete
	RangeList64 m_complete;

	//! We keep weak references to all used ranges we have given out, for
	//! cases where we end up needing to return one of these (e.g. no other
	//! range can be found).
	std::list<boost::weak_ptr<UsedRange> > m_used;

	//! Availability map. Outer key is chunksize, internal vector contains
	//! number of times a chunk has been seen on net.
	std::map<uint32_t, std::vector<uint32_t> > m_avail;

	RangeList<HashRange> m_hashes;

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