//global variables
var verString="Blocklist Converter 2.38";
var settings,ui; // global access
var startTime,endTime; //timing
var cnv; //global Converter Object for error handling access
var tabPane; //holder for the tabPane object (defined in the page but required here to prevent errors)

//initialise the converter
function doOnLoad(){
	var initStr="";
	settings=new Cookie(document,"converter",8760); //cookie lasts for a year
	if(settings.read()) initStr+="Stored settings loaded. "; //reads the cookie into memory if it's there
	ui=new UI();
	ui.update();//do complete refresh
	ui.hideRow('trErr'); //hide the exception log
	initStr+="Converter initialised.";
	ui.updateStatus(initStr);
}

//clear up on page exit
function doOnUnLoad(){
	if(settings.saveOnExit=="0") settings.write();
}

//extensions to String Object for simplicity
String.prototype.startsWith=function(str){return (this.indexOf(str)==0);}
String.prototype.endsWith=function(str){return (this.lastIndexOf(str)==(this.length-str.length));}
String.prototype.contains=function(str){return (this.indexOf(str)!=-1);}

function convert(){
	if (settings.debugLevel!=null){
		if(settings.debugLevel==2){
			if(!confirm("Verbose debugging will cause the converter to slow down dramatically.\nThis is HIGHLY unadvisable with large lists!\n\nDo you wish to continue?")) {
				if(tabPane) tabPane.setSelectedIndex(4); //go to the debugging tab
				return;
			}
		}
	}
	startTime=new Date();//set the stopwatch
	cnv=new Converter(); //initialise the converter
	cnv.convert(); //do it
}

function Converter(){
	//clear the exception log
	var se = ui.getElement('serr');
	if (se){
		se.value='';
		ui.hideRow('trErr');
	}
	//clear the debug log
	var sdbg=ui.getElement('sdbg');
	if (sdbg) sdbg.value='';

	//add the plugins here
	this.plugins=new Array();
	var doFinalSort=(settings.sortBy!=0);
	//if merge then set up plugins for the merge
	if (settings.listCleaning>1){
		this.plugins.push(new SortByIP());
		this.plugins.push(new Merger());
		doFinalSort=(settings.sortBy>1);
	}
	//if output = zap4 then sort by rule type to allow permit & deny blocks
	if (ui.getComboValue("toformat")=="zonealarm"){
		this.plugins.push(new SortByRuleType());
		doFinalSort=false;
	}
	//set up a final sort, if needed
	if (doFinalSort) this.plugins.push(eval("new SortBy"+ui.getComboValue("sortBy")+"()"));
}

Converter.prototype.convert=function(){
	try{
		//assign formats from the ui
		ui.updateStatus("Assigning format handlers for conversion");
		this.assignIO();
		//read the data in
		this.rules=this.dataInput.read();
		var entriesRead=this.rules.length;
		//process the plugins
		ui.updateStatus("Processing plugins...");
		for(var m=0;m<this.plugins.length;m++){
			this.pluginID=this.plugins[m].name;
			ui.debug("Processing plugin: "+this.pluginID);
			this.rules=this.plugins[m].process(this.rules);
		}
		this.pluginID=null;
		//write the data back to the page
		this.dataOutput.write(this.rules);
		//now update the status
		var statStr=this.dataInput.formatName+" to "+this.dataOutput.formatName+": "+entriesRead+" rules read. "
		statStr+=(this.dataInput.ignored+this.dataInput.dupes)+" items ignored. ";
		statStr+=(this.rules.length)+" rules written. ";
		statStr+=(this.exceptionCount)+" exceptions. ";
		endTime = new Date();
		var diff = endTime.getTime() - startTime.getTime();
		statStr+= "Time taken : "+(diff/1000) + "s. ";
		//process the continuable exceptions
		if (this.exceptionList!=''){
			scnt=this.exceptionCount+' continuable exception(s) have occurred.\n\n';
			if(settings.listCleaning!=0) scnt+='Line numbering may be incorrect due to List Cleaning.\nFor accurate line numbers, please turn off List Cleaning.\n\n';
			errBox=ui.getElement("serr")
			if (errBox){
				errBox.value=this.exceptionList;
				alert(scnt+"Please check the error log for more details.");
				if(tabPane) tabPane.setSelectedIndex(3);
			}else{
				alert(scnt+this.exceptionList);
			}
		}else{
			//switch to output
			if(tabPane) tabPane.setSelectedIndex(2);
		}
		//update the status
		ui.setFinalStatus(statStr);
	}catch(e){
		//error handler
		if (e.isCustom){
			this.handleError(e.toString());
		} else if(e.message){
			//it's an internal error
			this.handleError("Javascript Runtime Error "+(e.number & 0xFFFF)+": ["+e.name+"]: "+e.message);
		}else{
			//it's one we've thrown ourself, but without the CustomError class
			this.handleError("Error: "+e);
		}
	}
}

Converter.prototype.dataInput; //Rule Decoder
Converter.prototype.dataOutput; //Rule Encoder
Converter.prototype.rules;	//storage array for intermediary Rule objects
Converter.prototype.plugins=new Array();

Converter.prototype.handleError=function(what){
	//Fatal Error
	var strFormats="";
	var lastStatus=ui.lastStatus;
	if(this.dataInput)strFormats+=this.dataInput.formatName
		else strFormats+=ui.getComboValue("fromformat");
	strFormats+=" to ";
	if(this.dataOutput)strFormats+=this.dataOutput.formatName
			else strFormats+=ui.getComboValue("toformat");
	ui.setFinalStatus("An error has occurred. Please check output for details.");
	var errStr=verString+" Quality Feedback Report\n\n"
	errStr+=what+"\nFormat conversion: "+strFormats+"\n";
	if(this.pluginID){
		//happened during plugin processing
		errStr+="Stalled during plugin processing: "+this.pluginID;
	}else if(this.dataInput.currLineNum==0){
		//happened before DataInput.read();
		errStr+="Stalled prior to rule conversion.";
	}else if(this.dataOutput.ruleIndex>-1){
		//happened after start of DataOutput.write();
		errStr+="Stalled writing rule "+(this.dataOutput.ruleIndex+1);
	}else{
		//happened during DataInput.read()
		errStr+="Stalled reading entry "+this.dataInput.currLineNum+": ["+this.dataInput.currLine+"]";
	}
	//build the info for the error log
	//errStr+=stacktrace();
	errStr+="\nLast Status: "+lastStatus;
	errStr+="\n\nUser Agent: "+navigator.userAgent+"\n\n";
	errStr+="Source List:\n\n"+ui.getElement("sfrom").value;

	var elmErrOut=ui.getElement("serr");
	if (elmErrOut){
		if(tabPane) tabPane.setSelectedIndex(3);
	}else{
		elmErrOut=ui.getElement("sto");
		if(tabPane) tabPane.setSelectedIndex(2);
	}
	elmErrOut.value=errStr;
	alert("An error has occurred while converting from "+strFormats+" format.\nPlease check that the source format is correct for the data you wish to convert, and that the source list is syntactically correct.\n\nA Quality Feedback Report has been generated.\n\nIf you have done everything right, it is important to post this in the forums, so I can fix the bug, but please check existing reports in the forums first!");
}

Converter.prototype.exceptionList=''; //store continuable exceptions
Converter.prototype.exceptionCount=0; //count the exceptions

Converter.prototype.handleException=function(what){
	//Continuable Error - log to exceptionList
	this.exceptionList+=what+"\n";
	this.exceptionCount++;
}

Converter.prototype.assignIO=function(){
	//assigns the DataInput and DataOutput
	//pass whether exact duplicates should be removed to DataInput
	var fromformat=ui.getComboValue("fromformat");
	var toformat=ui.getComboValue("toformat");
	try{
		ui.debug("creating new DataInput_"+fromformat+"();");
		this.dataInput=eval("new DataInput_"+fromformat+"();");
	}catch(e){
		throw "Source format not implemented (DataInput_"+fromformat+")!";
	}
	try{
		ui.debug("creating new DataOutput_"+toformat+"();");
		this.dataOutput=eval("new DataOutput_"+toformat+"();");
	}catch(e){
		throw "Output format not implemented (DataOutput_"+toformat+")!";
	}
}

//===========================
// Rule: internal format for data manipulation
//===========================

function Rule(startAddr,endAddr,netMask,action,comment){
	//just store it as startIP-endIP, this reduces mistakes.
	if (!startAddr) this.error="Start IP is required but is unavailable";
	if (!validateIP(startAddr)) this.error="Start IP is invalid";
	var startIP=ipToDec(startAddr);
    if (endAddr){
    	//endAddr is optional
		if (!validateIP(endAddr)) this.error="End IP is invalid";
    	var endIP=ipToDec(endAddr);
		if (endIP<startIP) {
			this.error= "Incorrect Data Format: Start IP is greater than End IP";
		}
    }else if (netMask){
    	//netMask is optional
    	//this is extremely tolerant - it considers the netmask to be in effect a range size
    	//and does no check for validity as a mask
    	var nmsk=ipToDec(netMask);
   		var rngSize=maxIP-nmsk;
   		var endIP=startIP+rngSize-1;
   	}else{
		//got neither endIP or netmask, so is a single ip
		var endIP=startIP;
	}
	if (action) this.action=action;
	if (comment) {
    	var prefix=ui.getElement("prefix").value;
    	//alert(comment.indexOf(prefix));
    	if (comment.indexOf(prefix)!=0) this.comment+=prefix;
    	this.comment+=comment;
    }
    if (!this.error) {
		ui.debug('-New Rule: ('+this.action+") "+this.comment);
		this.range=new NetworkRange(null,null,startIP,endIP);
	}
}

Rule.prototype.error=null; //stores conversion error (these are fatal for the rule but not the converter)
Rule.prototype.range; //holds the NetworkRange Object
Rule.prototype.action="deny"; //Default action = deny (anything else = permit)
Rule.prototype.comment=""; //Store the name/comment for the range

Rule.prototype.toString=function(){
	return '['+this.range.startAddr+'-'+this.range.endAddr+':'+this.action+':'+this.comment+']';
}

function CustomError(theName,theNumber,theMessage){
	//construct a new error
	this.name=theName;
	this.number=theNumber;
	this.message=theMessage;
	this.stacktrace="Stacktrace unavailable";
	//this.stacktrace=traceback(); //has to be run in constructor before the error is actually thrown
}

CustomError.prototype.isCustom=true; //custom flag to show it is a custom error

CustomError.prototype.toString=function(){
	return "Converter Runtime Error "+(this.number)+": ["+this.name+"]: "+this.message+"\nStacktrace: "+this.stacktrace;
}

CustomError.prototype.name;
CustomError.prototype.number;
CustomError.prototype.message;
CustomError.prototype.stacktrace;

function traceback(){
	var vFP=traceback.caller.caller;
	var retVal="";
	if(vFP != null){
		var aRoute=new Array();
		var iCounter=0;
		while(vFP && vFP != null ){

			var sName=getFunctionName(vFP.toString());
			if(sName==null){
				vFP=null;
				break;
			}
			aRoute[aRoute.length]=sName;
			vFP=vFP.caller;
		}
		var iRLen=aRoute.length;
		for(var i=iRLen-1;i>=0;i--){
			if(i < iRLen-1) retVal+= "->";
			retVal+=aRoute[i];
		}
	}
	else{
		retVal="Unavailable";
	}
	return retVal;
}

function getFunctionName(sFP){
	//if(sFP==null) return sFP;
	//may have to altered to accomodate classes
	var aM=sFP.match(/function\s([A-Za-z0-9_]*)\(/gi);
	if(aM!=null && aM.length){
		sFP=aM[0];
		sFP=sFP.replace(/^function\s+/,"");
		sFP=sFP.replace(/^\s*/,"");
		sFP=sFP.replace(/\s*$/,"");
		sFP=sFP.replace(/\($/,"");
		return sFP;
	}
	else{
		return null;
	}
}
