#include "libx2bc.h"
#include "libx2bc.h"

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

#include "../x2bc_common/version.h"
#ifdef S_OK
	#undef S_OK
#endif

#include "../x2bc_common/settings.h"
#include "../x2bc_common/bob_dom.h"
#include "../x2bc_common/bod_parser.h"
#include "../x2bc_common/bob_realfile_stream.h"

static Settings g_settings;

static HANDLE g_hinstDLL;

static ext::list<X2BC_ERROR*> g_errors;
//---------------------------------------------------------------------------------
void INIError(Settings::ErrorMsg *e);
void error(int facility, int severity, int code, const char *format, ...);
void BOBError(bob_dom_document::Error *e);
void BCError(bod_parser::Error *e);
//---------------------------------------------------------------------------------
BOOL WINAPI DllMain(HANDLE  hDllHandle,DWORD   dwReason,LPVOID  lpreserved) 
{
	if(dwReason==DLL_PROCESS_ATTACH)
		g_hinstDLL=hDllHandle;
	return TRUE;
}
//---------------------------------------------------------------------------------
X2BC_EXPORT int X2BCAPI X2BC_LoadSettings(const char *pszINIFileName)
{
	bool bRes=g_settings.loadINI(pszINIFileName);
	
	for(Settings::ErrorIterator &it=g_settings.errors.begin(); it!=g_settings.errors.end(); ++it){
		INIError(*it);
	}
	g_settings.clearErrors();
	
	return bRes;
}
//---------------------------------------------------------------------------------
X2BC_EXPORT int X2BCAPI X2BC_BOB2BOD(const char *pszFileName, const char *pszOutFileName)
{
	bob_dom_document doc(&g_settings);
	
	bob_dom_ibinaryrealfile is;
	bob_dom_otextrealfile os;
	os.open(pszOutFileName, bob_filestream::create);
	is.open(pszFileName, bob_filestream::rdonly);
	if(is.fail())
		error(X2BC_FACILITY_BOBLOADER, X2BC_SEV_ERROR, X2BC_E_FileCannotOpen, "Cannot open file \"%s\"", pszFileName);
	else
		if(os.fail())
			error(X2BC_FACILITY_BOBLOADER, X2BC_SEV_ERROR, X2BC_E_FileCannotOpen, "Cannot open file \"%s\"", pszOutFileName);
	
	if(is.fail() || os.fail()) return false;
	
	bool bRes=doc.convert(is, os);

	for(bob_dom_document::ErrorIterator &it=doc.errors.begin(); it!=doc.errors.end(); ++it){
		BOBError(*it);
	}
	return bRes;
}
//---------------------------------------------------------------------------------
X2BC_EXPORT int X2BCAPI X2BC_BOD2BOB(char *pszBuffer, int nSize, const char *pszOutFileName, int nBODType)
{
	bod_parser p(&g_settings);
	bool bRes;
	bod_parser::fileType type;
	
	if(nBODType==X2BC_BOD_CUT)
		type=bod_parser::cutFile;
	else if(nBODType==X2BC_BOD_BOB)
		type=bod_parser::bobFile;
	else{
		error(X2BC_FACILITY_DLL, X2BC_SEV_ERROR, X2BC_E_BadFlags, "Invalid input file type specified");
		return false;
	}
	
	bob_dom_obinaryrealfile os;
	os.open(pszOutFileName, bob_filestream::create);
	if(os.bad())
		error(X2BC_FACILITY_DLL, X2BC_SEV_ERROR, X2BC_E_FileCannotOpen, "Cannot open file \"%s\"", pszOutFileName);
	else{
		bRes=p.compile(pszBuffer, (size_t)nSize, type, os);
		for(bod_parser::iterator &it=p.errors.begin(); it!=p.errors.end(); ++it){
			BCError(*it);
		}
	}
	os.close();
	
	return bRes;
}
//---------------------------------------------------------------------------------
X2BC_EXPORT int X2BCAPI X2BC_GetError(X2BC_ERROR *pError)
{
	if(g_errors.size()==0) return false;
	
	X2BC_ERROR *e=g_errors.front();
	
	pError->code=e->code;
	pError->col=e->col;
	pError->line=e->line;
	memcpy(pError->text, e->text, sizeof(pError->text));
	delete g_errors.front();
	g_errors.pop_front();
	return true;
}
//---------------------------------------------------------------------------------
int makeCode(int f, int s, int c)
{
	int code;
	code=s << 24;
	code|=f << 16;
	code|=c;
	return code;
}
//---------------------------------------------------------------------------------
void INIError(Settings::ErrorMsg *e)
{
	X2BC_ERROR *er=new X2BC_ERROR();
	
	int c;
	switch(e->code){
		case Settings::E_BadConstantDeclaration:
			c=X2BC_E_BadConstantDeclaration;
			break;
		case Settings::E_BadFormatDeclaration:
			c=X2BC_E_BadFormatDeclaration;
			break;
		case Settings::E_SectionEmpty:
			c=X2BC_E_SectionEmpty;
			break;
		case Settings::W_ConstantAlwaysZero:
			c=X2BC_E_ConstantAlwaysZero;
			break;
		case Settings::W_LineIgnored:
			c=X2BC_E_LineIgnored;
			break;
		default:
			c=X2BC_E_Error;
	}
	
	er->code=makeCode(X2BC_FACILITY_SETTINGS, e->type==Settings::Warning ? X2BC_SEV_WARNING : X2BC_SEV_ERROR, c);
	strncpy(er->text, e->message, sizeof(er->text));
	g_errors.push_back(er);
}
//---------------------------------------------------------------------------------
void BOBError(bob_dom_document::Error *e)
{
	X2BC_ERROR *er=new X2BC_ERROR();
	
	int c;
	switch(e->getCode()){
		case bob_dom_document::E_BadEndHeader:
			c=X2BC_E_BadEndHeader;
			break;
		case bob_dom_document::E_BadHeader:
			c=X2BC_E_BadHeader;
			break;
		case bob_dom_document::E_FormatUserWarning:
			c=X2BC_E_FormatUserWarning;
			break;
		case bob_dom_document::E_MoreData:
			c=X2BC_E_MoreData;
			break;
		case bob_dom_document::E_NoError:
			c=X2BC_E_OK;
			break;
		case bob_dom_document::E_NoStatFormat:
			c=X2BC_E_NoStatFormat;
			break;
		case bob_dom_document::E_NotEnoughData:
			c=X2BC_E_NotEnoughData;
			break;
		case bob_dom_document::E_UnkPointHeader:
			c=X2BC_E_UnkPointHeader;
			break;
		case bob_dom_document::E_BadVersion:
			c=X2BC_E_BadVersion;
			break;
		case bob_dom_document::E_Error: // no break
		default:
			c=X2BC_E_Error;
	};
	int f;
	switch(e->facility()){
		case bob_dom_document::F_BOBLoader:
			f=X2BC_FACILITY_BOBLOADER;
			break;
		case bob_dom_document::F_StatLoader:
			f=X2BC_FACILITY_STATLOADER;
			break;
	}
	int s;
	switch(e->severity()){
		case bob_dom_document::S_Error:
			s=X2BC_SEV_ERROR;
			break;
		case bob_dom_document::S_OK:
			s=X2BC_SEV_SUCCESS;
			break;
		case bob_dom_document::S_Warning:
			s=X2BC_SEV_WARNING;
			break;
	}
	er->col=0;
	er->line=0;
	er->code=makeCode(f, s, c);
	strncpy(er->text, e->text, sizeof(er->text));
	g_errors.push_back(er);
}
//---------------------------------------------------------------------------------
void BCError(bod_parser::Error *e)
{
	X2BC_ERROR *er=new X2BC_ERROR();
	int c;
	switch(e->getCode()){
		case bod_parser::E_Error:
		default:
			c=X2BC_E_Error;
	}
	int s;
	switch(e->severity()){
		case bod_parser::S_Error:
			s=X2BC_SEV_ERROR;
			break;
		case bod_parser::S_OK:
			s=X2BC_SEV_SUCCESS;
			break;
		case bod_parser::S_Warning:
			s=X2BC_SEV_WARNING;
			break;
	}
	
	er->col=e->col;
	er->line=e->line;
	er->code=makeCode(X2BC_FACILITY_COMPILER, s, c);
	strncpy(er->text, e->text, sizeof(er->text));
	g_errors.push_back(er);
}
//---------------------------------------------------------------------------------
void error(int facility, int severity, int code, const char *format, ...)
{
	X2BC_ERROR *e=new X2BC_ERROR();
	e->code=makeCode(facility, severity, code);
	
	va_list ap;
	va_start(ap, format);
	_vsnprintf(e->text, sizeof(e->text), format, ap);
	va_end(ap);
	e->col=0;
	e->line=0;
	g_errors.push_back(e);
}
//---------------------------------------------------------------------------------
X2BC_EXPORT int X2BCAPI X2BC_SetOption(int nOption, const char *pszValue)
{
	bool bRes=true;
	int val=atoi(pszValue);
	
	switch(nOption){
		case X2BC_O_FRAME_WARNINGS:
			g_settings.statFormatWarnings(val!=0);
			break;
		case X2BC_O_XTRA_PNT_INFO:
			g_settings.extraPntInfo(val!=0);
			break;
		case X2BC_O_XTRA_X3_INFO:
			g_settings.extraX3BobInfo(val!=0);
			break;
		/*case X2BC_O_X3BOB:
			Settings::SwitchValue v;
			if(val==-1)
				v=Settings::SwitchValue::off;
			else if(val==1)
				v=Settings::SwitchValue::on;
			else
				v=Settings::SwitchValue::notSet;
				
			g_settings.X3BOB(v);
			break;*/
		default:
			error(X2BC_FACILITY_DLL, X2BC_SEV_ERROR, X2BC_E_BadFlags, "Bad option parameter");
			bRes=false;
	}
	return bRes;
}
//---------------------------------------------------------------------------------
X2BC_EXPORT int X2BCAPI X2BC_GetVersion(char *pszBuffer, int nBuffLen)
{
	char *ver=getMyVersion((HMODULE)g_hinstDLL);
#ifdef X2BC_USE_INI_FORMATS
#define VER_FLAG " (ini frame formats)"
	char *ver2=new char[strlen(ver) + strlen(VER_FLAG) + 1];
	ver2[0]=0;
	strcat(ver2, ver);
	strcat(ver2, VER_FLAG);
	delete[] ver;
	ver=ver2;
#endif

	int verlen=(int)strlen(ver) + 1;
	if(nBuffLen==0)
		return verlen;
	int len=min(nBuffLen, verlen);
	memcpy(pszBuffer, ver, len);
	pszBuffer[nBuffLen-1]=0;
	delete[] ver;
	return len;
}
//---------------------------------------------------------------------------------