/******************************************************************************
 *    scrversion.cpp
 *
 *    This file is part of Public Scripts
 *    Copyright (C) 2005-2009 Tom N Harris <telliamed@whoopdedo.org>
 *
 *    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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 *
 *****************************************************************************/
#include "scrversion.h"
#include "ScriptModule.h"

#include <malloc.h>

#include <windows.h>
#include <winver.h>
#include <lg/objstd.h>
#include <lg/interface.h>

#define _SHOCKINTERFACES 1
#include <lg/types.h>
#include <lg/defs.h>
#include <lg/scrmsgs.h>
#include <lg/scrservices.h>
#include <lg/objects.h>
#include <lg/properties.h>
#include <lg/links.h>

#include "ScriptLib.h"

#include <new>
#include <cstring>
#include <cstdlib>
#include <cctype>


static char* GetCfgFromFile(const char* pszName, const char* pszFile);
static char* GetScriptPaths(void);
static char* FindScriptModule(const char* pszName, const char* pszPaths);
static bool CheckFileVersion(const char* pszFile, DWORD dwVersHigh, DWORD dwVersLow);
static void DoSuccess(int iObjId);
static void DoFailure(int iObjId);
static char* GetObjectParamsCompatible(int iObjId);
static void RelayCompatible(const char* pszMsg, int iObjId);
static int ShowBookCompatible(int iObjId, unsigned long ulTime);


const sScrClassDesc cScriptModule::sm_ScriptsArray[] = {
	{ "version", "VersionCheck", "CustomScript", cScr_VersionCheck::MakeVersionCheck },
};
const unsigned int cScriptModule::sm_ScriptsArraySize = 1;


IScript* cScr_VersionCheck::MakeVersionCheck(const char* pszName, int iHostObjId)
{
	cScr_VersionCheck* pscrRet = new(std::nothrow) cScr_VersionCheck(pszName, iHostObjId);
	if (pscrRet)
	{
		pscrRet->AddRef();
	}
	return static_cast<IScript*>(pscrRet);
}

long __stdcall cScr_VersionCheck::ReceiveMessage(sScrMsg* pMsg, sMultiParm*, eScrTraceAction)
{
	if (!::stricmp(pMsg->message, "Sim"))
	{
		if (!static_cast<sSimMsg*>(pMsg)->fStarting)
		{
			// not sure if this is really necessary, but just to be safe...
			if (m_iTextType == 2)
			{
				// TDP has, rather annoyingly, all the Shock interfaces.
				// So we use a member variable. It's non-persistent though.
				// Let's just pretend the user isn't so demented as to
				// save the game in this state.
				SService<IShockGameSrv> pSGS(g_pScriptManager);
				pSGS->OverlayChange(41,0);
			}
			else if (m_iTextType == 1)
			{
				SService<IDarkUISrv> pUI(g_pScriptManager);
				pUI->TextMessage("", 0, 1);
			}
			return 0;
		}

		char* pszScriptPaths = GetScriptPaths();
		char* pszParams = GetObjectParamsCompatible(ObjId());
		char* pszScript;
		char* pszToken = pszParams;
		for (pszScript = strsep(&pszToken, ";"); pszScript; pszScript = strsep(&pszToken, ";"))
		{
			if (!*pszScript)
				continue;
			DWORD scrVersHigh = 0, scrVersLow = 0;
			char* pszVers = ::strchr(pszScript, '=');
			if (pszVers)
			{
				char* pt = pszVers + 1;
				scrVersHigh = ::strtoul(pt, &pt, 10) << 16;
				if (pt && *pt == '.')
				{
					scrVersHigh |= ::strtoul(pt+1, &pt, 10);
					if (pt && *pt == '.')
					{
						scrVersLow = ::strtoul(pt+1, &pt, 10) << 16;
						if (pt && *pt == '.')
							scrVersLow |= ::strtoul(pt+1, &pt, 10);
					}
				}
				*pszVers = '\0';
			}
			char* pszScriptFile = FindScriptModule(pszScript, pszScriptPaths);
			if (!pszScriptFile)
			{
				DoFailure(ObjId());
				delete[] pszParams;
				return 0;
			}
			if (scrVersHigh == 0 && scrVersLow == 0)
			{
				delete[] pszScriptFile;
				continue;
			}
			if (!CheckFileVersion(pszScriptFile, scrVersHigh, scrVersLow))
			{
				DoFailure(ObjId());
				delete[] pszScriptFile;
				delete[] pszParams;
				if (pszScriptPaths)
					delete[] pszScriptPaths;
				return 0;
			}
			delete[] pszScriptFile;
		}
		DoSuccess(ObjId());
		delete[] pszParams;
		if (pszScriptPaths)
			delete[] pszScriptPaths;
	} // "Sim"
	else if (!::stricmp(pMsg->message, "Timer"))
	{
		if (!::stricmp(static_cast<sScrTimerMsg*>(pMsg)->name, "ErrorText"))
		{
			m_iTextType = ShowBookCompatible(ObjId(), 0x7FFFFFFFUL);
			return 0;
		}
	}
	return 0;
}


char* GetCfgFromFile(const char* pszName, const char* pszFile)
{
	char buffer[140];
	char* value = NULL;
	HANDLE hCfgFile = ::CreateFileA(pszFile, GENERIC_READ, FILE_SHARE_READ, NULL, 
					OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
	if (hCfgFile != INVALID_HANDLE_VALUE)
	{
		DWORD dwCfgLen = ::strlen(pszName);
		DWORD dwBytes = 0;
		DWORD dwAvail = sizeof(buffer);
		char* startp = buffer;
		while (::ReadFile(hCfgFile, startp, dwAvail-1, &dwBytes, NULL))
		{
			char* line = buffer;
			char* endp = startp + dwBytes;
			*endp = '\0';
			while (line < endp)
			{
				char* endl = ::strchr(line, '\n');
				if (!endl)
				{
					if (dwBytes != 0)
					{
						dwBytes = endp - line;
						::memmove(buffer, line, dwBytes);
						startp = buffer + dwBytes;
						dwAvail = sizeof(buffer) - dwBytes;
						goto parseCfgContinue;
					}
				}
				else
					*endl++ = '\0';

				if (!::strnicmp(line,pszName,dwCfgLen) && ::isspace(line[dwCfgLen]))
				{
					char* l = &line[dwCfgLen+1];
					while (::isspace(*l)) ++l;
					char* k = l + strlen(l) - 1;
					while (::isspace(*k)) --k;
					++k;
					value = new(std::nothrow) char[k - l + 1];
					if (value)
					{
						::strncpy(value,l,k - l);
						value[k-l] = '\0';
					}
					goto parseCfgEnd;
				}

				if ((line = endl) == NULL)
					break;
			}

			if (dwBytes == 0)
				break;
			dwAvail = sizeof(buffer);
			startp = buffer;
	parseCfgContinue:
			continue;
		}
	parseCfgEnd:
		::CloseHandle(hCfgFile);
	}
	return value;
}

// script_module_path
char* GetScriptPaths(void)
{
	char* game = GetCfgFromFile("game", "cam.cfg");
	if (!game)
		return NULL;
	char* cfg = reinterpret_cast<char*>(::alloca(24 + ::strlen(game)));
	::strcpy(cfg,game);
	::strcat(cfg,"_include_install_cfg");
	delete[] game;
	char* cfgfile = GetCfgFromFile(cfg, "cam.cfg");
	if (!cfgfile)
		return NULL;

	char* paths = GetCfgFromFile("script_module_path", cfgfile);
	delete[] cfgfile;

	return paths;
}

char* FindScriptModule(const char* pszName, const char* pszPaths)
{
	char filepath[160];
	if (!pszPaths || !*pszPaths)
	{
		::strcpy(filepath, pszName);
		if (!::strchr(filepath, '.'))
		{
			::strcat(filepath, ".osm");
		}
		if (::GetFileAttributesA(filepath) != 0xFFFFFFFFUL)
		{
			char* ret = new(std::nothrow) char[::strlen(filepath)+1];
			if (ret)
				::strcpy(ret,filepath);
			return ret;
		}
		return NULL;
	}
	char* scrpaths = reinterpret_cast<char*>(::alloca(::strlen(pszPaths) + 1));
	::strcpy(scrpaths,pszPaths);
	char* tok = scrpaths;
	for (char* path = strsep(&tok, "+"); path; path = strsep(&tok, "+"))
	{
		while (::isspace(*path)) ++path;
		if (!*path)
			continue;
		::strcpy(filepath, path);
		::strcat(filepath, "\\");
		::strcat(filepath, pszName);
		if (!::strchr(pszName, '.'))
			::strcat(filepath, ".osm");
		if (::GetFileAttributesA(filepath) != 0xFFFFFFFFUL)
		{
			char* ret = new(std::nothrow) char[::strlen(filepath)+1];
			if (ret)
				::strcpy(ret,filepath);
			return ret;
		}
	}
	return NULL;
}

bool CheckFileVersion(const char* pszFile, DWORD dwVersHigh, DWORD dwVersLow)
{
	DWORD z;
	unsigned int len = ::GetFileVersionInfoSizeA(const_cast<LPSTR>(pszFile), &z);
	if (len)
	{
		char* buffer = reinterpret_cast<char*>(::alloca(len));
		VS_FIXEDFILEINFO* pFileVers;
		::GetFileVersionInfoA(const_cast<LPSTR>(pszFile), z, len, reinterpret_cast<void*>(buffer));
		len = 0;
		::VerQueryValueA(reinterpret_cast<void*>(buffer), "\\", reinterpret_cast<void**>(&pFileVers), &len);
		if (len > 0)
		{
			if ( (pFileVers->dwFileVersionMS > dwVersHigh)
			  || (pFileVers->dwFileVersionMS == dwVersHigh 
			   && pFileVers->dwFileVersionLS >= dwVersLow)
			)
				return true;
		}
	}
	return false;
}

void DoSuccess(int iObjId)
{
	RelayCompatible("TurnOn", iObjId);
	SInterface<IObjectSystem> pOS(g_pScriptManager);
	pOS->Destroy(iObjId);
}

void DoFailure(int iObjId)
{
	RelayCompatible("TurnOff", iObjId);
	//m_iTextType = ShowBookCompatible(iObjId, 0x7FFFFFFFUL);
	g_pScriptManager->SetTimedMessage2(iObjId, "ErrorText", 288, kSTM_OneShot, 0);
	//SInterface<IObjectSystem> pOS(g_pScriptManager);
	//pOS->Destroy(iObjId);
}

/* IPropertySrv isn't the same in all game versions,
 * nor is the property name always the same.
 * So let's try to even out those differences, even 
 * if it is more awkward.
 */
char* GetObjectParamsCompatible(int iObjId)
{
	SInterface<IPropertyManager> pPM(g_pScriptManager);
	SInterface<IStringProperty> pProp;

	pProp = static_cast<IStringProperty*>(pPM->GetPropertyNamed("DesignNote"));
	if (-1 == pProp->GetID())
	{
		pProp = static_cast<IStringProperty*>(pPM->GetPropertyNamed("ObjList"));
		if (-1 == pProp->GetID())
			return NULL;
	}
	
	if (!pProp->IsRelevant(iObjId))
	{
		return NULL;
	}

	char* pRet = NULL;
	const char* pszValue;
	pProp->Get(iObjId, &pszValue);
	if (pszValue)
	{
		pRet = new(std::nothrow) char[::strlen(pszValue)+1];
		if (pRet)
			::strcpy(pRet, pszValue);
	}
	return pRet;
}

void RelayCompatible(const char* pszMsg, int iObjId)
{
	SInterface<ILinkManager> pLM(g_pScriptManager);
	SInterface<IRelation> pRel;

	pRel = pLM->GetRelationNamed("ControlDevice");
	if (! pRel->GetID())
	{
		pRel = pLM->GetRelationNamed("SwitchLink");
		if (! pRel->GetID())
			return;
	}

	SInterface<ILinkQuery> pLQ;
	pLQ = pRel->Query(iObjId, 0);
	if (!pLQ)
		return;

	for (; ! pLQ->Done(); pLQ->Next())
	{
		sLink sl;
		pLQ->Link(&sl);
		g_pScriptManager->PostMessage2(iObjId, sl.dest, pszMsg, 0, 0, 0);
	}
}

int ShowBookCompatible(int iObjId, unsigned long ulTime)
{
	SInterface<IPropertyManager> pPM(g_pScriptManager);
	SInterface<IStringProperty> pBookProp;
	SInterface<IStringProperty> pArtProp;

	pBookProp = static_cast<IStringProperty*>(pPM->GetPropertyNamed("Book"));
	if (-1 == pBookProp->GetID())
	{
		// Must be SShock2
		pBookProp = static_cast<IStringProperty*>(pPM->GetPropertyNamed("UseMsg"));
		if (-1 != pBookProp->GetID()
		 && pBookProp->IsRelevant(iObjId))
		{
			const char* pszBook;
			pBookProp->Get(iObjId, &pszBook);
			SService<IShockGameSrv> pSGS(g_pScriptManager);
			pSGS->TlucTextAdd(pszBook, "error", -1);
			//pSGS->AddTranslatableText(pszBook, "error", 0, ulTime);
			return 2;
		}
		return 0;
	}

	if (pBookProp->IsRelevant(iObjId))
	{
		const char* pszBook;
		pBookProp->Get(iObjId, &pszBook);

		pArtProp = static_cast<IStringProperty*>(pPM->GetPropertyNamed("BookArt"));
		if (-1 != pArtProp->GetID() 
		 && pArtProp->IsRelevant(iObjId))
		{
			const char* pszBookArt;
			pArtProp->Get(iObjId, &pszBookArt);
			SService<IDarkUISrv> pUI(g_pScriptManager);
			pUI->ReadBook(pszBook, pszBookArt);
			return 0;
		}

		SService<IDataSrv> pDS(g_pScriptManager);
		char* szBookFile = reinterpret_cast<char*>(::alloca(10 + ::strlen(pszBook)));
		::strcpy(szBookFile, "..\\books\\");
		::strcat(szBookFile, pszBook);
		cScrStr strText;
		pDS->GetString(strText, szBookFile, "page_0", "", "strings");
		if (!strText.IsEmpty())
		{
			SService<IDarkUISrv> pUI(g_pScriptManager);
			pUI->TextMessage(strText, 0, ulTime);
		}
		strText.Free();
		return 1;
	}
	return 0;
}

