//This file is part of eMule
//Copyright (C)2003 eMule Project ( http://www.emule-project.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., 675 Mass Ave, Cambridge, MA 02139, USA.

#include "StdAfx.h"
#include "HashThread.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif


extern volatile BYTE	g_byHashThreadRunning; // declared in SharedFileList.cpp
//volatile bool			g_bHashThreadRunning; // TODO: switch the byte to bool...


volatile HANDLE		CFileHashThread::s_hSharedFilesThread = NULL;
CRITICAL_SECTION	CFileHashThread::s_lockPartFileThread;

CFileHashThread::CFileHashThread(CFilePtrList* pList)
{
	m_dwThreadId = 0;
	m_hThreadHandle = NULL;

	m_pFilesToHashList = pList;
	m_pPartFileOwner = NULL;
}

CFileHashThread::CFileHashThread(CPartFile* pOwner, char* pszDir, char* pszName)
{
	(CFilePtrList*) m_pFilesToHashList = new CFilePtrList;
	UnknownFileStruct* pToHash = new UnknownFileStruct;

	m_dwThreadId = 0;
	m_hThreadHandle = NULL;

	pToHash->pszFileDirectory = pszDir;
	pToHash->pszFileName = pszName;
	m_pFilesToHashList->AddTail(pToHash);
	m_pPartFileOwner = pOwner;
}

CFileHashThread::~CFileHashThread()
{
	// do some cleaning...
	while (!m_pFilesToHashList->IsEmpty()){
		UnknownFileStruct* pTemp = m_pFilesToHashList->RemoveHead();
		delete[] pTemp->pszFileDirectory;
		delete[] pTemp->pszFileName;
		delete pTemp;
	}

	if (m_pPartFileOwner)
		delete m_pFilesToHashList;

	if (m_hThreadHandle)
		::CloseHandle(m_hThreadHandle);
}


bool CFileHashThread::BeginThread(int nPriority, UINT nStackSize, LPSECURITY_ATTRIBUTES lpSecurityAttrs)
{
	m_hThreadHandle = ::CreateThread(lpSecurityAttrs, nStackSize, (LPTHREAD_START_ROUTINE)HashThreadProcStub, this, 0, &m_dwThreadId);
	if (m_hThreadHandle == NULL){
		TRACE("Failed to create hash thread!\n");
		return false;
	}
	TRACE("Successfully created the hash thread: %d, @%d\n", m_hThreadHandle, ::GetTickCount());
	
	::SetThreadPriority(m_hThreadHandle, nPriority);
	
	if (!m_pPartFileOwner)
		s_hSharedFilesThread = m_hThreadHandle;
	
	if((g_byHashThreadRunning&SHAREDFILESTHREAD) && m_pPartFileOwner)
		::SuspendThread(s_hSharedFilesThread);

#ifdef _DEBUG	
	if (m_pPartFileOwner)
		TRACE("Starting Part File Thread:%d, @%d\n", m_hThreadHandle, ::GetTickCount());
	else
		TRACE("Starting Shared Files Thread:%d, @%d\n", m_hThreadHandle, ::GetTickCount());
#endif // _DEBUG

	return true;
}

void WINAPI CFileHashThread::HashThreadProcStub(LPVOID pObj)
{
	((CFileHashThread*)pObj)->HashThreadProc(pObj);
	ExitThread(0);
}

void CFileHashThread::HashThreadProc(LPVOID pParam)
{
	if (m_pPartFileOwner)
		::EnterCriticalSection(&s_lockPartFileThread); // Lock this so only one part file thread can run at a time...
		// (I don't like to use critical section here but I guess it is as good as any other sync object... :) )
	
	uint32 nFileCount = m_pFilesToHashList->GetCount();
	
	do
	{
		UnknownFileStruct* pFileToHash = m_pFilesToHashList->RemoveHead();
		
		CKnownFile* pNewRecord = CreateNewKnownFile(pFileToHash);

		if (pNewRecord && (theApp.emuledlg->m_app_state == APP_STATE_RUNNING)) // no deadlocks?
			::SendMessage(theApp.emuledlg->m_hWnd, TM_FINISHEDHASHING, (WPARAM)m_pPartFileOwner, (LPARAM)pNewRecord);
		else if (theApp.emuledlg->m_app_state == APP_STATE_SHUTINGDOWN){ // maybe use a event object instead...
			::SetThreadPriority(m_hThreadHandle, THREAD_PRIORITY_ABOVE_NORMAL); // Boost the priority when we are shutting down...
			TRACE("The App is shutting down, exiting hash thread: %d, @%d\n", m_hThreadHandle, ::GetTickCount());
			delete pNewRecord;
			delete pFileToHash;
			if (m_pPartFileOwner)
				::LeaveCriticalSection(&s_lockPartFileThread);
			return;
		}
		delete pFileToHash;
	}
	while (!m_pFilesToHashList->IsEmpty());

	::SetThreadPriority(m_hThreadHandle, THREAD_PRIORITY_ABOVE_NORMAL); // Boost the priority when we are shutting down...

	if (!m_pPartFileOwner)
		while(!::PostMessage(theApp.emuledlg->m_hWnd, TM_HASHTHREADFINISHED,(WPARAM)nFileCount, (LPARAM)pParam))
			::Sleep(200); // the queue was full, we're now going to sleep and retry in 200ms... (shouldn't really happen, but just in case)
	else{
		while (!::PostMessage(theApp.emuledlg->m_hWnd, TM_HASHTHREADFINISHED,(WPARAM)0xFFFFFFFF, (LPARAM)pParam))
			::Sleep(200); // the queue was full, we're now going to sleep and retry in 200ms...
		::LeaveCriticalSection(&s_lockPartFileThread);
	}

#ifdef _DEBUG
	if (m_pPartFileOwner)
		TRACE("Exiting Part File Thread:%d, @%d\n", m_hThreadHandle, ::GetTickCount());
	else
		TRACE("Exiting Shared Files Thread:%d, @%d\n", m_hThreadHandle, ::GetTickCount());
#endif // _DEBUG

}

CKnownFile* CFileHashThread::CreateNewKnownFile(UnknownFileStruct* pFileToHash)
{
	CKnownFile* pNewKnownFile = new CKnownFile();

	pNewKnownFile->directory = pFileToHash->pszFileDirectory;
	pNewKnownFile->filename = pFileToHash->pszFileName;

	char* pFileNameBuffer = new char[strlen(pFileToHash->pszFileDirectory)+strlen(pFileToHash->pszFileName)+2];
	sprintf(pFileNameBuffer, "%s\\%s", pFileToHash->pszFileDirectory, pFileToHash->pszFileName);

	HANDLE hFile; // maybe use memory mapped files to see if we can get some better performance...
	if (m_pPartFileOwner){
		hFile = HANDLE(m_pPartFileOwner->m_hpartfile);
		::SetFilePointer(hFile, 0, 0, FILE_BEGIN);	//reset the file pointer if it has been set earlier...
	}
	else
		hFile = ::CreateFile(pFileNameBuffer, GENERIC_READ, FILE_SHARE_READ /*NULL*/, NULL, OPEN_EXISTING,
							 FILE_FLAG_SEQUENTIAL_SCAN, NULL);
	delete[] pFileNameBuffer;
	if (hFile == INVALID_HANDLE_VALUE){
		DWORD dwError = ::GetLastError();
		delete pNewKnownFile;
		return NULL;
	}

	uint32 nBytesLeft = ::GetFileSize(hFile, NULL);
	pNewKnownFile->filesize = nBytesLeft;
	uint32 nNumberOfHashes = (nBytesLeft/PARTSIZE)+((nBytesLeft%PARTSIZE)? 1 : 0);;
	uchar* pHashSetBuffer = new uchar[nNumberOfHashes*16];
	uint32 iHashCount = 0;
	uint32 nNumberOfBytesRead = 0;

	while (nBytesLeft > PARTSIZE) // PARTSIZE = 9728000
	{
		pNewKnownFile->CreateHashFromInput(hFile, NULL, PARTSIZE, pHashSetBuffer+(iHashCount*16));
		nBytesLeft -= PARTSIZE;
		iHashCount++;
		if (theApp.emuledlg->m_app_state == APP_STATE_SHUTINGDOWN){
			// try to clean up as much as possible if the user terminates the app when the thread is running...
			if (!m_pPartFileOwner)
				::CloseHandle(hFile);
			delete[] pHashSetBuffer;
			return pNewKnownFile;
		}
	}

	pNewKnownFile->CreateHashFromInput(hFile, NULL, nBytesLeft, pHashSetBuffer+(iHashCount*16));

	if (!iHashCount)
		memcpy(pNewKnownFile->filehash, pHashSetBuffer, 16);
	else{
		for (uint32 i = 0; i != nNumberOfHashes; i++){
			uchar* pTempHash = new uchar[16];
			memcpy(pTempHash, pHashSetBuffer+(i*16), 16);
			pNewKnownFile->hashlist.Add(pTempHash);
		}
		pNewKnownFile->CreateHashFromInput(NULL, pHashSetBuffer, nNumberOfHashes*16, pNewKnownFile->filehash);
	}
	delete[] pHashSetBuffer;

	// TODO: Add filetags
	// Get Time of last modification of file
	struct _stat fileinfo;
	int tempHandle = _open_osfhandle((intptr_t)hFile, 0);
	_fstat(tempHandle, &fileinfo);
	pNewKnownFile->date = fileinfo.st_mtime;
	if (!m_pPartFileOwner)
		_close(tempHandle);

	return pNewKnownFile;	
}
