/*
  x2bc - x2 bod compiler/bod converter command line utility

  Author: doubleshadow

*/

#include <stdio.h>
#include <conio.h>
#include <memory.h>
#include <io.h>
#include <fcntl.h>
#include <sys/stat.h>

#include <windows.h>

#include "../common/strutils.h"
#include "../x2bc_common/version.h"
#include "../libx2bc/libx2bc.h"
//---------------------------------------------------------------------------------
#define PROGRAM_NAME "x2bc"

// return values
#define ERROR_FLAGS 1 // invalid flags
#define ERROR_PARAMS 2 // missing parameters
#define ERROR_CONVERT	3 // error during conversion

#define INI_NAME "x2bc.ini"

// program flags
#define F_HELP					1
#define F_COMPILE				2
#define F_DECOMPILE			4
#define F_SILENT				8
#define F_BOB						16
#define F_CUT						32
#define F_INI						64
#define F_VERSION       128

#define STR_F_HELP "help"
#define STR_F_SILENT "silent"
#define STR_F_VERSION "version"

#define STR_F_COMPILE "compile"
#define STR_F_DECOMPILE "decompile"
#define STR_F_CUT "cut"
#define STR_F_BOB "bob"
#define STR_F_INI "ini"
#define STR_F_FRAME_WARN "frame_warnings"
#define STR_F_XTRAPNTINFO "xpinfo"
#define STR_F_XTRAX3INFO "x3info"
//#define STR_F_X3BOB "x3bob"
//---------------------------------------------------------------------------------
#define SWITCH_ON 1
#define SWITCH_OFF -1
#define SWITCH_NONE 0

struct convert_params
{
	int xtraPtInfo;
	int xtraX3Info;
	int frameWarnings;
	int flags;
	//int X3BOB;
	
	/*convert_params()
	{
		X3BOB=SWITCH_NONE;
	}*/
};

//---------------------------------------------------------------------------------
int convert(const char *pszSourceFile, const char *pszOutFile, const char *pszININame, const convert_params& params);
bool compile(const char *pszFileName, const char *pszOutFileName, int FileType);
bool decompile(const char *pszFileName, const char *pszOutFileName);
char *fullININame(const char *fileName);

char * getVersion(const char *lpFileName);
void printVersion();

const char* getSeverity(const X2BC_ERROR &e)
{
	static const char * severity[]={
		"Success",
		"Error",
		"Warning"
	};

	return severity[X2BC_SEVERITY(e.code)];
}
//---------------------------------------------------------------------------------
const char* getFacility(const X2BC_ERROR &e)
{
	static const char * facility[]={
		"", // 0 is not used, facilities are from 1
		"Settings",
		"BOB Loader",
		"STAT Loader",
		"Compiler",
		"Library"
	};
	return facility[X2BC_FACILITY(e.code)];
}
//---------------------------------------------------------------------------------
void showHelp()
{
	printf("\nUsage: " PROGRAM_NAME " [flags] [input file] [output file]\n");
	printf("\nFlags:\n    -?, --" STR_F_HELP "\t\t\tshow this help\n");
	printf("    -s, --" STR_F_SILENT "\t\tdon't ask anything. Assume Yes on questions\n");
	printf("        --" STR_F_VERSION "\t\tprint version information\n");
	printf("\n  Compilation\n");
	printf("    -c, --" STR_F_COMPILE "\t\tcompile BOD to BOB\n");
	printf("        --" STR_F_CUT "\t\t\tinput BOD contains CUT data\n");
	printf("        --" STR_F_BOB "\t\t\tinput BOD contains BOB data\n");
	printf("\n  Decompilation\n");
	printf("    -d, --" STR_F_DECOMPILE "\t\tdecompile BOB to BOD\n");
	printf("        --" STR_F_INI " <file name>\tspecify INI file to load\n");
	printf("        --" STR_F_FRAME_WARN "[-]\t(don't) show user frame warnings\n");
	printf("        --" STR_F_XTRAPNTINFO "[-]\t\t(don't) write extra point info\n");
	printf("        --" STR_F_XTRAX3INFO "[-]\t\t(don't) write extra info stored in X3 BOB1\n");
	//printf("        --" STR_F_X3BOB "[+/-]\t\tforce/suppress X3 BOB1 data loading/writing\n");
	printf("\n");
}
//---------------------------------------------------------------------------------
int main(int argc, char *argv[])
{
	int res=0;
	convert_params params;

	params.frameWarnings=SWITCH_NONE;
	params.xtraPtInfo=SWITCH_NONE;
	params.flags=0;

	printf("\nX2/X3 BOD compiler/BOB decompiler by Doubleshadow.\n");
	printf("BOB decompiler based on work of CheckerTwo.\n\n");

	const char *pszSourceFile=0, *pszOutFile=0, *pszININame=0;

	char *arg;
	for(int i=1; i < argc; i++){

		int flags=0;

		arg=argv[i];
		// file names
		if(*arg!='-' && *arg!='/'){
			if(params.flags & F_INI){
				if(pszININame!=0){
					printf(PROGRAM_NAME": INI name already specified\n");
					res=ERROR_FLAGS;
				}
				else{
					pszININame=arg;
					params.flags&=~F_INI;
				}
			}
			else if(pszSourceFile==0)
				pszSourceFile=arg;
			else if(pszOutFile==0)
				pszOutFile=arg;
			else{
				printf(PROGRAM_NAME": Too many paramaters\n");
				res=ERROR_FLAGS;
			}
		}
		// long options
		else{
			arg++;
			if(*arg=='-' && arg[-1]=='-'){
				arg++;

				int switchState=SWITCH_NONE;
				char ch=arg[strlen(arg)-1];
				if(ch=='+' || ch=='-'){
 					arg[strlen(arg)-1]=0;
					if(ch=='+')
						switchState=SWITCH_ON;
					else if(ch=='-')
						switchState=SWITCH_OFF;
				}

				// flags
				if(stricmp(arg, STR_F_HELP)==0) flags=F_HELP;
				else if(stricmp(arg, STR_F_VERSION)==0) flags=F_VERSION;
				else if(stricmp(arg, STR_F_COMPILE)==0) flags=F_COMPILE;
				else if(stricmp(arg, STR_F_DECOMPILE)==0) flags=F_DECOMPILE;
				else if(stricmp(arg, STR_F_SILENT)==0) flags=F_SILENT;
				else if(stricmp(arg, STR_F_BOB)==0) flags=F_BOB;
				else if(stricmp(arg, STR_F_CUT)==0) flags=F_CUT;
				else if(stricmp(arg, STR_F_INI)==0) flags=F_INI;
				// switches
				else if(stricmp(arg, STR_F_FRAME_WARN)==0) params.frameWarnings=switchState ? switchState : SWITCH_ON;
				else if(stricmp(arg, STR_F_XTRAPNTINFO)==0) params.xtraPtInfo=switchState ? switchState : SWITCH_ON;
				else if(stricmp(arg, STR_F_XTRAX3INFO)==0) params.xtraX3Info=switchState ? switchState : SWITCH_ON;
				//else if(stricmp(arg, STR_F_X3BOB)==0) params.X3BOB=switchState ? switchState : 2;

				else{
					printf(PROGRAM_NAME": Unknown option '--%s'\n", arg);
					res=ERROR_FLAGS;
					break;
				}
				if(flags && switchState!=SWITCH_NONE){
					printf(PROGRAM_NAME": Switch modifier not allowed here '--%s'\n", arg);
					res=ERROR_FLAGS;
				}
			}
			// short options
			else{
				while(*arg!=0){
					switch(*arg){
						case '?':
							flags=F_HELP;
							break;
						case 'c':
							flags=F_COMPILE;
							break;
						case 'd':
							flags=F_DECOMPILE;
							break;
						case 's':
							flags=F_SILENT;
							break;
						default:
							printf(PROGRAM_NAME": Unknown flag '%c'\n", *arg);
							res=ERROR_FLAGS;
					}
					if(res==ERROR_FLAGS) break;
					arg++;
				}
			}

			params.flags|=flags;
		}
		if(res!=0) break;
	}

	if(params.flags & F_VERSION)
		printVersion();

	if(res==0 && pszSourceFile==0 && (params.flags & (F_HELP | F_VERSION))==0){
		printf("No input file specified\n");
		res=ERROR_PARAMS;
	}

	if(res==0 && pszSourceFile && (params.flags & F_COMPILE) && ((params.flags & F_BOB)==0 && (params.flags & F_CUT)==0)){
		printf(PROGRAM_NAME": Type of input file not specified\n");
		res=ERROR_PARAMS;
	}

	if((params.flags & F_HELP) || res!=0)
		showHelp();

	if(res==0 && (params.flags & F_COMPILE) && (params.flags & F_DECOMPILE)){
		printf(PROGRAM_NAME": Cannot specify --" STR_F_COMPILE " and --" STR_F_DECOMPILE " together\n");
		res=ERROR_FLAGS;
	}
	if(res==0){
		if((params.flags & F_BOB) && (params.flags & F_CUT)){
			printf(PROGRAM_NAME": Cannot specify --" STR_F_BOB " and --" STR_F_CUT " together\n");
			res=ERROR_FLAGS;
		}
	}

	if(res==0 && pszSourceFile){
		char *ini=fullININame(pszININame);
		res=convert(pszSourceFile, pszOutFile, ini, params);
		delete[] ini;
	}
#ifdef _DEBUG
	printf("\n\nPress any key to continue...");
	while(getch()==0)
		;;
#endif

	return res;
}
//---------------------------------------------------------------------------------
// returns ini name relative to the path of "this" executable
char * defaultININame()
{
	char *name=new char[MAX_PATH];

	// load ini relatively to the directory where is the executable
	GetModuleFileName(NULL, name, MAX_PATH);
	char *path=ExtractFilePath(name);
	size_t len=strlen(path);
	memcpy(name + len + 1, INI_NAME, strlen(INI_NAME) + 1);
	delete[] path;
	return name;
}
//---------------------------------------------------------------------------------
/*
  in debug this returns full path to ini relative to current working dir
  in release it works the same with the exception that if there is no fileName
  given it will return fullININame2
*/
char * fullININame(const char *fileName)
{
#ifndef _DEBUG
	if(fileName==NULL)
		return defaultININame();
#endif

	char *name=new char[MAX_PATH];

	GetFullPathName(fileName ? fileName : INI_NAME, MAX_PATH, name, NULL);

	return name;
}
//---------------------------------------------------------------------------------
bool askFileOverwrite(const char *pszFileName)
{
	bool bRes=true;
	struct stat x;
	if(stat(pszFileName, &x)!=-1){
		printf("File \"%s\" already exists, overwrite? (y/n): ", pszFileName);
		char ch;
		while((ch=getch())==0)
			;
		printf("%c\n\n", ch);
		bRes=((ch | 0x20)=='y');
	}
	return bRes;
}
//---------------------------------------------------------------------------------
int convert(const char *pszSourceFile, const char *pszOutFile, const char *pszININame, const convert_params& params)
{
	char *outfile;
	bool bRes=false;
	if(pszSourceFile){
		if(pszOutFile==0){
			char extension[10];
			const char *ch;
			int len;
			if(params.flags & F_COMPILE)
				len=GetPrivateProfileString("program", "BOBExt", "bob", extension, sizeof(extension), pszININame);
			else
				len=GetPrivateProfileString("program", "BODExt", "bod", extension, sizeof(extension), pszININame);
			ch=extension;
			if(*ch=='.') ch++;
			outfile=ChangeFileExtension(pszSourceFile, ch);
		}
		else
			outfile=(char*)pszOutFile;

		X2BC_ERROR e;

		bRes=X2BC_LoadSettings(pszININame) > 0;
		while(X2BC_GetError(&e)){
			printf("%s - %s: %s\n", getFacility(e), getSeverity(e), e.text);
		}

		if(bRes){
			if(params.frameWarnings!=SWITCH_NONE)
				X2BC_SetOption(X2BC_O_FRAME_WARNINGS, params.frameWarnings==SWITCH_ON ? "1" : "0");
			if(params.xtraPtInfo!=SWITCH_NONE)
				X2BC_SetOption(X2BC_O_XTRA_PNT_INFO, params.xtraPtInfo==SWITCH_ON ? "1" : "0");
			if(params.xtraX3Info!=SWITCH_NONE)
				X2BC_SetOption(X2BC_O_XTRA_X3_INFO, params.xtraX3Info==SWITCH_ON ? "1" : "0");
			/*if(params.X3BOB!=SWITCH_NONE){
				char ch[5];
				int i=params.X3BOB;
				if(i==2) i=0;
				itoa(i, ch, 10);
				X2BC_SetOption(X2BC_O_X3BOB, ch);
			}*/
			if((params.flags & F_SILENT) || askFileOverwrite(outfile)==true){
				printf("%s file\n\"%s\" to\n\"%s\"\n\n", params.flags & F_COMPILE ? "Compiling" : "Decompiling", pszSourceFile, outfile);

				if(params.flags & F_COMPILE)
					bRes=compile(pszSourceFile, outfile, (params.flags & F_BOB ? X2BC_BOD_BOB : X2BC_BOD_CUT));
				else
					bRes=decompile(pszSourceFile, outfile);
			}
		}
		if(outfile!=pszOutFile) delete[] outfile;
	}

	return bRes ? 0 : ERROR_CONVERT;
}
//---------------------------------------------------------------------------------
bool decompile(const char *pszFileName, const char *pszOutFileName)
{
	bool bRes=X2BC_BOB2BOD(pszFileName, pszOutFileName) > 0;

	X2BC_ERROR e;
	while(X2BC_GetError(&e)){
		printf("%s - %s: %s\n", getFacility(e), getSeverity(e), e.text);
	}

	char *name=ExtractFileName(pszFileName);
	printf("%s - decompiled %s\n", name, bRes ? "successfully" : "with errors");
	delete name;

	return bRes;
}
//---------------------------------------------------------------------------------
bool compile(const char *pszFileName, const char *pszOutFileName, int FileType)
{
	int fd=_open(pszFileName, _O_BINARY | _O_RDONLY);
	bool bRes=false, bExit=false;
	if(fd==-1)
		printf("Error opening file \"%s\"\n", pszFileName);

	else{
		long len=filelength(fd);

		char *data=new char[len + 1];
		read(fd, data, len);
		close(fd);
		data[len]=0;

 		bRes=X2BC_BOD2BOB(data, len, pszOutFileName, FileType) > 0;
		delete[] data;
 
		X2BC_ERROR e;
		int errCount=0, warnCount=0;
		while(X2BC_GetError(&e)){
			if(X2BC_SEVERITY(e.code)==X2BC_SEV_ERROR)
				errCount++;
			else if(X2BC_SEVERITY(e.code)==X2BC_SEV_WARNING)
				warnCount++;

			printf("%s", getFacility(e));
			if(e.line!=-1)
				printf(" - line %d, col %d", e.line, e.col);
			printf(" - %s: %s\n", getSeverity(e), e.text);
		}
		char *name=ExtractFileName(pszFileName);
		printf("\n%s - %d error(s), %d warning(s) - compiled %s\n", name, errCount, warnCount, bRes ? "successfully" : "with errors");
		delete name;
	}
	return bRes;
}
//---------------------------------------------------------------------------------
void printVersion()
{   
	char *myver=getMyVersion(0);
	char dllver[50];
	X2BC_GetVersion(dllver, sizeof(dllver));

	printf("Version: %s, libx2bc: %s\n", myver, dllver);

	delete[] myver;
}
//---------------------------------------------------------------------------------
