/*
Author: Adam Sawicki
	mailto: sawickiap at poczta dot onet dot pl
	http://regedit.gamedev.pl/
License: Public Domain
*/
#include "StdAfx.hpp"
#include "FileChangeMonitor.hpp"
#include "Framework.hpp"

using namespace common;

// Time period between consecutive file checks.
static const int MILLISECONDS_BETWEEN_FILE_CHECKS = 200;


//HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH
// FileChangeMonitor

FileChangeMonitor *g_FileChangeMonitor = NULL;

FileChangeMonitor::FileChangeMonitor()
: m_NextFileId(1)
, _m_NextIndex(0)
, m_FileDescsMutex(0)
, m_EventQueueMutex(0)
, m_QuitEvent(false, Event::TYPE_MANUAL_RESET)
{
	LOG(1, "Starting File Change Monitor");
	Thread::Start();
}

FileChangeMonitor::~FileChangeMonitor()
{
	m_QuitEvent.Set();
	Thread::Join();
}

void FileChangeMonitor::ProcessEvents()
{
	MutexLock ml(m_EventQueueMutex);
	for (uint i = 0; i < m_EventQueue.size(); i++)
		if (CheckIfRegistered(m_EventQueue[i]))
		m_EventQueue[i].EventListener->OnFileChange(m_EventQueue[i].FileId);
	m_EventQueue.clear();
}

uint FileChangeMonitor::RegisterFile(IFileChangeEventListener *EventListener, const string &FileName)
{
	assert(EventListener != NULL);
	assert(!FileName.empty());

	uint NewId = m_NextFileId++;

	shared_ptr<FILE_DESC> NewDesc(new FILE_DESC());
	NewDesc->FileName = FileName;
	NewDesc->FileId = NewId;
	NewDesc->EventListener = EventListener;
	NewDesc->LastFileModificationTime.m_Time = 0ll;
	
	{
		MutexLock ml(m_FileDescsMutex);
		m_FileDescs.push_back(NewDesc);
	}

	return NewId;
}

void FileChangeMonitor::UnregisterFile(IFileChangeEventListener *EventListener, uint FileId)
{
	assert(EventListener != NULL);
	assert(FileId != 0);

	bool Found = false;
	{
		MutexLock ml(m_FileDescsMutex);
		for (uint i = m_FileDescs.size(); i--; )
		{
			const FILE_DESC &Desc = *m_FileDescs[i].get();
			if (Desc.EventListener == EventListener && Desc.FileId == FileId)
			{
				m_FileDescs.erase(m_FileDescs.begin() + i);
				if (_m_NextIndex >= i) _m_NextIndex--;
				Found = true;
				break;
			}
		}
	}

	if (!Found)
		LOG(1 | LOG_WARNING, Format("FileChangeMonitor::UnregisterFile: FileId=# not found") % FileId);
}

uint FileChangeMonitor::UnregisterAllFilesByListener(IFileChangeEventListener *EventListener)
{
	assert(EventListener != NULL);

	uint Count = 0;
	{
		MutexLock ml(m_FileDescsMutex);
		for (uint i = m_FileDescs.size(); i--; )
		{
			const FILE_DESC &Desc = *m_FileDescs[i].get();
			if (Desc.EventListener == EventListener)
			{
				m_FileDescs.erase(m_FileDescs.begin() + i);
				if (_m_NextIndex >= i) _m_NextIndex--;
				Count++;
			}
		}
	}
	return Count;
}

uint FileChangeMonitor::UnregisterAllFiles()
{
	uint Count = 0;
	{
		MutexLock ml(m_FileDescsMutex);
		Count = m_FileDescs.size();
		m_FileDescs.clear();
		_m_NextIndex = 0;
	}
	return Count;
}

void FileChangeMonitor::Run()
{
	string FileName;
	for(;;)
	{
		uint WaitTime;
		{
			MutexLock ml(m_FileDescsMutex);
			uint DescCount = m_FileDescs.size();
			WaitTime = (DescCount != 0) ? (MILLISECONDS_BETWEEN_FILE_CHECKS / DescCount) : MILLISECONDS_BETWEEN_FILE_CHECKS;
		}

		bool Quit = m_QuitEvent.TimeoutWait(WaitTime);
		if (Quit) break;

		{
			MutexLock ml(m_FileDescsMutex);

			uint DescCount = m_FileDescs.size();
			if (DescCount > 0)
			{
				if (_m_NextIndex >= DescCount) _m_NextIndex = 0;
				FileName = m_FileDescs[_m_NextIndex]->FileName;

				DATETIME NewFileTime;
				// It's a pity that this mutex is blocked for this entire time.
				bool ok = GetFileItemInfo(FileName, NULL, NULL, &NewFileTime);
				if (!ok) NewFileTime.m_Time = 0x7fffffffffffffffll;

				{
					MutexLock ml(m_FileDescsMutex);
					FILE_DESC &Desc = *m_FileDescs[_m_NextIndex].get();
					if (Desc.LastFileModificationTime.m_Time == 0ll)
						Desc.LastFileModificationTime = NewFileTime;
					else if (NewFileTime != Desc.LastFileModificationTime)
					{
						EVENT_DESC Event = { Desc.EventListener, Desc.FileId };
						{
							MutexLock ml(m_EventQueueMutex);
							m_EventQueue.push_back(Event);
						}
						Desc.LastFileModificationTime = NewFileTime;
					}
				}
				
				_m_NextIndex = (_m_NextIndex + 1) % DescCount;
			}
		}
	}
}

bool FileChangeMonitor::CheckIfRegistered(const EVENT_DESC &EventDesc)
{
	MutexLock ml(m_FileDescsMutex);
	for (uint i = 0; i < m_FileDescs.size(); i++)
	{
		if (m_FileDescs[i]->FileId == EventDesc.FileId &&
			m_FileDescs[i]->EventListener == EventDesc.EventListener)
		{
			return true;
		}
	}
	return false;
}

