#include "stdafx.h"

#include <string>

#include "DenAgent.h"
#include "DownloaderDlg.h"
#include "AutoUpdate.h"

static LPCSTR updateList = "updatelist.xml" ;

// DownloadAgent

/* static */ bool DownloadAgent::downloadFile(std::string remoteFile, std::string localFile) {
	// Perform a direct, one time download of a file

	cDownloaderDlg dlg;

	dlg.addFile(remoteFile, localFile);

	dlg.DoModal();

	return (dlg.GetDownloadStatus() == cDownloaderDlg::DownloadStatus::DOWNLOAD_SUCCEEDED);
}

DownloadAgent::DownloadAgent() {
	m_ScheduledDownloads.clear();
}

void DownloadAgent::scheduleDownload(std::string remoteFile, std::string localFile) {
	// Encapsulate this download in a download slot, and add it to the schedule list

	DownloadSlot slot;

	slot.remoteFile = remoteFile;
	slot.localFile = localFile;

	m_ScheduledDownloads.push_back(slot);
}

bool DownloadAgent::runDownloads() {
	if (!m_ScheduledDownloads.size())
		return false;

	cDownloaderDlg dlg;

	// Add each schduled download to our downloader
	std::list<DownloadSlot>::iterator it;

	for (it = m_ScheduledDownloads.begin(); it != m_ScheduledDownloads.end(); ++it) {
		DownloadSlot slot = *it;

		dlg.addFile(slot.remoteFile, slot.localFile);
	}

	// And run them
	dlg.DoModal();

	return (dlg.GetDownloadStatus() == cDownloaderDlg::DownloadStatus::DOWNLOAD_SUCCEEDED);
}

// AutoUpdateSource

AutoUpdateSource::AutoUpdateSource(std::string decalDir, AutoUpdateType type, std::string localTarget, std::string remoteSource, std::string requiredVersion) 
	: m_DecalDir(decalDir)
	, m_LocalTarget(localTarget)
	, m_RequiredVersion(requiredVersion)
	, m_RemoteSource(remoteSource)
	, m_Type(type)
{
}

bool AutoUpdateSource::needsUpdating() {
	USES_CONVERSION;

	// Get the version of the local file

	// Make sure the file exists

	OFSTRUCT targetFile;
	std::string localTarget = m_DecalDir + "/" + m_LocalTarget;

	if (OpenFile(localTarget.c_str(), &targetFile, OF_EXIST) == HFILE_ERROR) {
		// It didn't exist, we must update
		return true;
	}

	if (m_RequiredVersion.length() == 0) {
		// If we don't have a remote version, assume we want to update
		return true;
	}

	int reqMajor(0), reqMinor(0), reqPatch(0), reqBuild(0) ;
	sscanf(m_RequiredVersion.c_str(), "%i.%i.%i.%i", &reqMajor, &reqMinor, &reqPatch, &reqBuild);

	if (m_Type == AU_TYPE_XML) {
		// Extract the version node from an XML document
		MSXML::IXMLDOMDocumentPtr pDoc;

		try {
			pDoc.CreateInstance (__uuidof(MSXML::DOMDocument), NULL, CLSCTX_INPROC_SERVER);
			pDoc->async = false;

			// Construct a list of update targets based on the XML contents
			if (pDoc->load(static_cast<LPCTSTR>(localTarget.c_str()))) {
				// Read the autoupdate version string
//				MSXML::IXMLDOMNodePtr pVersionNode = pDoc->selectSingleNode ( _T( "/*/@version" ) );
				MSXML::IXMLDOMElementPtr pVersionNode = pDoc->selectSingleNode ("//revision");
				if (pVersionNode != NULL) {
//					_variant_t vXMLVer = pVersionNode->text;
					_variant_t vXMLVer = pVersionNode->getAttribute("version") ;
					if (vXMLVer.vt == VT_BSTR) {
						int locMajor(0), locMinor(0), locPatch(0), locBuild(0) ;
						sscanf(OLE2T(vXMLVer.bstrVal), "%i.%i.%i.%i", &locMajor, &locMinor, &locPatch, &locBuild);
						if (locMajor==reqMajor) {
							if (locMinor==reqMinor) {
								if (locPatch==reqPatch) {
									return locBuild<reqBuild ;
								} else {
									return locPatch<reqPatch ;
								}
							} else {
								return locMinor<reqMinor ;
							}
						} else {
							return locMajor<reqMajor ;
						}
					}
				}
			}
		} catch (...) {
			return true ;	// if error reading xml, assume needs updating
		}
	} else if (m_Type == AU_TYPE_DLL || m_Type == AU_TYPE_BETADLL) {
		// Check to see if the file itself needs updating
		if (isOutDatedVersion(localTarget, reqMajor, reqMinor, reqPatch, reqBuild))
		{
			// File is outdated, check requirements
			std::vector<UpdateRequirement>::iterator it;
			for (it = m_Requirements.begin(); it != m_Requirements.end(); ++it)
			{
				UpdateRequirement ur = *it;
				localTarget = m_DecalDir + "/" + ur.sFile;
				sscanf(ur.sVers.c_str(), "%i.%i.%i.%i", &reqMajor, &reqMinor, &reqPatch, &reqBuild);
				if (isOutDatedVersion(localTarget, reqMajor, reqMinor, reqPatch, reqBuild))
				{
					std::string sMsg = m_LocalTarget + " cannot be updated because one or more dependencies is outdated";
					::MessageBox(0, _T(sMsg.c_str()), NULL, S_OK);
					return false;
				}
			}
			return true;
		}
		else	// File is newer, ignore requirements and exit
		{	
			return false;
		}
	}
	// if anything went wrong or we were unable to tell whether it should be updated
	//	or not...assume it needs updating.
	return true ;
}

std::string AutoUpdateSource::getSource() {
	return m_RemoteSource;
}

std::string AutoUpdateSource::getDestination() {
	return m_LocalTarget;
}

AutoUpdateType AutoUpdateSource::getType() {
	return m_Type;
}

bool AutoUpdateSource::isOutDatedVersion(std::string sFile, int nMajor, int nMinor, int nPatch, int nBuild)
{
	// Extract the DLL version
	DWORD dwDummy, dwVerSize;

	dwVerSize = ::GetFileVersionInfoSize(const_cast<LPTSTR>(sFile.c_str()), &dwDummy);

	if( dwVerSize == 0 )
		return true;	// if file vsn not available, assume needs updating
	else
	{
		BYTE *pbVersionInfo = reinterpret_cast< BYTE * >(::_alloca(dwVerSize));

		::GetFileVersionInfo(const_cast<LPTSTR>(sFile.c_str()), 0, dwVerSize, pbVersionInfo);

		VS_FIXEDFILEINFO *vffi;
		UINT nLength = sizeof(VS_FIXEDFILEINFO);

		if( ::VerQueryValue( pbVersionInfo, _T("\\"), reinterpret_cast< LPVOID * >( &vffi ), &nLength ) )
		{
			// Got it, so format it
			int locMajor(0), locMinor(0), locPatch(0), locBuild(0);
			locMajor = static_cast< int >( HIWORD( vffi->dwFileVersionMS ) );
			locMinor = static_cast< int >( LOWORD( vffi->dwFileVersionMS ) );
			locPatch = static_cast< int >( HIWORD( vffi->dwFileVersionLS ) );
			locBuild = static_cast< int >( LOWORD( vffi->dwFileVersionLS ) );

			if( locMajor == nMajor )
			{
				if( locMinor == nMinor )
				{
					if( locPatch == nPatch )
						return locBuild < nBuild;
					else
						return locPatch < nPatch;
				}
				
				else
					return locMinor < nMinor ;
			}
			
			else
				return locMajor < nMajor ;
		}

		// problem with VerQueryValue... maybe updating will fix it.
		else
			return true;
	}
}

void AutoUpdateSource::AddRequirement(UpdateRequirement reqUpdate)
{
	m_Requirements.push_back(reqUpdate);
}

// AutoUpdate

AutoUpdate::AutoUpdate(std::string decalDir) {
	m_DecalDir = decalDir;
	m_LocalXML = "";	

	m_RemoteSources.clear();
}

bool AutoUpdate::initialiseFromXML(std::string remoteXML) {
	USES_CONVERSION;

	m_RemoteXML = remoteXML + "/" + updateList ;

	// Download the remote XML document to a local file
	if (!DownloadAgent::downloadFile(m_RemoteXML, m_DecalDir + "/" + updateList))
	{
		// We couldn't download the update list. We must bail here, or suffer!
		::MessageBox(NULL, "Could not obtain updatelist.xml. Cannot update - Hosting server down?", NULL, MB_OK);

		return false;
	}

	// Parse it as an XML document
	MSXML::IXMLDOMDocumentPtr pDoc;

//	try {
		pDoc.CreateInstance (__uuidof(MSXML::DOMDocument), NULL, CLSCTX_INPROC_SERVER);
		pDoc->async = false;

		// Construct a list of update targets based on the XML contents
		std::string localXML = m_DecalDir + "/" + updateList ;

		if (!pDoc->load( static_cast<LPCTSTR>(localXML.c_str())))
			return false;

		// Read the autoupdate version string
		MSXML::IXMLDOMNodePtr pVersionNode = pDoc->selectSingleNode ( _T( "/*/@version" ) );

		if (pVersionNode == NULL) {
			::MessageBox(NULL,"No version information in updatelist.xml. Cannot update",NULL,MB_OK) ;
			return false;
		}

		// Check to make sure we understand this version		
		char *version = OLE2T(pVersionNode->text);
		if (strcmp(version,"1.0.0.0")) {
			::MessageBox(NULL,"Unknown version type in updatelist.xml. Cannot update",NULL,MB_OK) ;
			return false ;
		}

		// Now iterate over the Files entries, creating an AutoUpdateSource for each
		MSXML::IXMLDOMNodeListPtr xmlFiles = pDoc->selectNodes(_T("/updatelist/file"));

		if (xmlFiles.GetInterfacePtr() == NULL)
			return false;

		for (MSXML::IXMLDOMElementPtr xmlFile = xmlFiles->nextNode(); xmlFile.GetInterfacePtr() != NULL; xmlFile = xmlFiles->nextNode()) {			
			std::string localFile, remoteFile, version;
			AutoUpdateType type;
			BSTR bResult;
			_variant_t vResult;

			// Read the type
			vResult = xmlFile->getAttribute("type");

			if (vResult.vt != VT_BSTR)
				continue;

			bResult = vResult.bstrVal;
			char *cType = OLE2T(bResult);

			if (strcmp(cType, "xml") == 0) {
				type = AU_TYPE_XML;
			} else if (strcmp(cType, "dll") == 0) {
				type = AU_TYPE_DLL;
			} else if (strcmp(cType, "beta") == 0) {
				type = AU_TYPE_BETADLL ;
			} else {
				continue;
			}

			// Local name
			vResult = xmlFile->getAttribute("localname");

			if (vResult.vt != VT_BSTR)
				continue;

			bResult = vResult.bstrVal;
			localFile = OLE2T(bResult);

			// Remote name
			vResult = xmlFile->getAttribute("remotename");

			if (vResult.vt != VT_BSTR)
				continue;

			bResult = vResult.bstrVal;
			remoteFile = OLE2T(bResult);

			// And version
			vResult = xmlFile->getAttribute("version");

			if (vResult.vt != VT_BSTR)
				continue;

			bResult = vResult.bstrVal;
			version = OLE2T(bResult);

			AutoUpdateSource aUSource(m_DecalDir, type, localFile, remoteFile, version);
			//if the source is dll || beta check for requirements
			if (type == AU_TYPE_DLL || type == AU_TYPE_BETADLL)
			{
				MSXML::IXMLDOMNodeListPtr xmlReqs = NULL;//xmlFile->selectNodes(_T("requirement"));  //pDoc->selectNodes(_T("/updatelist/file"));
				xmlReqs = xmlFile->getElementsByTagName(_T("requirement"));

				if (xmlReqs.GetInterfacePtr() == NULL)
					continue;

				for (MSXML::IXMLDOMElementPtr xmlNode = xmlReqs->nextNode(); xmlNode.GetInterfacePtr() != NULL; xmlNode = xmlReqs->nextNode())
				{
					std::string sLocalFile, sVersion;

					// Read the file name
					vResult = xmlNode->getAttribute("file");
					if (vResult.vt != VT_BSTR)
						continue;

					bResult = vResult.bstrVal;
					sLocalFile = OLE2T(bResult);
					
					// Read the version
					vResult = xmlNode->getAttribute("version");
					if (vResult.vt != VT_BSTR)
						continue;

					bResult = vResult.bstrVal;
					sVersion = OLE2T(bResult);
					
					UpdateRequirement uReq(sLocalFile, sVersion);
					aUSource.AddRequirement(uReq);
				}
			}
			// Add the source to the cache
			m_RemoteSources.push_back(aUSource);
		}		
//	} catch (...) {
//		return false;
//	}

	return true;
}

bool AutoUpdate::needsUpdate() {
	// Ask all the dependancies if they want to update themselves

	// Check the versions of all our local XML documents to see if we need an update
	std::list<AutoUpdateSource>::iterator it;

	for (it = m_RemoteSources.begin(); it != m_RemoteSources.end(); ++it) {
		AutoUpdateSource aUSource = *it;

		if (aUSource.needsUpdating())
			return true;
	}

	// Nothing needed updating
	return false;
}

bool AutoUpdate::performUpdate() {
	// Construct a list of all the remote components that need updating
	std::list<AutoUpdateSource> updateList;
	DownloadAgent dlAgent;


	bool DllUpdate = false ;
	bool BetaUpdate = false ;
	std::list<AutoUpdateSource>::iterator itRemote = m_RemoteSources.begin();;
	while (itRemote != m_RemoteSources.end()) {
		AutoUpdateSource aUSource = *itRemote++;

		if (aUSource.needsUpdating()) {
			switch(aUSource.getType()) {
			case AU_TYPE_DLL:		DllUpdate = true ; break ;
			case AU_TYPE_BETADLL:	BetaUpdate = true ; break ;
			}
			if (aUSource.getType()==AU_TYPE_BETADLL) {
				BetaUpdate = true ;
			} 
			updateList.push_back(aUSource);
		}
	}
	if (DllUpdate) {
		if (	MessageBox(NULL,
				"Updates to the Decal program are available.\n\n"
				"These are supported updates to the current release\n"
				"version of Decal.\n"
				"Installation of these updates is recommended.\n\n"
				"Do you wish to install these updates now?",
				"Decal Program Updates Available",
				MB_YESNO) == IDNO
			){
			DllUpdate = false ;
		}
	}
	if (BetaUpdate) {
		if (MessageBox(NULL, 
			"Decal Beta updates are available.\n\n"
			"NOTE: Beta versions are not supported and have NOT\n"
			"been tested extensively.\n"
			" They can crash your system, and require a full\n"
			"re-install of the original Decal program.\n\n"
			"Do you wish to install these unsupported Beta updates?",
			"Beta program updates available",
			MB_YESNO|MB_DEFBUTTON2) == IDNO
			) {
			BetaUpdate = false ;
		}
	}
	// Schedule downloads and processing for all selected types
	std::list<AutoUpdateSource>::iterator itUpdates = updateList.begin();;
	while (itUpdates != updateList.end()) {
		AutoUpdateSource source = *itUpdates++ ;
		if	(	(source.getType() == AU_TYPE_DLL && !DllUpdate)
			||	(source.getType() == AU_TYPE_BETADLL && !BetaUpdate)
			){
			continue ;
		}
		dlAgent.scheduleDownload(source.getSource(), m_DecalDir + "\\" + source.getDestination() + ".tmp");
	}

	// Download them to the local cache
	if (!dlAgent.runDownloads()) {
		// Something went horribly wrong
		return false;
	}

	// Delete the destinations and move the cached objects to their new homes
	itUpdates = updateList.begin() ;
	while (itUpdates != updateList.end()) {
		AutoUpdateSource aUSource = *itUpdates++;
		if	(	(aUSource.getType() == AU_TYPE_DLL && !DllUpdate)
			||	(aUSource.getType() == AU_TYPE_BETADLL && !BetaUpdate)
			){
			continue ;
		}

		std::string newFile = (m_DecalDir + "\\" + aUSource.getDestination());
		std::string tmpFile = (m_DecalDir + "\\" + aUSource.getDestination() + ".tmp");		

		BOOL bResult = ::DeleteFile (newFile.c_str ());
		DWORD dwLastError = 0;

		if (!bResult)
		{
			dwLastError = ::GetLastError ();
		}

		if (bResult || dwLastError == ERROR_FILE_NOT_FOUND)
		{
			MoveFile(tmpFile.c_str(), newFile.c_str());

			// We may have to register DLLs

			if (aUSource.getType() == AU_TYPE_DLL || aUSource.getType() == AU_TYPE_BETADLL )
			{
				HINSTANCE hDLL = LoadLibrary(newFile.c_str());

				if (hDLL) {
					typedef CRuntimeClass * (*DLLREG)();

					DLLREG DllReg = (DLLREG)GetProcAddress(hDLL, "DllRegisterServer");
				
					if (DllReg != NULL)
						DllReg();

					FreeLibrary(hDLL);
				}
			}
		}

		// Delete failed - file is in use
		else
		{
			MessageBox(NULL, "A file being updated is in use - close programs and try again.", "Decal", MB_OK);
		}
	}

	if (updateList.size() > 0) {
		MessageBox(NULL, "Your Decal components have been updated", "Decal", MB_OK);
	} else {
		MessageBox(NULL, "Your Decal components are already up to date", "Decal", MB_OK);
	}

	return true;
}