/**
 *  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
 */

/** \file hasher.h Interface for Files Checksumming Subsystem */

#ifndef __HASHER_H__
#define __HASHER_H__

#include <hn/osdep.h>
#include <hn/event.h>
#include <hn/hash.h>
#include <hn/workthread.h>
#include <hn/hashsetmaker.h>
#include <boost/enable_shared_from_this.hpp>

/**
 * Events emitted by HashWork object when the job has been completed by
 * Hasher.
 */
enum HashEvent {
	HASH_COMPLETE    = 1,   //!< Full HashWork has been completed.
	HASH_VERIFIED    = 2,   //!< Range Hash has been verified.
	HASH_FAILED      = 3,   //!< Range Hash verification failed.
	HASH_FATAL_ERROR = 4    //!< Fatal error has occoured.
};

/**
 * Represents a job entry to be performed by WorkThread. Always wrap this class
 * into boost::shared_ptr<>, because it is a 'loose' object - it is not
 * contained anywhere, however needs to stay alive through various different
 * containers/classes. As such, there is no specific 'destroyer' assigned
 * to this class - it will need to be cleaned up once nobody really needs
 * it anymore. The path through which this object goes is generally this:
 *
 * - Client code creates an object, and submits it to WorkThread.
 * - WorkThread inserts it into its local queue of pending jobs.
 * - When time comes, hashing thread takes it out from the queue and performs
 *   the work.
 * - When the work is completed, an event is emitted from the work.
 * - If client code handles it, it can retrieve the results, after which the
 *   object gets auto-destructed, since its not contained anywhere, and the
 *   last copy of the object was sent to the client.
 * - If the client does not handle the event, the object gets autodestructed,
 *   since the last place it was stored was in event table queue, and after
 *   calling all handlers for the object, the object is removed from the queue.
 */
class DLLEXPORT HashWork :
	public ThreadWork,
	public boost::enable_shared_from_this<HashWork>
{
public:
	DECLARE_EVENT_TABLE(boost::shared_ptr<HashWork>, HashEvent);

	/**
	 * Constructor for full hash work.
	 *
	 * @param filename      Full path to file to be hashed.
	 *
	 * Submitting this job to Hasher will result in HASH_COMPLETE or
	 * HASH_FATAL_ERROR events to be submitted when the job is completed,
	 * as well as the resulting data being submitted to MetaDb.
	 */
	HashWork(const std::string &filename);

	/**
	 * Construct a range hash work.
	 *
	 * @param filename      Full path to file to be hashed.
	 * @param begin         Begin location to begin hashing.
	 * @param end           End location until what to hash.
	 * @param ref           Reference hash to check against.
	 *
	 * Submitting this job to Hasher will result in HASH_VERIFIED or
	 * HASH_FAILED event being submitted when the job is completed.
	 */
	HashWork(
		const std::string &filename, uint64_t begin, uint64_t end,
		const HashBase *ref
	);

	//! Destructor
	~HashWork();

	//! @name Accessors
	//! \note We can't have these as const since scoped_lock is non-const
	//@{
	//! Whether this job is a full job.
	bool isFull() {
		boost::mutex::scoped_lock l(m_lock);
		return m_full;
	}
	//! In case of partial job, retrieves job range begin
	uint64_t begin() {
		boost::mutex::scoped_lock l(m_lock);
		return m_begin;
	}
	//! In case of partial job, retrieves job range end
	uint64_t end() {
		boost::mutex::scoped_lock l(m_lock);
		return m_end;
	}
	//! Retrieves file name to be hashed.
	std::string getFileName() {
		boost::mutex::scoped_lock l(m_lock);
		return m_filename;
	}
	//! Retrieves type of hash to be generated in case of range verification
	CGComm::HashTypeId getType() {
		boost::mutex::scoped_lock l(m_lock);
		CHECK_THROW(m_ref);
		return m_ref->getTypeId();
	}
	//! In case of range hash work, retrieves reference/control hash
	const HashBase* getRef() {
		boost::mutex::scoped_lock l(m_lock);
		return m_ref;
	}
	//! Retrieves metaData pointer (filled after full hash job)
	MetaData* getMetaData() {
		boost::mutex::scoped_lock l(m_lock);
		return m_md;
	}
	//! Check if this job is valid, e.g. still needed to be performed. This
	//! is needed to make sure that while the job was waiting in the queue,
	//! it hasn't become invalid.
	bool isValid() {
		boost::mutex::scoped_lock l(m_lock);
		return m_valid;
	}
	//! This method should be called by the original job poster to abort
	//! this job, and remove from pending jobs queue. If the work is in
	//! progress already, it will also be aborted, and no results posted.
	void invalidate() {
		boost::mutex::scoped_lock l(m_lock);
		m_valid = false;
	}
	//@}

	//! For implementation use only - set metadata
	void setMetaData(MetaData *md) {
		boost::mutex::scoped_lock l(m_lock);
		m_md = md;
	}

	static uint64_t getHashed() { return s_dataCnt; }
	static double   getTime()   { return s_timeCnt; }

	//! Process this job
	virtual bool process();
private:
	//! File to be hashed. Must include full path to the file.
	const std::string m_filename;

	//! After completing full hash job, contains full metadata about the
	//! file.
	MetaData *m_md;

	//! In case of range hash, this specifies range begin bytes
	uint64_t m_begin;

	//! In case of range hash, this specifies range end bytes
	uint64_t m_end;

	//! In case of range hash, this specifies reference/control hash
	const HashBase *m_ref;

	/**
	 * \short Indicates valditiy of this job
	 *
	 * This variable is set to true as default, and can be set to false
	 * using invalidate() member function. The purpose of this is to
	 * provide a mechanism to abort hashing jobs which are no longer
	 * wanted by the original poster.
	 */
	bool m_valid;

	//! Whether this job is a "full" hash job
	bool m_full;

	/**
	 * Protects all members of this object. This lock must be aquired
	 * before touching anything in this object.
	 */
	boost::mutex m_lock;

	void initState();
	void doProcess();
	void finish();

	boost::scoped_ptr<std::ifstream> m_file;
	boost::scoped_array<char> m_buf;
	std::vector<boost::shared_ptr<HashSetMaker> > m_makers;
	bool m_inProgress;
	static uint64_t s_dataCnt;
	static double   s_timeCnt;
	static boost::mutex s_statsLock;
};

typedef boost::shared_ptr<HashWork> HashWorkPtr;

#endif