#include "InstallerMain.h"
#include "InstallerUtil.h"
#include "PerformInstall.h"
#include "CCriticalSection.h"

#define WMU_INSTALLER_DONE		WM_USER+2
#define PBAR_RANGE_HIGH			100
#define PBAR_RANGE_LOW			0

ICopyMethod *g_pCopyMethod = NULL;
HANDLE g_hCopyThread = NULL;
copy_thread_args_t g_thread_args = {NULL, NULL, NULL, false, false};
CCriticalSection g_update_window;

class TrackProgress : public ICopyProgress
{
public:
	void Initialize(HWND hTextBar, HWND hCurBar, HWND hTotalBar, size_t total_size)
	{
		m_hTextBar = hTextBar;
		m_hCurBar = hCurBar;
		m_hTotalBar = hTotalBar;
		m_TotalSize = total_size;
		m_TotalDone = 0;
		RedrawProgressBars(0.0, 0.0);
	}
	void Finished()
	{
		RedrawProgressBars(100.0, 100.0);
	}
public:
	void StartingNewFile(const TCHAR *filename)
	{
		TCHAR buffer[255];

		if (g_update_window.TryEnter())
		{
			UTIL_Format(buffer, sizeof(buffer) / sizeof(TCHAR), _T("Copying: %s"), filename);
			SendMessage(m_hTextBar, WM_SETTEXT, 0, (LPARAM)buffer);
			UpdateWindow(m_hTextBar);
			g_update_window.Leave();
		}
	}
	void UpdateProgress(size_t bytes, size_t total_bytes)
	{
		float fCur = (float)bytes / (float)total_bytes;
		float fTotal = ((float)m_TotalDone + (float)bytes) / (float)m_TotalSize;
		RedrawProgressBars(fCur, fTotal);
	}
	void FileDone(size_t total_size)
	{
		m_TotalDone += total_size;
		RedrawProgressBars(0.0, (float)m_TotalDone / (float)m_TotalSize);
	}
private:
	void RedrawProgressBar(HWND hBar, float fPerc)
	{
		/* Get a percentage point in the range */
		float fPointInRange = (float)(PBAR_RANGE_HIGH - PBAR_RANGE_LOW) * fPerc;
		int iPointInRange = (int)fPointInRange;

		/* Scale it */
		iPointInRange += PBAR_RANGE_LOW;

		if (g_update_window.TryEnter())
		{
			SendMessage(hBar,
				PBM_SETPOS,
				iPointInRange,
				0);
			g_update_window.Leave();
		}
	}
	void RedrawProgressBars(float fCurrent, float fTotal)
	{
		RedrawProgressBar(m_hCurBar, fCurrent);
		RedrawProgressBar(m_hTotalBar, fTotal);
	}
private:
	size_t m_TotalSize;
	size_t m_TotalDone;
	HWND m_hTextBar;
	HWND m_hCurBar;
	HWND m_hTotalBar;
} s_ProgressTracker;

void CancelPerformInstall()
{
	delete g_thread_args.pFileList;
	g_thread_args.pFileList = NULL;
}

void SetInstallMethod(ICopyMethod *pCopyMethod)
{
	g_pCopyMethod = pCopyMethod;
}

bool CopyStructureRecursively(ICopyMethod *pCopyMethod,
							  CFileList *pFileList,
							  const TCHAR *basepath,
							  const TCHAR *local_path,
							  TCHAR *errbuf,
							  size_t maxchars)
{
	TCHAR file_path[MAX_PATH];
	const TCHAR *file;
	CFileList *pSubList;

	if (!pCopyMethod->SetCurrentFolder(local_path, errbuf, maxchars))
	{
		return false;
	}

	/* Copy files */
	while ((file = pFileList->PeekCurrentFile()) != NULL)
	{
		if (local_path == NULL)
		{
			UTIL_PathFormat(file_path,
				sizeof(file_path) / sizeof(TCHAR),
				_T("%s\\%s"),
				basepath,
				file);
		}
		else
		{
			UTIL_PathFormat(file_path,
				sizeof(file_path) / sizeof(TCHAR), 
				_T("%s\\%s\\%s"),
				basepath,
				local_path,
				file);
		}

		if (!pCopyMethod->SendFile(file_path, errbuf, maxchars))
		{
			return false;
		}

		pFileList->PopCurrentFile();
	}

	/* Now copy folders */
	while ((pSubList = pFileList->PeekCurrentFolder()) != NULL)
	{
		if (g_thread_args.m_bIsUpgrade)
		{
			/* :TODO: put this somewhere else because it technically 
			 * means the progress bars get calculated wrong 
			 */
			if (tstrcasecmp(pSubList->GetFolderName(), _T("cfg")) == 0
				|| tstrcasecmp(pSubList->GetFolderName(), _T("configs")) == 0)
			{
				pFileList->PopCurrentFolder();
				continue;
			}
		}

		/* Try creating the folder */
		if (!pCopyMethod->CreateFolder(pSubList->GetFolderName(), errbuf, maxchars))
		{
			return false;
		}

		TCHAR new_local_path[MAX_PATH];
		if (local_path == NULL)
		{
			UTIL_PathFormat(new_local_path,
				sizeof(new_local_path) / sizeof(TCHAR),
				_T("%s"),
				pSubList->GetFolderName());
		}
		else
		{
			UTIL_PathFormat(new_local_path,
				sizeof(new_local_path) / sizeof(TCHAR),
				_T("%s\\%s"),
				local_path,
				pSubList->GetFolderName());
		}

		if (!CopyStructureRecursively(pCopyMethod,
				pSubList,
				basepath, 
				new_local_path, 
				errbuf, 
				maxchars))
		{
			return false;
		}

		pFileList->PopCurrentFolder();

		/* Set the current folder again for the next operation */
		if (!pCopyMethod->SetCurrentFolder(local_path, errbuf, maxchars))
		{
			return false;
		}
	}

	return true;
}

DWORD WINAPI T_CopyFiles(LPVOID arg)
{
	bool result = 
		CopyStructureRecursively(g_thread_args.pCopyMethod,
		g_thread_args.pFileList,
		g_thread_args.basepath,
		NULL,
		g_thread_args.error,
		sizeof(g_thread_args.error) / sizeof(TCHAR));

	PostMessage(g_thread_args.hWnd, WMU_INSTALLER_DONE, result ? TRUE : FALSE, 0);

	return 0;
}

bool StartFileCopy(HWND hWnd)
{
	g_thread_args.m_bWasCancelled = false;
	g_thread_args.hWnd = hWnd;
	if ((g_hCopyThread = CreateThread(NULL, 
		0, 
		T_CopyFiles, 
		NULL, 
		0,
		NULL))
		== NULL)
	{
		MessageBox(
			hWnd,
			_T("Could not initialize copy thread."),
			_T("SourceMod Installer"),
			MB_OK|MB_ICONERROR);
		return false;
	}
	return true;
}

void StopFileCopy()
{
	g_thread_args.m_bWasCancelled = true;
	g_pCopyMethod->CancelCurrentCopy();

	if (g_hCopyThread != NULL)
	{
		g_update_window.Enter();
		WaitForSingleObject(g_hCopyThread, INFINITE);
		g_update_window.Leave();
		CloseHandle(g_hCopyThread);
		g_hCopyThread = NULL;
	}
}

bool RequestCancelInstall(HWND hWnd)
{
	StopFileCopy();

	int val = MessageBox(
		hWnd,
		_T("Are you sure you want to cancel the install?"),
		_T("SourceMod Installer"),
		MB_YESNO|MB_ICONQUESTION);

	if (val == IDYES)
	{
		return true;
	}
	
	if (g_thread_args.pFileList == NULL)
	{
		return false;
	}

	/* Start the thread, note our return value is opposite */
	return !StartFileCopy(hWnd);
}

bool StartInstallProcess(HWND hWnd)
{
	if (g_pCopyMethod->CheckForExistingInstall())
	{
		int val = MessageBox(
			hWnd, 
			_T("It looks like a previous SourceMod installation exists.  Select \"Yes\" to skip copying configuration files.  Select \"No\" to perform a full re-install."),
			_T("SourceMod Installer"), 
			MB_YESNO|MB_ICONQUESTION);

		if (val == 0 || val == IDYES)
		{
			g_thread_args.m_bIsUpgrade = true;
		}
		else
		{
			g_thread_args.m_bIsUpgrade = false;
		}
	}

#if 0
	TCHAR cur_path[MAX_PATH];
	if (_tgetcwd(cur_path, sizeof(cur_path)) == NULL)
	{
		MessageBox(
			hWnd,
			_T("Could not locate current directory!"),
			_T("SourceMod Installer"),
			MB_OK|MB_ICONERROR);
		return false;
	}
#endif

#if 0
	UTIL_PathFormat(source_path,
		sizeof(source_path) / sizeof(TCHAR),
		_T("%s\\files"),
		cur_path);
#else
	UTIL_PathFormat(g_thread_args.basepath,
		sizeof(g_thread_args.basepath) / sizeof(TCHAR),
		_T("C:\\real\\done\\base"));
#endif

	if (GetFileAttributes(g_thread_args.basepath) == INVALID_FILE_ATTRIBUTES)
	{
		MessageBox(
			hWnd,
			_T("Could not locate the source installation files!"),
			_T("SourceMod Installer"),
			MB_OK|MB_ICONERROR);
		return false;
	}

	delete g_thread_args.pFileList;
	g_thread_args.pFileList = CFileList::BuildFileList(_T(""), g_thread_args.basepath);

	s_ProgressTracker.Initialize(
		GetDlgItem(hWnd, IDC_PROGRESS_CURCOPY),
		GetDlgItem(hWnd, IDC_PROGRESS_CURRENT),
		GetDlgItem(hWnd, IDC_PROGRESS_TOTAL),
		(size_t)g_thread_args.pFileList->GetRecursiveSize());
	g_pCopyMethod->TrackProgress(&s_ProgressTracker);
	g_thread_args.pCopyMethod = g_pCopyMethod;

	return StartFileCopy(hWnd);
}

INT_PTR CALLBACK PerformInstallHandler(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
	switch (message)
	{
	case WM_INITDIALOG:
		{
			SetToGlobalPosition(hDlg);
			return (INT_PTR)TRUE;
		}
	case WM_COMMAND:
		{
			if (LOWORD(wParam) == ID_INSTALL_CANCEL
				|| LOWORD(wParam) == ID_CLOSE)
			{
				if (RequestCancelInstall(hDlg))
				{
					CancelPerformInstall();
					UpdateGlobalPosition(hDlg);
					EndDialog(hDlg, NULL);
				}
				return (INT_PTR)TRUE;
			}
			else if (LOWORD(wParam) == ID_INSTALL_START)
			{
				HWND hButton = GetDlgItem(hDlg, ID_INSTALL_START);
				EnableWindow(hButton, FALSE);
				StartInstallProcess(hDlg);
			}
			break;
		}
	case WMU_INSTALLER_DONE:
		{
			if (wParam == TRUE)
			{
				s_ProgressTracker.Finished();
				MessageBox(hDlg,
					_T("SourceMod was successfully installed!  Please visit http://www.sourcemod.net/ for documentation."),
					_T("SourceMod Installer"),
					MB_OK);
				CancelPerformInstall();
				UpdateGlobalPosition(hDlg);
				EndDialog(hDlg, NULL);
				return (INT_PTR)TRUE;
			}
			else if (!g_thread_args.m_bWasCancelled)
			{
				TCHAR buffer[500];

				UTIL_Format(buffer, 
					sizeof(buffer) / sizeof(TCHAR), 
					_T("Encountered error: %s"),
					g_thread_args.error);
				int res = MessageBox(hDlg,
					buffer,
					_T("SourceMod Installer"),
					MB_ICONERROR|MB_RETRYCANCEL);

				if (res == IDRETRY)
				{
					StartFileCopy(hDlg);
				}
				else
				{
					CancelPerformInstall();
					UpdateGlobalPosition(hDlg);
					EndDialog(hDlg, NULL);
					return (INT_PTR)TRUE;
				}
			}
			break;
		}
	}

	return (INT_PTR)FALSE;
}

void *DisplayPerformInstall(HWND hWnd)
{
	INT_PTR val;

	if ((val = DialogBox(
		g_hInstance, 
		MAKEINTRESOURCE(IDD_PERFORM_INSTALL),
		hWnd,
		PerformInstallHandler)) == -1)
	{
		return NULL;
	}

	return (void *)val;
}