#include <Windows.h>
//#include <ShellAPI.h>
#pragma warning (push)
#pragma warning (disable: 4091)
	#include <Shlobj.h>
#pragma warning(pop)
#include <time.h>
#include <iostream>
#include <string>
#include <sstream>
#include <algorithm>
#include <vector>
#include "../sqlite/sqlite3.h"


#pragma comment(lib, "User32.lib") //Shell32.lib

// MakePtr is a macro that allows you to easily add two values (including
// pointers) together without dealing with C's pointer arithmetic.  It
// essentially treats the last two parameters as DWORDs.  The first
// parameter is used to typecast the result to the appropriate pointer type.
#define MakePtr( cast, ptr, addValue ) (cast)( (DWORD)(ptr) + (DWORD)(addValue))

// time2struct is a macro that allows to set one of the time conversion
// functions to be used. It seems that 'localtime_s' is used inside Orbiters
// 'oapiGetOrbiterVersion' & 'oapiGetModuleVersion' functions, so 'localtime_s'
// seems to be correct here. Allowed values however are 'gmtime_s' or 'localtime_s'
#define time2struct localtime_s

// Orbiter SVN repository base-URL to retrieve version information from
#define REPO_BASE_URL "svn://svn.orbiter-forum.com/orbiter"

// Type definitions
typedef enum { Name, Full, Long, Verbose } eFormat;

// Declare functions.
std::string LookupName (const std::string &version, const std::string &sub, eFormat format);
BOOL ProcessExeFile(LPVOID pHeader, struct tm *pTimeStruct);
BOOL ProcessObjFile(LPVOID pHeader, struct tm *pTimeStruct);
BOOL ProcessLibFile(LPVOID pHeader, struct tm *pTimeStruct);
VOID PrintUsage();

void init_data();
int extractFileInfo (std::string &filename, char *version, char *sub);

/*
-------------------------------------------------------------------------------
	DB OPEN/CLOSE
-------------------------------------------------------------------------------
*/

static sqlite3 *db = 0;
static char *filename = 0; // "%localappdata%\\over\\data.db3" set @ migrate_db()

void open_db ()
{
	if (db == 0)
	{
		sqlite3_initialize();
		sqlite3_open_v2(filename, &db, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, NULL);
		if (db == 0 || SQLITE_OK != sqlite3_errcode(db))
		{
			std::cerr << "Error: unable to open database "
			          << "\"" << filename << "\""
			          << ": " << sqlite3_errmsg(db) << std::endl;
			exit(1);
		}
	}
}

void close_db () {
	sqlite3_close(db);
}

/*
-------------------------------------------------------------------------------
	MIGRATION
-------------------------------------------------------------------------------
*/
sqlite_int64 get_max_rev (sqlite3 *db)
{
	sqlite_int64 ret = -1;
	int rc;
	sqlite3_stmt *pStmt;

	sqlite3_prepare_v2(db, "SELECT max(revision) FROM file", -1, &pStmt, NULL);
	rc = sqlite3_step(pStmt);
	if (rc == SQLITE_ROW) {
		ret = sqlite3_column_int64(pStmt, 0);
	}
	sqlite3_finalize(pStmt);
	return ret;
}

void migrate_db ()
{
	bool oldExists(false);
	bool newExists(false);
	sqlite_int64 oldMaxRevision(-1);
	sqlite_int64 newMaxRevision(-1);

	const  TCHAR oldPath[] = "data.db3";
	static TCHAR newPath[MAX_PATH];
	TCHAR localAppdata[MAX_PATH];
	if (S_OK == SHGetFolderPath(NULL, CSIDL_LOCAL_APPDATA, NULL, 0, localAppdata))
	{
		sprintf_s(newPath, MAX_PATH, "%s\\over", localAppdata);
		CreateDirectory(newPath, NULL);
		sprintf_s(newPath, MAX_PATH, "%s\\over\\data.db3", localAppdata);
	}

	sqlite3_initialize();

	// old one exists?
	sqlite3_open_v2(oldPath, &db, SQLITE_OPEN_READONLY, NULL);
	if (db != 0 && SQLITE_OK == sqlite3_errcode(db)) {
		oldExists = true;
		oldMaxRevision = get_max_rev(db);
	}
	if (db != 0)
	{
		sqlite3_close(db);
		db = 0;
	}
	// new one exists?
	sqlite3_open_v2(newPath, &db, SQLITE_OPEN_READONLY, NULL);
	if (db != 0 && SQLITE_OK == sqlite3_errcode(db)) {
		newExists = true;
		newMaxRevision = get_max_rev(db);
	}
	if (db != 0)
	{
		sqlite3_close(db);
		db = 0;
	}

	if (oldExists && !newExists) {
		CopyFile(oldPath, newPath, FALSE);
	}
	else if (oldExists && newExists) {
		// check wich one's newer / better
		if (oldMaxRevision > newMaxRevision) {
			CopyFile(oldPath, newPath, FALSE);
		}
	}

	if (oldExists) {
		// remove old one
		DeleteFile(oldPath);
	}
	filename = newPath;
}

/*
-------------------------------------------------------------------------------
	DB-SCHEMA
-------------------------------------------------------------------------------
*/
void init_db ()
{
	int rc;
	sqlite3_stmt *pStmt;

	sqlite3_prepare_v2(db, "SELECT count(*) FROM sqlite_master WHERE tbl_name = 'files';", -1, &pStmt, NULL);
	rc = sqlite3_step(pStmt);
	if (rc == SQLITE_ROW) {
		int num = sqlite3_column_int(pStmt, 0);
		if (num == 0) {
			sqlite3_exec(db,
				"CREATE TABLE file("
					"id "       "INTEGER PRIMARY KEY" ","
					"path "     "TEXT"                ","
					"revision " "INTEGER"             ","
					"name "     "TEXT"                ","
					"version "  "TEXT"                ","
					"sub "      "TEXT"
				");",0,0,0);
			init_data();
		} else {
			;//std::cout << "OK" << std::endl;
		}
		sqlite3_finalize(pStmt);
	}
}


// Constants
typedef struct {
	int         revision;
	const char* name;
	const char* version;
	const char* sub;
} t_row;
const t_row data[] = { // version => name lookup table
	{  0, "2006"     , "060929", "1530" },
	{  0, "2010"     , "100606", "2136" },
	{  0, "2010-P1"  , "100830", "0344" }, //
	{  0, "2010-P1"  , "100830", "0355" }, // <= is this true? or is EXE different from LIB?
	{  0, "2010-P2"  , "110822", "0155" },
	{  0, "2010-P2.1", "110824", "0313" },
	{  0, "BETA 1"   , "111105", "0310" },
	{  0, "BETA 2"   , "121103", "0517" },
	{  1, "BETA 3"   , "130101", "1604" }, // same
//	{  1, "BETA r1"  , "130101", "1604" }, //  "
//	{  2, "BETA r2"  , "140413", "0123" },
//	{  7, "BETA r7"  , "140503", "0558" },
//	{  8, "BETA r8"  , "140602", "0212" },
//	{  9, "BETA r9"  , "141220", "2238" },
//	{ 11, "BETA r11" , "150214", "0412" },
//	{ 12, "BETA r12" , "150228", "1718" },
//	{ 13, "BETA r13" , "150307", "0203" },
//	{ 14, "BETA r14" , "150907", "0036" },
//	{ 15, "BETA r15" , "150907", "2313" },
//	{ 16, "BETA r16" , "150908", "0025" },
//	{ 17, "BETA r17" , "150908", "0112" },
//	{ 18, "BETA r18" , "150908", "2204" },
//	{ 19, "BETA r19" , "150908", "2326" },
//	{ 20, "BETA r20" , "150909", "0133" },
//	{ 21, "BETA r21" , "150910", "0525" },
//	{ 22, "BETA r22" , "150911", "0505" },
//	{ 23, "BETA r23" , "150912", "0031" },
//	{ 24, "BETA r24" , "150915", "0252" },
//	{ 25, "BETA r25" , "150916", "0207" },
//	{ 26, "BETA r26" , "150922", "0526" },
//	{ 27, "BETA r27" , "150925", "0602" },
//	{ 28, "BETA r28" , "150929", "0256" },
//	{ 29, "BETA r29" , "150930", "0433" },
//	{ 30, "BETA r30" , "151002", "0359" },
//	{ 31, "BETA r31" , "151005", "2257" },
//	{ 32, "BETA r32" , "151007", "2350" },
//	{ 33, "BETA r33" , "151008", "2345" },
//	{ 34, "BETA r34" , "151011", "0045" },
//	{ 35, "BETA r35" , "151021", "2105" },
//	{ 36, "BETA r36" , "151022", "2047" },
//	{ 37, "BETA r37" , "151025", "1907" },
//	{ 38, "BETA r38" , "151028", "0332" },
//	{ 39, "BETA r39" , "151031", "0327" },
//	{ 40, "BETA r40" , "151101", "2302" },
//	{ 41, "BETA r41" , "151104", "0437" },
//	{ 42, "BETA r42" , "151110", "0328" },
//	{ 43, "BETA r43" , "151119", "0342" },
//	{ 44, "BETA r44" , "151205", "0538" },
//	{ 45, "BETA r45" , "160120", "2153" },
//	{ 46, "BETA r46" , "160127", "0341" },
//	{ 47, "BETA r47" , "160129", "0615" },
//	{ 48, "BETA r48" , "160213", "0737" },
//	{ 49, "BETA r49" , "160214", "0124" },
//	{ 50, "BETA r50" , "160220", "0320" },
//	{ 51, "BETA r51" , "160304", "0358" },
//	{ 52, "BETA r52" , "160310", "0007" },
//	{ 53, "BETA r53" , "160323", "0357" },
//	{ 54, "BETA r54" , "160403", "0020" },
//	{ 55, "BETA r55" , "160412", "0246" },
//	{ 56, "BETA r56" , "160701", "0040" },
//	{ 57, "BETA r57" , "160703", "1530" },
	{  0, "2016-RC.1", "160712", "0451" }, // r58
	{  0, "2016-RC.2", "160728", "2213" }, // r59
	{  0, "2016-RC.3", "160806", "0419" }, // r60
	{  0, "2016-RC.4", "160815", "2328" }, // r61
	{  0, "2016"     , "160828", "0245" }, // r62
//	{ 63, "BETA r63" , "160903", "1416" },
//	{ 64, "BETA r64" , "160910", "0510" },
//	{ 65, "BETA r65" , "161125", "0011" },
//	{ 66, "BETA r66" , "170827", "2030" },
//	{ 67, "BETA r67" , "170830", "0313" },
//	{ 68, "BETA r68" , "170831", "0256" },
//	{ 69, "BETA r69" , "170901", "0228" },
//	{ 70, "BETA r70" , "171010", "0120" },
//	{ 71, "BETA r71" , "171014", "0026" },
//	{ 72, "BETA r72" , "171203", "2357" },
//	{ 74, "BETA r74" , "180807", "0304" },
//	{ 75, "BETA r75" , "180809", "0321" },
//	{ 76, "BETA r76" , "180812", "0048" },
//	{ 77, "BETA r77" , "180813", "0016" },
//	{ 78, "BETA r78" , "180818", "2354" },
//	{ 79, "BETA r79" , "180821", "0049" },
//	{ 80, "BETA r80" , "180821", "0404" },
//	{ 81, "BETA r81" , "180822", "0504" },
//	{ 82, "BETA r82" , "180823", "0111" },
//	{ 83, "BETA r83" , "180825", "0744" },
//	{ 84, "BETA r84" , "180905", "0253" },
//	{ 85, "BETA r85" , "190220", "1730" },
//	{ 86, "BETA r86" , "190228", "2241" },
//	{ 87, "BETA r87" , "190304", "2305" }
};


void init_data ()
{
	sqlite3_stmt *pStmt1;
	sqlite3_stmt *pStmt2;

	sqlite3_prepare_v2(db, "SELECT id FROM file WHERE path = ? AND revision = ? AND version = ? AND sub = ?",
	                   -1, &pStmt1, NULL);

	sqlite3_prepare_v2(db, "INSERT INTO file (path,revision,name,version,sub)"
	                       " VALUES (?,?,?,?,?);", -1, &pStmt2, NULL);
	// "UPSERT"
	for (int i=0; i< sizeof(data)/sizeof(t_row); ++i)
	{
		t_row *pRow = (t_row *)&data[i];

		sqlite3_reset(pStmt1);
		sqlite3_bind_text (pStmt1, 1, "orbiter.lib", -1, SQLITE_STATIC);
		sqlite3_bind_int  (pStmt1, 2, pRow->revision);
		sqlite3_bind_text (pStmt1, 3, pRow->version, -1, SQLITE_STATIC);
		sqlite3_bind_text (pStmt1, 4, pRow->sub, -1, SQLITE_STATIC);

		if (sqlite3_step(pStmt1) == SQLITE_ROW) {
			continue;
		}

		sqlite3_bind_text (pStmt2, 1, "orbiter.lib", -1, SQLITE_STATIC);
		sqlite3_bind_int  (pStmt2, 2, pRow->revision);
		sqlite3_bind_text (pStmt2, 3, pRow->name, -1, SQLITE_STATIC);
		sqlite3_bind_text (pStmt2, 4, pRow->version, -1, SQLITE_STATIC);
		sqlite3_bind_text (pStmt2, 5, pRow->sub, -1, SQLITE_STATIC);

		sqlite3_step(pStmt2);
		sqlite3_reset(pStmt2);
	}

	sqlite3_finalize(pStmt1);
	sqlite3_finalize(pStmt2);
}


/*
-------------------------------------------------------------------------------
	DB-SELECT
-------------------------------------------------------------------------------
*/
sqlite_int64 get_file_info (const char *version, const char *sub, char *name )
{
	static sqlite3_stmt *pStmt = NULL;

	if (pStmt == NULL) {
		sqlite3_prepare_v2(db, "SELECT id, name FROM file "
		                       "WHERE version = ? AND sub = ? "
		                       "ORDER BY revision " // ASC
		                       "LIMIT 1", -1, &pStmt, NULL);
	} else {
		sqlite3_reset(pStmt);
	}

	sqlite3_bind_text (pStmt, 1, version, -1, SQLITE_STATIC);
	sqlite3_bind_text (pStmt, 2, sub, -1, SQLITE_STATIC);

	sqlite_int64 id = -1;
	int rc = sqlite3_step(pStmt);
	if (rc == SQLITE_ROW)
	{
		id = sqlite3_column_int64(pStmt, 0);
		if (name) {
			strcpy_s(name, 64, (const char *)sqlite3_column_text(pStmt, 1));
		}
	}
	return id;
}

bool g_found = false;

std::string LookupName (const std::string &version, const std::string &sub, eFormat format)
{
	TCHAR _name[64] = "Unknown";
	g_found = get_file_info(version.c_str(), sub.c_str(), _name) >= 0;

	std::string name(_name);
	if (format == ::Full) {
		name += "[" + version + "]";
	}

	return name;
}

/*
-------------------------------------------------------------------------------
	DB-INSERT
-------------------------------------------------------------------------------
*/
sqlite_int64 insert_file_info (const char *path, int revision, const char *name, const char *version, const char *sub = NULL)
{
	static sqlite3_stmt *pStmt1 = NULL;
	static sqlite3_stmt *pStmt2 = NULL;
	static sqlite3_stmt *pStmt3 = NULL;

	if (pStmt1 == NULL) {
		sqlite3_prepare_v2(db, "SELECT id FROM file WHERE path = ? AND revision = ? AND name = ? AND version = ? AND sub = ?", -1, &pStmt1, NULL);
	}
	if (pStmt2 == NULL) {
		sqlite3_prepare_v2(db, "SELECT id FROM file WHERE path = ? AND revision = ? AND name = ? AND version = ? AND sub IS NULL", -1, &pStmt2, NULL);
	}
	if (pStmt3 == NULL) {
		sqlite3_prepare_v2(db, "INSERT INTO file (path, revision, name, version, sub) VALUES (?,?,?,?,?)", -1, &pStmt3, NULL);
	}

	// Select either prepared statement 1 or 2
	sqlite3_stmt *pStmt = (sub) ? pStmt1 : pStmt2;

	sqlite3_bind_text (pStmt, 1, path, -1, SQLITE_STATIC);
	sqlite3_bind_int  (pStmt, 2, revision); // sqlite3_bind_int64(pStmt, 4, revision);
	sqlite3_bind_text (pStmt, 3, name, -1, SQLITE_STATIC);
	sqlite3_bind_text (pStmt, 4, version, -1, SQLITE_STATIC);
	if (sub) {
		sqlite3_bind_text (pStmt, 5, sub, -1, SQLITE_STATIC);
	}

	sqlite_int64 id = 0;
	int rc = sqlite3_step(pStmt);
	if (rc == SQLITE_ROW) {
		id = sqlite3_column_int64(pStmt, 0);
	}

	sqlite3_reset(pStmt);

	if (rc != SQLITE_ROW)
	{
		sqlite3_bind_text (pStmt3, 1, path, -1, SQLITE_STATIC);
		sqlite3_bind_int  (pStmt3, 2, revision); // sqlite3_bind_int64(pStmt3, 4, revision);
		sqlite3_bind_text (pStmt3, 3, name, -1, SQLITE_STATIC);
		sqlite3_bind_text (pStmt3, 4, version, -1, SQLITE_STATIC);
		if (sub) sqlite3_bind_text (pStmt3, 5, sub, -1, SQLITE_STATIC);
		else     sqlite3_bind_null (pStmt3, 5);

		sqlite3_step(pStmt3);
		sqlite3_reset(pStmt3);
		return sqlite3_last_insert_rowid(db);
	}

	return id;
}

// Availabe
std::vector<std::string> g_revisions;

void remove_known_revs ()
{
	static sqlite3_stmt *pStmt = NULL;
	if (pStmt == NULL) {
		sqlite3_prepare_v2(db, "SELECT DISTINCT revision FROM file", -1, &pStmt, NULL);
	}

	while (sqlite3_step(pStmt) == SQLITE_ROW)
	{
		std::string rev( (const char *)sqlite3_column_text(pStmt, 0) );
		g_revisions.erase(std::remove(g_revisions.begin(), g_revisions.end(), rev), g_revisions.end());
		//vec.erase(std::remove(vec.begin(), vec.end(), 8), vec.end());
		//g_revisions.erase();
	}
	sqlite3_finalize(pStmt);
}





// ----------------------------------------------------------------------------
// ----------------------------------------------------------------------------
const size_t BUFSIZE = 4*1024;
HANDLE g_hChildStd_IN = NULL;
HANDLE g_hChildStd_OUT_Wr = NULL;
HANDLE g_hChildStd_OUT_Rd = NULL;

void CreateChildProcess ()
{
	TCHAR szCmdline[]=TEXT("cmd.exe /C svn log " REPO_BASE_URL "/Orbitersdk/lib/orbiter.lib -q -r HEAD:1 | find \"|\"");
	PROCESS_INFORMATION piProcInfo = {0};
	STARTUPINFO siStartInfo = {0};

	// Set up members of the PROCESS_INFORMATION structure
	//ZeroMemory( &piProcInfo, sizeof(PROCESS_INFORMATION) );

	// Set up members of the STARTUPINFO structure.
	// This structure specifies the STDIN and STDOUT handles for redirection
	// ZeroMemory( &siStartInfo, sizeof(STARTUPINFO) );
	siStartInfo.cb = sizeof(STARTUPINFO);
	siStartInfo.hStdError = g_hChildStd_OUT_Wr;
	siStartInfo.hStdOutput = g_hChildStd_OUT_Wr;
	siStartInfo.hStdInput = g_hChildStd_IN;
	siStartInfo.dwFlags |= STARTF_USESTDHANDLES;

	// Create the child process
	BOOL bSuccess = CreateProcess(
		NULL,          // application name
		szCmdline,     // command line
		NULL,          // process security attributes
		NULL,          // primary thread security attributes
		TRUE,          // handles are inherited
		0,             // creation flags
		NULL,          // use parent's environment
		NULL,          // use parent's current directory
		&siStartInfo,  // STARTUPINFO pointer
		&piProcInfo    // receives PROCESS_INFORMATION
	);
	// If an error occured, exit the application.
	if ( ! bSuccess ) {
		ExitProcess(3);
	}

	// Close handles to the child process and its primary thread.
	// Some applications might keep these handles to monitor the status
	// of the child process, for example
	CloseHandle(piProcInfo.hProcess);
	CloseHandle(piProcInfo.hThread);

	CloseHandle(g_hChildStd_IN);
	CloseHandle(g_hChildStd_OUT_Wr);
	//CloseHandle(g_hChildStd_OUT_Rd);
}


void ReadFromPipe ()
{
	DWORD dwRead;
	CHAR chBuf[BUFSIZE];
	BOOL bSuccess = FALSE;
	std::vector<std::string> revisions; // std::vector<int> revisions;
	int state = -1;     // -2: wait for eol; -1: wait for 'r'; 0...n revision decimals
	CHAR rev[5] = "\0"; // space for revs up to "9999"
	CHAR c;             // working char
	DWORD i;

	for (;;)
	{
		bSuccess = ReadFile( g_hChildStd_OUT_Rd, chBuf, BUFSIZE, &dwRead, NULL);
		// Check for eof.
		if (bSuccess && dwRead == 0) { break; }
		if( !bSuccess ) { break; }

		for (i = 0, c = chBuf[i]; i < dwRead; c = chBuf[++i])
		{
			if (state == -1 && c == 'r') {
				state = 0;
			}
			else if ( state >= 0)
			{
				if (c != ' ' ) {
					rev[state] = c;
					++state;
				} else {
					rev[state] = '\0';
					revisions.push_back(rev); // revisions.push_back(atoi(rev));
					state = -2;
				}
			}
			if (state == -2 && c == '\r') {
				state = -1;
			}
		}
	}
	g_revisions = revisions;
	return;
}

VOID GetOrbiterLibs ()
{
	SECURITY_ATTRIBUTES saAttr;

	// Set the bInheritHandle flag so pipe handles are inherited
	saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
	saAttr.bInheritHandle = TRUE;
	saAttr.lpSecurityDescriptor = NULL;

	// Create a pipe for the child process's STDOUT
	if ( ! CreatePipe(&g_hChildStd_OUT_Rd, &g_hChildStd_OUT_Wr, &saAttr, 0) ) {
		ExitProcess(1);//ErrorExit(TEXT("StdoutRd CreatePipe"));
	}

	// Ensure the read handle to the pipe for STDOUT is not inherited
	if ( ! SetHandleInformation(g_hChildStd_OUT_Rd, HANDLE_FLAG_INHERIT, 0) ) {
		ExitProcess(2);//ErrorExit(TEXT("Stdout SetHandleInformation"));
	}

	//
	g_hChildStd_IN = GetStdHandle(STD_INPUT_HANDLE);

	// Create the child process
	CreateChildProcess();

	// Read from pipe that is the standard output for child process
	ReadFromPipe();

	// Remove 'known' revisions
	remove_known_revs();

	// SVN get and store ...
	TCHAR tempPath[MAX_PATH+1] = "";
	GetTempPath(MAX_PATH, tempPath);

	std::string tempFile(tempPath);
	tempFile += "\\orbiter.lib";

	if (g_revisions.size()) {
		std::cerr << "Retrieving version information from Orbiter repository...\n";
	}
	for (auto it = g_revisions.begin(); it != g_revisions.end(); ++it)
	{
		std::string URI = REPO_BASE_URL "/Orbitersdk/lib/orbiter.lib";
		std::string params = "/C svn cat " + URI + "@" + *it + " > \"" + tempFile + "\"";

		SHELLEXECUTEINFO ShExecInfo = {0};
		ShExecInfo.cbSize = sizeof(SHELLEXECUTEINFO);
		ShExecInfo.fMask = SEE_MASK_NOCLOSEPROCESS;
		ShExecInfo.hwnd = NULL;
		ShExecInfo.lpVerb = "open";
		ShExecInfo.lpFile = "cmd.exe";
		ShExecInfo.lpParameters = params.c_str();
		ShExecInfo.lpDirectory = NULL;
		ShExecInfo.nShow = SW_HIDE;//SW_SHOW;
		ShExecInfo.hInstApp = NULL;
		ShellExecuteEx(&ShExecInfo);
		WaitForSingleObject(ShExecInfo.hProcess, INFINITE);

		CloseHandle(ShExecInfo.hProcess);

		/*@todo: "orbiter.lib" vs. "orbiter.exe"! */
		//std::string filename("_orbiter.lib");
		TCHAR version[7] = "";
		TCHAR sub[5] = "";

		extractFileInfo(tempFile, version, sub);

		std::string name( "BETA r" + *it );
		insert_file_info("orbiter.lib", atoi((*it).c_str()), name.c_str(), version, sub);
	}
	DeleteFile(tempFile.c_str());
}

// ----------------------------------------------------------------------------
// ----------------------------------------------------------------------------


// ----------------------
int extractFileInfo (std::string &filename, char *version, char *sub)
{
	if (!version || !sub) {
		return EXIT_FAILURE +1;
	}

	tm     timeStruct = {0};
	BOOL   ok = FALSE;
	HANDLE hFile;
	HANDLE hFileMapping;
	LPVOID pFileBase;
	PIMAGE_DOS_HEADER dosHeader;

	hFile = CreateFile(filename.c_str(), GENERIC_READ, FILE_SHARE_READ, NULL,
	                   OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);

	if ( hFile == INVALID_HANDLE_VALUE )
	{
		std::cerr << "Couldn't open file \"" << filename << "\"\n";
		return EXIT_FAILURE +2;
	}

	hFileMapping = CreateFileMapping(hFile, NULL, PAGE_READONLY, 0, 0, NULL);
	if ( hFileMapping == 0 )
	{
		CloseHandle(hFile);
		std::cerr << "Couldn't open file mapping for \"" << filename << "\"\n";
		return EXIT_FAILURE +3;
	}

	pFileBase = MapViewOfFile(hFileMapping, FILE_MAP_READ, 0, 0, 0);
	if ( pFileBase == 0 )
	{
		CloseHandle(hFileMapping);
		CloseHandle(hFile);
		std::cerr << "Couldn't map view of file \"" << filename << "\"\n";
		return EXIT_FAILURE +4;
	}

	dosHeader = (PIMAGE_DOS_HEADER)pFileBase;
	if ( dosHeader->e_magic == IMAGE_DOS_SIGNATURE ) {
		ok = ProcessExeFile(dosHeader, &timeStruct);
	}
	else if ( *(INT64*)pFileBase == *(INT64*)IMAGE_ARCHIVE_START) {
		ok = ProcessLibFile(pFileBase, &timeStruct);
	}
	else if ( (dosHeader->e_magic == 0x014C)  // Does it look like a i386
	       && (dosHeader->e_sp == 0) )        // COFF OBJ file???
	{
		// The two tests above aren't what they look like.  They're
		// really checking for IMAGE_FILE_HEADER.Machine == i386 (0x14C)
		// and IMAGE_FILE_HEADER.SizeOfOptionalHeader == 0;
		ok = ProcessObjFile(pFileBase, &timeStruct);
	}

	// Not needed anymore, close'em
	UnmapViewOfFile(pFileBase);
	CloseHandle(hFileMapping);
	CloseHandle(hFile);

	if (!ok) {
		return EXIT_FAILURE +5;
	}

	// version & sub
	strftime(version, 7, "%y%m%d", &timeStruct);
	strftime(sub, 5, "%H%M", &timeStruct);

	return EXIT_SUCCESS;
}



// Main
int main (int argc, char **argv)
{
	// Parameter (& defaults)
	std::string filename;
	eFormat		format(::Name);

	//
	// Parse arguments
	//
	for (int i = 1; i < argc; ++i)
	{
		std::string arg = argv[i];
		std::transform(arg.begin(), arg.end(), arg.begin(), ::toupper);

		if (arg == "/?") {
			PrintUsage();
			return EXIT_SUCCESS;
		}
		else if (arg == "/L" || arg == "/LONG") {
			format = ::Long; // long (date) format
		}
		else if (arg == "/N" || arg == "/NAME") {
			format = ::Name; // verbose name (LUT) format
		}
		else if (arg == "/F" || arg == "/FULLNAME") {
			format = ::Full; // verbose name (LUT) format
		}
		else if (arg == "/V" || arg == "/VERBOSE") {
			format = ::Verbose; // 'secret' verbose (full data) format
		}
		else { // treat any other as filename
			filename = argv[i]; // arg is UPPERCASE, so we take argv[i]
		}
	}

	// Check minimum
	if (!filename.length())
	{
		std::cerr << "No filename given\n";
		PrintUsage();
		return EXIT_FAILURE +1;
	}

	//
	// Migrate old DB location to new (if needed)
	//
	migrate_db();

	//
	// Now let's try to do the real job
	//
	open_db();
	init_db();

	TCHAR version[7] = "";
	TCHAR sub[7] = "";

	int result = extractFileInfo(filename, version, sub);

	if (result == EXIT_SUCCESS)
	{
		// Need lookup ?
		if (format == ::Name || format == ::Full || format == ::Verbose)
		{
			std::string name = LookupName(version, sub, format);

			if (!g_found)
			{
				// get svn...
				GetOrbiterLibs();
				// and try again
				name = LookupName(version, sub, format);
				// DEBUG
				if (!g_found) {
					std::cerr << version << ":" << sub << std::endl;
				}

			}

			if (format == ::Verbose) // 'secret' option
			{
				std::cout << "Name   : " << name    << std::endl
				          << "Version: " << version << std::endl
				          << "Sub    : " << sub     << std::endl;
			} else {
				std::cout << name;
			}
		}
		else if (format == ::Long) // like "2015-12-31"
		{
			std::cout << "20" << version[0] << version[1]
			          <<  "-" << version[2] << version[3]
			          <<  "-" << version[4] << version[5];
//		}
//		else if (format == ::Verbose) // 'secret' option
//		{
//			std::string name = LookupName(version, sub, format);
		} else {
			std::cout << version;
		}
	}

	close_db();

	return EXIT_SUCCESS;
}


VOID PrintUsage ()
{
	std::cout << "\n"
		"Orbiter VERsion extraction tool v3.4\t(c) 2015-2019 Peter Schneider\n"
		"\356       \356\356\356\n" // 0356 = 0xEE = 238 = 'overline'
		"OVER [/L][/N][/F] <filename> [/?]\n"
		"\n"
		"  <filename>\t(required) Name of the file to get information from.\n"
		"\t\tThis can be an EXE or a LIB file.\n"
		"  /L\t\tPrint the long (date) version info (e.g. '2010-08-30').\n"
		"  /N\t\tPrint the name (if known)          (e.g. '2010-P1').\n"
		"  /F\t\tPrint the full name (if known)     (e.g. '2010-P1[100830]').\n"
		"\t\tDefault is short (version) info    (e.g. '100830').\n"
		"  /?\t\tDisplays this usage information and exits.\n";
}


BOOL ProcessExeFile (LPVOID pHeader, struct tm *pTimeStruct)
{
	PIMAGE_NT_HEADERS pNTHeader;

	pNTHeader = MakePtr( PIMAGE_NT_HEADERS, pHeader,
	                     ((PIMAGE_DOS_HEADER)pHeader)->e_lfanew );
	if ( pNTHeader->Signature != IMAGE_NT_SIGNATURE )
	{
		// Not a Portable Executable (PE) EXE
		return FALSE;
	}

	// Link DateTime (number of seconds UNIX epoch Jan 1. 1970 00:00 UTC)
	// pNTHeader->FileHeader.TimeDateStamp (DWORD)
	time_t timeDateStamp = pNTHeader->FileHeader.TimeDateStamp;

	time2struct(pTimeStruct, &timeDateStamp);
	return TRUE;
}


BOOL ProcessLibFile (LPVOID pHeader, struct tm *pTimeStruct)
{
	PIMAGE_ARCHIVE_MEMBER_HEADER pArchiveHeader;

	pArchiveHeader = MakePtr(PIMAGE_ARCHIVE_MEMBER_HEADER, pHeader, 8);

	TCHAR  date[13] = "";
	strncpy_s(date, (TCHAR*)pArchiveHeader->Date, 12);
	date[12] = '\0';
	time_t t = atoi(date);

	time2struct(pTimeStruct, &t);
	return TRUE;
}


BOOL ProcessObjFile (LPVOID pHeader, struct tm *pTimeStruct)
{
	return FALSE;
}
