/**
 *  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.cpp Implementation of Files Identification Subsystem */

// Precompiled header
#include <hn/hnprec.h>

#include <hn/hasher.h>
#include <hn/hashsetmaker.h>
#include <hn/md4transform.h>
#include <hn/md5transform.h>
#include <hn/sha1transform.h>
#include <hn/metadata.h>
#include <boost/filesystem/path.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/progress.hpp>
#include <fstream>

using boost::filesystem::path;
using boost::filesystem::no_check;
using namespace CGComm;

uint64_t HashWork::s_dataCnt = 0;
double   HashWork::s_timeCnt = 0.0;
boost::mutex HashWork::s_statsLock;
static uint32_t s_bufSize = 32*1024;
static uint32_t s_sleepTime = 0;
static boost::shared_ptr<boost::progress_display> s_prog;
IMPLEMENT_EVENT_TABLE(HashWork, boost::shared_ptr<HashWork>, HashEvent);

// Full hash job Constructor
HashWork::HashWork(const std::string &filename)
: m_filename(filename), m_md(), m_begin(), m_end(), m_ref(), m_valid(true),
m_full(true), m_inProgress() {}

// Range hash verification job
HashWork::HashWork(
	const std::string &filename, uint64_t begin, uint64_t end,
	const HashBase* ref
) : m_filename(filename), m_md(), m_begin(begin), m_end(end), m_ref(ref),
m_valid(true), m_full(false), m_inProgress() {
	CHECK_THROW(ref);
}

HashWork::~HashWork() {}

bool HashWork::process() {
	if (!m_inProgress) {
		initState();
	}
	CHECK_THROW(!isComplete());
	Utils::StopWatch s1;
	doProcess();
	if (s_sleepTime) {
		boost::xtime xt;
		boost::xtime_get(&xt, boost::TIME_UTC);
		xt.nsec += s_sleepTime;
		boost::thread::sleep(xt);
		boost::mutex::scoped_lock l(s_statsLock);
	}
	s_timeCnt += s1.elapsed();
	return isComplete();
}

void HashWork::initState() {
	m_file.reset(new std::ifstream(m_filename.c_str(), std::ios::in));
	if (!*m_file) {
		getEventTable().postEvent(shared_from_this(), HASH_FATAL_ERROR);
		throw std::runtime_error(
			(boost::format("Unable to open file `%s' for hashing.")
			% m_filename).str()
		);
	}
	logMsg(boost::format("Hashing file `%s'") % m_filename);
	boost::shared_ptr<HashSetMaker> t;
	if (isFull()) {
		t.reset(new ED2KHashMaker);
		m_makers.push_back(t);
		t.reset(new SHA1HashMaker);
		m_makers.push_back(t);
		t.reset(new MD4HashMaker);
		m_makers.push_back(t);
		t.reset(new MD5HashMaker);
		m_makers.push_back(t);
		m_begin = 0;
		m_end = Utils::getFileSize(m_filename);
		s_prog.reset(new boost::progress_display(m_end));
	} else {
		switch (getType()) {
			case OP_HT_MD4:
				t.reset(new MD4HashMaker);
				break;
			case OP_HT_MD5:
				t.reset(new MD5HashMaker);
				break;
			case OP_HT_ED2K:
				t.reset(new ED2KHashMaker);
				break;
			case OP_HT_SHA1:
				t.reset(new SHA1HashMaker);
				break;
			default:
				boost::format fmt(
					"Requested unknown hash of type %s"
				);
				logError(fmt % m_ref->getType());
				break;
		}
		m_makers.push_back(t);
		m_file->seekg(m_begin);
		s_prog.reset(new boost::progress_display(m_end-m_begin+1));
	}
	m_buf.reset(new char[s_bufSize]);
	m_inProgress = true;
}

void HashWork::doProcess() {
	if (s_bufSize + static_cast<uint64_t>(m_file->tellg()) > m_end) {
		m_file->read(m_buf.get(), m_end - m_file->tellg() + 1);
	} else {
		m_file->read(m_buf.get(), s_bufSize);
	}
	for (uint32_t i = 0; i < m_makers.size(); ++i) {
		m_makers[i]->sumUp(m_buf.get(), m_file->gcount());
	}
	boost::mutex::scoped_lock l(s_statsLock);
	s_dataCnt += m_file->gcount();
	*s_prog += m_file->gcount();
	if (!*m_file || m_end + 1 == static_cast<uint64_t>(m_file->tellg())) {
		finish();
	}
}
void HashWork::finish() {
	if (isFull()) {
		CHECK_THROW(m_makers.size());
		m_md = new MetaData(Utils::getFileSize(m_filename));
		for (uint32_t i = 0; i < m_makers.size(); ++i) {
			m_md->addHashSet(m_makers[i]->getHashSet());
		}
		m_md->setModDate(Utils::getModDate(m_filename));
		m_md->addFileName(path(m_filename, no_check).leaf());
		getEventTable().postEvent(shared_from_this(), HASH_COMPLETE);
	} else {
		CHECK_THROW(m_makers.size() == 1);
		const HashBase &h = m_makers[0]->getHashSet()->getFileHash();
		HashEvent evt(h == *m_ref ? HASH_VERIFIED : HASH_FAILED);
		getEventTable().postEvent(shared_from_this(), evt);
	}
	setComplete();
	m_file.reset();
	s_prog.reset();
}