/*
 * Copyright (c) 2005-2006 Tom N Harris.
 *
 *     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.
 *
 *     A copy of the GNU General Public License can be found at
 *     http://www.gnu.org/
 *
 * Written by Tom N Harris <telliamed@whoopdedo.org>.
 *
 */

#include <algorithm>

#include "Installer.h"
#include "globals.h"
#include "dark.h"
#include "resource.h"

#include "Window.h"
#include "Progress.h"

#include "RdfGraph.h"
#include "FileVersion.h"
#include "InstallCfg.h"
#include "Backup.h"
#include "Restore.h"

using namespace Setup;
using namespace SimpleRDF;
using namespace std;


static BOOL CALLBACK
prompt_dialog_cb(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	switch (message)
	{
	case WM_INITDIALOG:
	{
		LPCTSTR *params = reinterpret_cast<LPCTSTR*>(lParam);
		HWND hctrl = ::GetDlgItem(hwnd, IDC_PROMPT_MESSAGE);
		int len = ::GetWindowTextLength(hctrl) + 1;
		TCHAR* tmp = reinterpret_cast<TCHAR*>(::operator new(len*sizeof(TCHAR)));
		if (::GetWindowText(hctrl, tmp, len))
		{
			String msg = String::Format(tmp, params[0], params[1]);
			::SetWindowText(hctrl, msg.c_str());
		}
		::operator delete(tmp);
		::MessageBeep(MB_ICONEXCLAMATION);
		return TRUE;
	}
	case WM_COMMAND:
		if (HIWORD(wParam) == BN_CLICKED)
		{
			::EndDialog(hwnd,wParam);
			return TRUE;
		}
	}
	return FALSE;
}

static BOOL CALLBACK
error_dialog_cb(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	switch (message)
	{
	case WM_INITDIALOG:
	{
		LPCTSTR *params = reinterpret_cast<LPCTSTR*>(lParam);
		HWND hctrl = ::GetDlgItem(hwnd, IDC_PROMPT_MESSAGE);
		int len = ::GetWindowTextLength(hctrl) + 1;
		TCHAR* tmp = reinterpret_cast<TCHAR*>(::operator new(len*sizeof(TCHAR)));
		if (::GetWindowText(hctrl, tmp, len))
		{
			String msg = String::Format(tmp, params[0], params[1], params[2]);
			::SetWindowText(hctrl, msg.c_str());
		}
		::operator delete(tmp);
		::MessageBeep(MB_ICONHAND);
		return TRUE;
	}
	case WM_COMMAND:
		if (HIWORD(wParam) == BN_CLICKED)
		{
			::EndDialog(hwnd,wParam);
			return TRUE;
		}
	}
	return FALSE;
}


Installer::Installer()
	: m_installgames(0),
	  m_dirsT1(NULL), m_filesT1(NULL), m_backupT1(NULL), 
	  m_dirsT2(NULL), m_filesT2(NULL), m_backupT2(NULL), 
	  m_dirsSS2(NULL), m_filesSS2(NULL),m_backupSS2(NULL), 
	  m_installcfgT1(0), m_installcfgT2(0), m_installcfgSS2(0)
	  //,m_copier(NULL)
{
}

Installer::~Installer()
{
	//delete m_copier;
	delete m_filesSS2;
	delete m_filesT2;
	delete m_filesT1;
	delete m_dirsSS2;
	delete m_dirsT2;
	delete m_dirsT1;
	delete m_backupSS2;
	delete m_backupT2;
	delete m_backupT1;
}


const InstallTarget* 
Installer::getTarget(string const& targ_id) const
{
	TargList::const_iterator i = m_targs.find(targ_id);
	if (i != m_targs.end())
		return &(i->second);
	return NULL;
}

InstallTarget* 
Installer::_getTarget(string const& targ_id)
{
	TargList::iterator i = m_targs.find(targ_id);
	if (i != m_targs.end())
		return &(i->second);
	return NULL;
}

void 
Installer::Reset()
{
	m_installgames = 0;
	for (TargSet::iterator i = m_installs.begin(); i != m_installs.end(); ++i)
		(*i)->resetInstallGames();
	m_installs.clear();
	delete m_backupT1;
	m_backupT1 = NULL;
	delete m_backupT2;
	m_backupT2 = NULL;
	delete m_backupSS2;
	m_backupSS2 = NULL;
	delete m_filesT1;
	m_filesT1 = NULL;
	delete m_filesT2;
	m_filesT2 = NULL;
	delete m_filesSS2;
	m_filesSS2 = NULL;
	delete m_dirsT1;
	m_dirsT1 = NULL;
	delete m_dirsT2;
	m_dirsT2 = NULL;
	delete m_dirsSS2;
	m_dirsSS2 = NULL;
	m_installcfgT1 = 0;
	m_installcfgT2 = 0;
	m_installcfgSS2 = 0;
}

void Installer::AddInstall(string const& targ_id)
{
	TargList::iterator i = m_targs.find(targ_id);
	if (i != m_targs.end())
	{
		InstallTarget* targ = &(i->second);
		m_installgames |= targ->gameId();
		TargSet::iterator x = m_installs.find(targ);
		if (x == m_installs.end())
		{
			m_installs.insert(targ);
			targ->addInstallGames(targ->gameId());
			for (size_t d = 0; d < targ->numDependencies(); ++d)
			{
				_AddInstall(targ->getDependency(d), targ->gameId());
			}
		}
	}
}

void 
Installer::_AddInstall(string const& targ_id, int game)
{
	TargList::iterator i = m_targs.find(targ_id);
	if (i != m_targs.end())
	{
		InstallTarget* targ = &(i->second);
		TargSet::iterator x = m_installs.find(targ);
		if (x == m_installs.end())
		{
			m_installs.insert(targ);
			targ->addInstallGames(game);
			for (size_t d = 0; d < targ->numDependencies(); ++d)
			{
				_AddInstall(targ->getDependency(d), game);
			}
		}
		else
		{
			(*x)->addInstallGames(game);
		}
	}
}

void 
Installer::SetDestDirectory(int game, String const& dir)
{
	switch (game)
	{
	case DARKGAME_T1:
		m_destT1 = dir;
		replace(m_destT1.begin(), m_destT1.end(), TEXT('/'), TEXT('\\'));
		if (*m_destT1.end() == TEXT('\\'))
			m_destT1.erase(m_destT1.size() - 1);
		break;
	case DARKGAME_T2:
		m_destT2 = dir;
		replace(m_destT2.begin(), m_destT2.end(), TEXT('/'), TEXT('\\'));
		if (*m_destT2.end() == TEXT('\\'))
			m_destT2.erase(m_destT2.size() - 1);
		break;
	case DARKGAME_SS2:
		m_destSS2 = dir;
		replace(m_destSS2.begin(), m_destSS2.end(), TEXT('/'), TEXT('\\'));
		if (*m_destSS2.end() == TEXT('\\'))
			m_destSS2.erase(m_destSS2.size() - 1);
		break;
	}
}

/*
int
Installer::InstallGames() const
{
	int games = 0;
	for (TargSet::const_iterator t = m_installs.begin(); t != m_installs.end(); ++t)
		games |= (*t)->gameId();
	return games;
}
*/

bool
Installer::PreCollectFiles(HWND hwnd)
{
	if (m_backupT1)
		m_backupT1->clear();
	if (m_filesT1)
		m_filesT1->clear();
	if (m_dirsT1)
		m_dirsT1->clear();
	if (m_backupT2)
		m_backupT2->clear();
	if (m_filesT2)
		m_filesT2->clear();
	if (m_dirsT2)
		m_dirsT2->clear();
	if (m_backupSS2)
		m_backupSS2->clear();
	if (m_filesSS2)
		m_filesSS2->clear();
	if (m_dirsSS2)
		m_dirsSS2->clear();
	m_installcfgT1 = 0;
	m_installcfgT2 = 0;
	m_installcfgSS2 = 0;
	for (TargSet::iterator i = m_installs.begin(); i != m_installs.end(); ++i)
	{
		InstallTarget const* targ = *i;
		int installgames = m_installgames & targ->getInstallGames();
		if (installgames & DARKGAME_T1)
		{
			if (m_filesT1 == NULL)
				m_filesT1 = new FileSet();
			if (m_dirsT1 == NULL)
				m_dirsT1 = new DirSet();
		}
		if (installgames & DARKGAME_T2)
		{
			if (m_filesT2 == NULL)
				m_filesT2 = new FileSet();
			if (m_dirsT2 == NULL)
				m_dirsT2 = new DirSet();
		}
		if (installgames & DARKGAME_SS2)
		{
			if (m_filesSS2 == NULL)
				m_filesSS2 = new FileSet();
			if (m_dirsSS2 == NULL)
				m_dirsSS2 = new DirSet();
		}
		for (size_t f = 0; f < targ->numDirs(); ++f)
		{
			InstallDir const& dir = targ->getDir(f);
			if (installgames & DARKGAME_T1)
			{
				DirSet::iterator x = m_dirsT1->find(dir);
				if (x != m_dirsT1->end())
					const_cast<InstallDir&>(*x).addCfgEntry(dir);
				else
					m_dirsT1->insert(dir);
			}
			if (installgames & DARKGAME_T2)
			{
				DirSet::iterator x = m_dirsT2->find(dir);
				if (x != m_dirsT2->end())
					const_cast<InstallDir&>(*x).addCfgEntry(dir);
				else
					m_dirsT2->insert(dir);
			}
			if (installgames & DARKGAME_SS2)
			{
				DirSet::iterator x = m_dirsSS2->find(dir);
				if (x != m_dirsSS2->end())
					const_cast<InstallDir&>(*x).addCfgEntry(dir);
				else
					m_dirsSS2->insert(dir);
			}
		}
		for (size_t f = 0; f < targ->numFiles(); ++f)
		{
			InstallFile const& file = targ->getFile(f);
			if (! file.SourceExists())
			{
				LPCTSTR params[3] = { file.Source().c_str(), NULL, NULL };
				switch (::DialogBoxParam(Window::GetAppInstance(), 
					  MAKEINTRESOURCE(IDD_PROMPTMISSING_FILE), 
					  hwnd, error_dialog_cb, 
					  reinterpret_cast<LPARAM>(&params)
					))
				{
				case IDCANCEL:
					return false;
				}
				continue;
			}
			if (installgames & DARKGAME_T1)
			{
				m_filesT1->insert(file);
			}
			if (installgames & DARKGAME_T2)
			{
				m_filesT2->insert(file);
			}
			if (installgames & DARKGAME_SS2)
			{
				m_filesSS2->insert(file);
			}
		}
	}
	return true;
}

bool 
Installer::CollectFiles(HWND hwnd)
{
	FileSet* t1 = NULL;
	FileSet* t1old = NULL;
	FileSet* t2 = NULL;
	FileSet* t2old = NULL;
	FileSet* ss2 = NULL;
	FileSet* ss2old = NULL;

	if (m_installgames & DARKGAME_T1)
	{
		if (m_dirsT1)
		{
			for (DirSet::iterator d = m_dirsT1->begin(); d != m_dirsT1->end(); ++d)
			{
				if (d->FileExistsInDest(m_destT1))
				{
					PromptDirOverFile(hwnd, d->Dest(), m_destT1);
					return false;
				}
				if (d->NeedsInstallCfg())
					++m_installcfgT1;
			}
		}
		t1 = new FileSet();
		t1old = new FileSet();
		if (m_filesT1)
		{
			for (FileSet::iterator f = m_filesT1->begin(); f != m_filesT1->end(); ++f)
			{
				if (f->FileExistsInDest(m_destT1))
				{
					if (f->DestIsDirectory(m_destT1))
					{
						PromptFileOverDir(hwnd, f->Dest(), m_destT1);
						delete t1old;
						delete t1;
						return false;
					}
					else
					{
						switch (PromptDuplicateFile(hwnd, f->Source(), f->Dest(), m_destT1))
						{
						case OverwriteFileCancel:
							delete t1old;
							delete t1;
							return false;
						case OverwriteFileReplace:
							break;
						case OverwriteFileKeep:
							continue;
						}
						if (f->DestIsReadOnly(m_destT1))
						{
							switch (PromptReadOnlyFile(hwnd, f->Dest(), m_destT1))
							{
							case OverwriteFileCancel:
								delete t1old;
								delete t1;
								return false;
							case OverwriteFileReplace:
								break;
							case OverwriteFileKeep:
								continue;
							}
						}
						t1old->insert(*f);
						t1->insert(*f);
					}
				}
				else
					t1->insert(*f);
			}
		}
		if (t1old->empty())
		{
			delete t1old;
			t1old = NULL;
		}
		if (t1->empty())
		{
			delete t1;
			t1 = NULL;
		}
	}
	else
	{
		if (m_backupT1)
		{
			delete m_backupT1;
			m_backupT1 = NULL;
		}
		if (m_filesT1)
		{
			delete m_filesT1;
			m_filesT1 = NULL;
		}
		if (m_dirsT1)
		{
			delete m_dirsT1;
			m_dirsT1 = NULL;
		}
	}
	if (m_installgames & DARKGAME_T2)
	{
		if (m_dirsT2)
		{
			for (DirSet::iterator d = m_dirsT2->begin(); d != m_dirsT2->end(); ++d)
			{
				if (d->FileExistsInDest(m_destT2))
				{
					PromptDirOverFile(hwnd, d->Dest(), m_destT2);
					delete t1old;
					delete t1;
					return false;
				}
				if (d->NeedsInstallCfg())
					++m_installcfgT2;
			}
		}
		t2 = new FileSet();
		t2old = new FileSet();
		if (m_filesT2)
		{
			for (FileSet::iterator f = m_filesT2->begin(); f != m_filesT2->end(); ++f)
			{
				if (f->FileExistsInDest(m_destT2))
				{
					if (f->DestIsDirectory(m_destT2))
					{
						PromptFileOverDir(hwnd, f->Dest(), m_destT2);
						delete t2old;
						delete t2;
						delete t1old;
						delete t1;
						return false;
					}
					else
					{
						switch (PromptDuplicateFile(hwnd, f->Source(), f->Dest(), m_destT2))
						{
						case OverwriteFileCancel:
							delete t2old;
							delete t2;
							delete t1old;
							delete t1;
							return false;
						case OverwriteFileReplace:
							break;
						case OverwriteFileKeep:
							continue;
						}
						if (f->DestIsReadOnly(m_destT2))
						{
							switch (PromptReadOnlyFile(hwnd, f->Dest(), m_destT2))
							{
							case OverwriteFileCancel:
								delete t2old;
								delete t2;
								delete t1old;
								delete t1;
								return false;
							case OverwriteFileReplace:
								break;
							case OverwriteFileKeep:
								continue;
							}
						}
						t2old->insert(*f);
						t2->insert(*f);
					}
				}
				else
					t2->insert(*f);
			}
		}
		if (t2old->empty())
		{
			delete t2old;
			t2old = NULL;
		}
		if (t2->empty())
		{
			delete t2;
			t2 = NULL;
		}
	}
	else
	{
		if (m_backupT2)
		{
			delete m_backupT2;
			m_backupT2 = NULL;
		}
		if (m_filesT2)
		{
			delete m_filesT2;
			m_filesT2 = NULL;
		}
		if (m_dirsT2)
		{
			delete m_dirsT2;
			m_dirsT2 = NULL;
		}
	}
	if (m_installgames & DARKGAME_SS2)
	{
		if (m_dirsSS2)
		{
			for (DirSet::iterator d = m_dirsSS2->begin(); d != m_dirsSS2->end(); ++d)
			{
				if (d->FileExistsInDest(m_destSS2))
				{
					PromptDirOverFile(hwnd, d->Dest(), m_destSS2);
					delete t2old;
					delete t2;
					delete t1old;
					delete t1;
					return false;
				}
				if (d->NeedsInstallCfg())
					++m_installcfgSS2;
			}
		}
		ss2 = new FileSet();
		ss2old = new FileSet();
		if (m_filesSS2)
		{
			for (FileSet::iterator f = m_filesSS2->begin(); f != m_filesSS2->end(); ++f)
			{
				if (f->FileExistsInDest(m_destSS2))
				{
					if (f->DestIsDirectory(m_destSS2))
					{
						PromptFileOverDir(hwnd, f->Dest(), m_destSS2);
						delete ss2old;
						delete ss2;
						delete t2old;
						delete t2;
						delete t1old;
						delete t1;
						return false;
					}
					else
					{
						switch (PromptDuplicateFile(hwnd, f->Source(), f->Dest(), m_destSS2))
						{
						case OverwriteFileCancel:
							delete ss2old;
							delete ss2;
							delete t2old;
							delete t2;
							delete t1old;
							delete t1;
							return false;
						case OverwriteFileReplace:
							break;
						case OverwriteFileKeep:
							continue;
						}
						if (f->DestIsReadOnly(m_destSS2))
						{
							switch (PromptReadOnlyFile(hwnd, f->Dest(), m_destSS2))
							{
							case OverwriteFileCancel:
								delete ss2old;
								delete ss2;
								delete t2old;
								delete t2;
								delete t1old;
								delete t1;
								return false;
							case OverwriteFileReplace:
								break;
							case OverwriteFileKeep:
								continue;
							}
						}
						ss2old->insert(*f);
						ss2->insert(*f);
					}
				}
				else
					ss2->insert(*f);
			}
		}
		if (ss2old->empty())
		{
			delete ss2old;
			ss2old = NULL;
		}
		if (ss2->empty())
		{
			delete ss2;
			ss2 = NULL;
		}
	}
	else
	{
		if (m_backupSS2)
		{
			delete m_backupSS2;
			m_backupSS2 = NULL;
		}
		if (m_filesSS2)
		{
			delete m_filesSS2;
			m_filesSS2 = NULL;
		}
		if (m_dirsSS2)
		{
			delete m_dirsSS2;
			m_dirsSS2 = NULL;
		}
	}

	if (m_backupT1)
		delete m_backupT1;
	m_backupT1 = t1old;
	if (m_filesT1)
		delete m_filesT1;
	m_filesT1 = t1;
	if (m_backupT2)
		delete m_backupT2;
	m_backupT2 = t2old;
	if (m_filesT2)
		delete m_filesT2;
	m_filesT2 = t2;
	if (m_backupSS2)
		delete m_backupSS2;
	m_backupSS2 = ss2old;
	if (m_filesSS2)
		delete m_filesSS2;
	m_filesSS2 = ss2;

	return true;
}

Thread* 
Installer::CopyFiles(ProgressPage& progress)
{
	if (m_installgames == 0)
	{
		progress.postMessage(WM_APP_INSTALL_NOTHING);
		return NULL;
	}
	size_t numfiles = 0;
	if (m_installgames & DARKGAME_T1)
	{
		if (m_dirsT1)
		{
			numfiles += m_dirsT1->size();
			numfiles += m_installcfgT1;
		}
		if (m_filesT1)
			numfiles += m_filesT1->size();
		if (m_backupT1)
			numfiles += m_backupT1->size();
	}
	if (m_installgames & DARKGAME_T2)
	{
		if (m_dirsT2)
		{
			numfiles += m_dirsT2->size();
			numfiles += m_installcfgT2;
		}
		if (m_filesT2)
			numfiles += m_filesT2->size();
		if (m_backupT2)
			numfiles += m_backupT2->size();
	}
	if (m_installgames & DARKGAME_SS2)
	{
		if (m_dirsSS2)
		{
			numfiles += m_dirsSS2->size();
			numfiles += m_installcfgSS2;
		}
		if (m_filesSS2)
			numfiles += m_filesSS2->size();
		if (m_backupSS2)
			numfiles += m_backupSS2->size();
	}
	if (numfiles == 0)
	{
		progress.postMessage(WM_APP_INSTALL_NOTHING);
		return NULL;
	}

	progress.initProgress(numfiles);

	CopierThread* copier = new CopierThread(*this, progress);
	return copier;
}

void 
Installer::CopierThread::run()
{
	m_progress.postMessage(WM_APP_INSTALL_BEGIN);

	if (m_installer.m_installgames & DARKGAME_T1)
		process(m_installer.m_destT1, m_installer.m_backupT1,
			m_installer.m_filesT1, m_installer.m_dirsT1, 
			m_installer.m_installcfgT1, dark_install_cfg_file);
	if (m_installer.m_installgames & DARKGAME_T2)
		process(m_installer.m_destT2, m_installer.m_backupT2,
			m_installer.m_filesT2, m_installer.m_dirsT2, 
			m_installer.m_installcfgT2, dark_darkinst_cfg_file);
	if (m_installer.m_installgames & DARKGAME_SS2)
		process(m_installer.m_destSS2, m_installer.m_backupSS2, 
			m_installer.m_filesSS2, m_installer.m_dirsSS2,
			m_installer.m_installcfgSS2, dark_install_cfg_file);

	m_progress.postMessage(WM_APP_INSTALL_COMPLETE);
}

void
Installer::CopierThread::process(String const& dest, FileSet const* backup, FileSet const* files, 
				 DirSet const* dirs, unsigned int cfgs, LPCTSTR cfgname)
{
	m_progress.setDirectoryName(dest);
	m_backupfile.clear();
	if (backup)
	{
		m_progress.setAction(IDS_ACTION_BACKUPFILES);
		Backup bu(m_installer.m_backupname);
		if (! bu.SetDest(dest))
		{
			if (IDCANCEL == BackupError(dest))
			{
				cancel();
			}
		}
		for (FileSet::iterator f = backup->begin();
			 f != backup->end(); ++f)
		{
			m_progress.setFileName(f->Dest());
			if (! bu.AddFile(f->Dest()))
			{
				bu.Close();
				if (IDCANCEL == BackupError(dest))
				{
					cancel();
				}
			}
			m_progress.advanceProgress();
		}
		if (cfgs > 0
		 && InstallFile::FileExists(dest + cfgname))
		{
			if (! bu.AddFile(cfgname))
			{
				bu.Close();
				if (IDCANCEL == BackupError(dest))
				{
					cancel();
				}
			}
		}
		m_backupfile = bu.GetBackupName();
	}
	{
		SYSTEMTIME st;
		::GetSystemTime(&st);
		::SystemTimeToFileTime(&st, &m_starttime);
	}
	if (dirs)
	{
		m_progress.setAction(IDS_ACTION_CREATEDIR);
		for (DirSet::iterator d = dirs->begin();
			 d != dirs->end(); ++d)
		{
			m_progress.setFileName(d->Dest());
			if (! d->CreateDirectoryInDest(dest))
			{
				if (IDCANCEL == CopyDirError(*d, dest))
				{
					cleanup(dest, files, dirs);
					cancel();
				}
			}
			m_progress.advanceProgress();
		}
	}
	if (files)
	{
		m_progress.setAction(IDS_ACTION_COPYFILE);
		for (FileSet::iterator f = files->begin();
			 f != files->end(); ++f)
		{
			m_progress.setFileName(f->Dest());
			if (! f->CopyToDest(dest))
			{
				if (IDCANCEL == CopyFileError(*f, dest))
				{
					cleanup(dest, files, dirs);
					cancel();
				}
			}
			m_progress.advanceProgress();
		}
	}
	if (dirs && cfgs > 0)
	{
		m_progress.setAction(IDS_ACTION_UPDATECONFIG);
		String installname = dest + cfgname;
		InstallCfg cfgfile;
		if (! cfgfile.LoadFile(installname))
		{
			if (IDCANCEL == UpdateInstallCfgError(dest))
			{
				cleanup(dest, files, dirs);
				cancel();
			}
			m_progress.advanceProgress(cfgs);
		}
		else
		{
			for (DirSet::iterator d = dirs->begin();
			     d != dirs->end(); ++d)
			{
				if (d->NeedsInstallCfg())
				{
					d->UpdateInstallCfg(cfgfile, dest);
					m_progress.advanceProgress();
				}
			}
			if (!cfgfile.SaveFile(installname))
			{
				if (IDCANCEL == UpdateInstallCfgError(dest))
				{
					cleanup(dest, files, dirs);
					cancel();
				}
			}
		}
	}
}

void
Installer::CopierThread::cleanup(String const& dest, FileSet const* files, DirSet const* dirs)
{
	if (m_backupfile.empty())
		return;
	
	m_progress.setAction(IDS_ACTION_RESTOREFILES);
	if (files)
	{
		for (FileSet::iterator f = files->begin();
			 f != files->end(); ++f)
		{
			m_progress.setFileName(f->Dest());
			f->DeleteFromDest(dest);
			//m_progress.advanceProgress();
		}
	}
	if (dirs)
	{
		for (DirSet::iterator d = dirs->begin();
			 d != dirs->end(); ++d)
		{
			m_progress.setFileName(d->Dest());
			d->DeleteDirectoryFromDest(dest, &m_starttime);
			//m_progress.advanceProgress();
		}
	}
	Restore bu(m_backupfile);
	bu.CopyToDest(dest);
}

void
Installer::CopierThread::cancel()
{
	m_progress.postMessage(WM_APP_INSTALL_CANCEL);
	exit(1);
}

int 
Installer::CopierThread::BackupError(String const& dest)
{
	TCHAR msg[1024];
	DWORD err = ::GetLastError();
	DWORD len = ::FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, 
				    NULL, err, ::GetUserDefaultLangID(), msg, 1024, NULL);
	if (len != 0)
	{
		// Damn \r\n...
		msg[len-2] = TEXT('\0');
	}
	else
	{
		wsprintf(msg, TEXT("%d"), err);
	}
	LPCTSTR params[3] = { NULL, dest.c_str(), msg };
	return ::DialogBoxParam(Window::GetAppInstance(),
				   MAKEINTRESOURCE(IDD_ERRORINSTALL_BACKUP),
				   m_progress.getHWND(), error_dialog_cb,
				   reinterpret_cast<LPARAM>(&params));
}

int 
Installer::CopierThread::CopyFileError(InstallFile const& file, String const& dest)
{
	TCHAR msg[1024];
	DWORD err = ::GetLastError();
	DWORD len = ::FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, 
				    NULL, err, ::GetUserDefaultLangID(), msg, 1024, NULL);
	if (len != 0)
	{
		// Damn \r\n...
		msg[len-2] = TEXT('\0');
	}
	else
	{
		wsprintf(msg, TEXT("%d"), err);
	}
	LPCTSTR params[3] = { file.Dest().c_str(), dest.c_str(), msg };
	return ::DialogBoxParam(Window::GetAppInstance(),
				   MAKEINTRESOURCE(IDD_ERRORINSTALL_FILE),
				   m_progress.getHWND(), error_dialog_cb,
				   reinterpret_cast<LPARAM>(&params));
}

int 
Installer::CopierThread::CopyDirError(InstallDir const& file, String const& dest)
{
	TCHAR msg[1024];
	DWORD err = ::GetLastError();
	DWORD len = ::FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, 
				    NULL, err, ::GetUserDefaultLangID(), msg, 1024, NULL);
	if (len != 0)
	{
		msg[len-2] = TEXT('\0');
	}
	else
	{
		wsprintf(msg, TEXT("%d"), err);
	}
	LPCTSTR params[3] = { file.Dest().c_str(), dest.c_str(), msg };
	return ::DialogBoxParam(Window::GetAppInstance(),
				   MAKEINTRESOURCE(IDD_ERRORINSTALL_DIR),
				   m_progress.getHWND(), error_dialog_cb,
				   reinterpret_cast<LPARAM>(&params));
}

int 
Installer::CopierThread::UpdateInstallCfgError(String const& dest)
{
	TCHAR msg[1024];
	DWORD err = ::GetLastError();
	DWORD len = ::FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, 
				    NULL, err, ::GetUserDefaultLangID(), msg, 1024, NULL);
	if (len != 0)
	{
		msg[len-2] = TEXT('\0');
	}
	else
	{
		wsprintf(msg, TEXT("%d"), err);
	}
	LPCTSTR params[3] = { NULL, dest.c_str(), msg };
	return ::DialogBoxParam(Window::GetAppInstance(),
				   MAKEINTRESOURCE(IDD_ERRORINSTALL_CONFIG),
				   m_progress.getHWND(), error_dialog_cb,
				   reinterpret_cast<LPARAM>(&params));
}

bool 
Installer::LoadFromRdf(const char* rdffile)
{
	RdfGraph graph;
	if (! graph.LoadFromFile(rdffile))
	{
		String title = String::Load(IDS_ERR_TITLE);
		String msg = String::Load(IDS_LOADRDF_FAILED);
		String err = graph.ErrorMsg;
#ifdef UNICODE
		err = Win32::LocalizeString(err.to_ucs2());
#endif
		msg += err + TEXT("\n");
		err = graph.ErrorLoc;
#ifdef UNICODE
		err = Win32::LocalizeString(err.to_ucs2());
#endif
		msg += err + TEXT("\n");
		::MessageBox(NULL, msg.c_str(), title.c_str(), MB_ICONERROR | MB_OK | MB_TASKMODAL);
		return false;
	}

	Reset();
	m_visiblesofts.clear();
	m_targs.clear();
	m_softs.clear();
	m_backupname = backup_archive_file;

	RdfGraph::TripleList allsofts = graph.FindByType(rdf_vi_software);
	for (size_t n = 0; n < allsofts.size(); ++n)
	{
		string const& softid = allsofts[n]->subj;
		RdfTriple* softname = graph.FindOneBySubjectPredicateLang(softid.c_str(),
					    rdf_vi_name, Win32::Locale());
		if (!softname)
			continue;
		InstallSoftware& softinfo = (m_softs.insert(
					     make_pair(softid,InstallSoftware(softid, softname->obj))
					     ).first)->second;
		RdfGraph::TripleList installs = graph.FindBySubjectPredicate(softid.c_str(), rdf_vi_install);
		for (size_t i = 0; i < installs.size(); ++i)
		{
			string const& installid = installs[i]->obj;
			RdfTriple* installversion = graph.FindOneBySubjectPredicate(installid.c_str(), rdf_vi_version);
			if (!installversion)
				continue;
			InstallTarget& targinfo = (m_targs.insert(
						   make_pair(installid,
							     InstallTarget(softinfo,installid,installversion->obj))
						   ).first)->second;
			softinfo.addTarget(installid);
			RdfGraph::TripleList games = graph.FindBySubjectPredicate(installid.c_str(), rdf_dark_game);
			if (games.size() == 0)
			{
				targinfo.addGame(DARKGAME_ALL);
			}
			else for (size_t g = 0; g < games.size(); ++g)
			{
				targinfo.addGame(games[g]->obj);
			}
			RdfGraph::TripleList deps = graph.FindBySubjectPredicate(installid.c_str(), rdf_vi_dependency);
			for (size_t d = 0; d < deps.size(); ++d)
			{
				targinfo.addDependency(deps[d]->obj);
			}
			RdfGraph::TripleList files = graph.FindBySubjectPredicate(installid.c_str(), rdf_vi_installfile);
			for (size_t f = 0; f < files.size(); ++f)
			{
				RdfTriple* filesrc = graph.FindOneBySubjectPredicate(files[f]->obj.c_str(), rdf_vi_source);
				if (!filesrc)
					continue;
				RdfTriple* filedst = graph.FindOneBySubjectPredicate(files[f]->obj.c_str(), rdf_vi_destination);
				bool readonly = false;
				RdfTriple* fileflag = graph.FindOneBySubjectPredicate(files[f]->obj.c_str(), rdf_vi_readonly);
				readonly = (fileflag != NULL);
				if (filedst)
					targinfo.addFile(filesrc->obj, filedst->obj, readonly);
				else
					targinfo.addFile(filesrc->obj, readonly);
			}
			RdfGraph::TripleList dirs = graph.FindBySubjectPredicate(installid.c_str(), rdf_vi_installdir);
			for (size_t d = 0; d < dirs.size(); ++d)
			{
				RdfTriple* dirname = graph.FindOneBySubjectPredicate(dirs[d]->obj.c_str(), rdf_vi_destination);
				if (dirname)
				{
					InstallDir* dir = targinfo.addDir(dirname->obj);
					RdfGraph::TripleList cfgs = graph.FindBySubjectPredicate(dirs[d]->obj.c_str(), rdf_dark_installcfg);
					for (size_t c = 0; c < cfgs.size(); ++c)
					{
						dir->addCfgEntry(cfgs[c]->obj);
					}
					cfgs = graph.FindBySubjectPredicate(dirs[d]->obj.c_str(), rdf_dark_preinstallcfg);
					for (size_t c = 0; c < cfgs.size(); ++c)
					{
						dir->addCfgEntry(cfgs[c]->obj, InstallDir::CfgPrefix);
					}
					cfgs = graph.FindBySubjectPredicate(dirs[d]->obj.c_str(), rdf_dark_postinstallcfg);
					for (size_t c = 0; c < cfgs.size(); ++c)
					{
						dir->addCfgEntry(cfgs[c]->obj, InstallDir::CfgPostfix);
					}
				}
			}
		}
	}

	for (TargList::iterator t = m_targs.begin(); t != m_targs.end(); ++t)
	{
		vector<string> addlist, remlist;
		InstallTarget& targinfo = t->second;
		for (size_t d = 0; d < targinfo.numDependencies(); ++d)
		{
			string const& depid = targinfo.getDependency(d);
			RdfTriple* deptype = graph.FindOneBySubjectPredicate(depid.c_str(), rdf_rdf_predicate_type);
			// I can't imagine what deranged input could cause this to be NULL
			if (deptype->obj == rdf_vi_software)
			{
				//targinfo.removeDependency(depid);
				remlist.push_back(depid);
				SoftList::iterator s = m_softs.find(depid);
				if (s != m_softs.end())
				{
					InstallSoftware& depsoft = s->second;
					for (size_t dt = 0; dt < depsoft.numTargets(); ++dt)
					{
						int games = targinfo.gameId();
						InstallTarget* deptarg = _getTarget(depsoft.getTarget(dt));
						if (!deptarg)
							continue;
						if (deptarg->gameId() & games)
						{
							//targinfo.addDependency(deptarg->id());
							addlist.push_back(deptarg->id());
							games &= ~deptarg->gameId();
							if (games == 0)
								break;
						}
					}
				}
			}
			else if (deptype->obj == rdf_vi_target)
			{
				if (! _getTarget(depid))
					remlist.push_back(depid);
			}
			else
				remlist.push_back(depid);
		}
		for (vector<string>::iterator i = remlist.begin(); 
		     i != remlist.end(); ++i)
			     targinfo.removeDependency(*i);
		for (vector<string>::iterator i = addlist.begin(); 
		     i != addlist.end(); ++i)
			     targinfo.addDependency(*i);
	}

	RdfGraph::TripleList listsofts = graph.GetSequence(rdf_vi_installsoftware);
	for (size_t n = 0; n < listsofts.size(); ++n)
	{
		string const& softid = listsofts[n]->obj;
		SoftList::iterator s = m_softs.find(softid);
		if (s == m_softs.end())
			continue;
		InstallSoftware* soft = &(s->second);
		vector<InstallSoftware*>::iterator x = 
				find(m_visiblesofts.begin(), m_visiblesofts.end(), soft);
		if (x == m_visiblesofts.end())
			m_visiblesofts.insert(x, soft);
	}

	RdfTriple* backupname = graph.FindOneBySubjectPredicate(rdf_vi_installdescription, rdf_vi_backupname);
	if (backupname)
	{
#ifdef UNICODE
		m_backupname += String(backupname->obj);
#else
		m_backupname += Win32::LocalizeString(backupname->obj.c_str(), backupname->obj.size());
#endif
		m_backupname += TEXT("-");
	}

	return true;
}

Installer::OverwriteFileAction
Installer::PromptDuplicateFile(HWND hwnd, String const& srcfile, String const& destfile, String const& destdir)
{
	String destpath = destdir;
	if (*destpath.end() != TEXT('\\'))
		destpath += TEXT('\\');
	destpath += destfile;

	FileVersion destver(destpath);
	FileVersion srcver(srcfile);
	if (srcver < destver)
	{
		LPCTSTR params[2] = { destfile.c_str(), destdir.c_str() };
		switch (::DialogBoxParam(Window::GetAppInstance(), 
			  MAKEINTRESOURCE(IDD_PROMPTDUPLICATE_FILE),
			  hwnd, prompt_dialog_cb,
			  reinterpret_cast<LPARAM>(&params)
			))
		{
		case IDCANCEL:
			return OverwriteFileCancel;
		case IDC_DUPLICATE_KEEP:
			return OverwriteFileKeep;
		case IDC_DUPLICATE_REPLACE:
			return OverwriteFileReplace;
		}
	}

	return OverwriteFileReplace;
}

Installer::OverwriteFileAction
Installer::PromptReadOnlyFile(HWND hwnd, String const& destfile, String const& destdir)
{
	LPCTSTR params[2] = { destfile.c_str(), destdir.c_str() };
	switch (::DialogBoxParam(Window::GetAppInstance(), 
		  MAKEINTRESOURCE(IDD_PROMPTREADONLY_FILE),
		  hwnd, prompt_dialog_cb,
		  reinterpret_cast<LPARAM>(&params)
		))
	{
	case IDCANCEL:
		return OverwriteFileCancel;
	case IDC_READONLY_KEEP:
		return OverwriteFileKeep;
	case IDC_READONLY_REPLACE:
		return OverwriteFileReplace;
	}

	return OverwriteFileReplace;
}

void
Installer::PromptDirOverFile(HWND hwnd, String const& destfile, String const& destdir)
{
	LPCTSTR params[3] = { destfile.c_str(), destdir.c_str(), NULL };
	::DialogBoxParam(Window::GetAppInstance(), 
			 MAKEINTRESOURCE(IDD_ERRORDUPLICATE_DIR),
			 hwnd, error_dialog_cb,
			 reinterpret_cast<LPARAM>(&params));
}

void
Installer::PromptFileOverDir(HWND hwnd, String const& destfile, String const& destdir)
{
	LPCTSTR params[3] = { destfile.c_str(), destdir.c_str(), NULL };
	::DialogBoxParam(Window::GetAppInstance(), 
			 MAKEINTRESOURCE(IDD_ERRORDUPLICATE_FILE),
			 hwnd, error_dialog_cb,
			 reinterpret_cast<LPARAM>(&params));
}

