//====================================
//Author: C-take
//ProjectName:OriginalBF
//Source:http://shitake-crude-production.wikidot.com/hoby:kisei
//CC-BY-SA
//====================================
function OriginalBF(){
	
	var BFcommand = {
		"+":"+",
		"-":"-",
		">":">",
		"<":"<",
		"[":"[",
		"]":"]",
		",":",",
		".":"."
	}
	
	var BFsetting = {
		maxbit:65535,
		runingspan:5,
		response: function(hikisu){},
		autostop:true,
		manualstop:false
	}
	var runningState = {
		pointer:0,
		readpoint:0,
		nextRead:0,
		skip:0
	}
	
	function commandCustom(obj){
		return customSettingBase(BFcommand,obj);
	}
	function commandSet(obj){
		return customSettingBase(BFsetting,obj);
	}
	function customSettingBase(program,user){
		if(getValueType(user) != "object"){
			return false;
		}
		for(var i in user){
			if(program[i] != undefined){
				if(getValueType(user[i]) == getValueType(program[i])){
					program[i] = user[i];
				}
			}
		}
		return true;		
	}
	
		
	var commandLine = new Array();
	var runningTimes = new Array();
	var memories = [0];
	var loopHeader = new Array();
	
	function autoRunInner(sentence){
		var check = analyseBFInner(sentence);
		if(!check)return {
			success:false,
			pointer:0,
			readpoint:0,
			nextRead:0,
			Char:"",
			Num:0,
			End:true	
		
	};
		runningBF();
	}
	
	
	function analyseBFInner(sentence){
		if(getValueType(sentence) != "string"){
			alert("引数の型が不正です。\n型:"+getValueType(sentence));
			return false;
		}
		for(var i in runningState){
			runningState[i] = 0;
		}
		commandLine = new Array();
		showCommandLine = new Array();
		runningTimes = new Array();
		memories = [0];
		loopHeader = new Array();
		
		var prev = "";
		var read = false;
		while(sentence.length > 0){
			registerCommandLine();
			if(!read){
				alert("コードが不正です:\n"+sentence);
				return false;
			}

		}
		return read;
		function registerCommandLine(){
			read = false;
			var isSimplification = false;
			for(var i in BFcommand){
				if(sentence.indexOf(BFcommand[i]) == 0){
					Rprev = i;
					sentence = sentence.substring(BFcommand[i].length,sentence.length);
					if((prev == i) && ((i == "+")||(i == "-")||(i == ">")||(i == "<"))){
						isSimplification = true;
					}
					if(isSimplification){
						runningTimes[runningTimes.length-1]++;
					}else{
						commandLine.push(i);
						runningTimes.push(1);
					}
					read=true;
					prev = i;
					break;
				}
			}
			
		}
	}
	

	

	function runningBF(){
		var resultObj = {
			success:true,
			pointer:0,
			readpoint:0,
			nextRead:0,
			skip:0,
			Char:"",
			Num:0,
			End:false
		}
		if(BFsetting.runingspan <= 0){
			var zeroResult = spanZeroRuning();
			resultbj.Char = zeroResult.Char;
			resultbj.success = zeroResult.success;
			return resultbj;
		}
		var cRR = runCommand();
		customSettingBase(resultObj,cRR);
		customSettingBase(resultObj,runningState);
		var isRuning = true;
		if(!resultObj.success){
			if(BFsetting.autostop || (!BFsetting.autostop && BFsetting.manualstop)){
				isRuning = false;
			}
		}
		if(runningState.nextRead >= commandLine.length){
			resultObj.End = true;
			isRuning = false;
		}
		BFsetting.response(resultObj);
		if(isRuning){
			setTimeout(runningBF,BFsetting.runingspan);
		}
		return resultObj;
	}
	
	function spanZeroRuning(){
		var outputResult = "";
		while(runningState.nextRead < commandLine.length){
			var commandRun = runCommand();
			if(!commandRun.success){
				return {
					success:false,
					Char:outputResult				
				}
			}
			outputResult += commandRun.Char;
		}
		return {
			success:true,
			Char:outputResult
		}
	}
	
	
	
	function runCommand(){
		var success = true;
		var Coutput = "";
		var Noutput = 0;
		runningState.readpoint = runningState.nextRead;
		runningState.nextRead++;
		switch(commandLine[runningState.readpoint]){
			case "+":
				if(runningState.skip == 0){
					memories[runningState.pointer] += runningTimes[runningState.readpoint];
					if(memories[runningState.pointer] > BFsetting.maxbit){
						//オーバーフロー
						memories[runningState.pointer] %= (BFsetting.maxbit+1);
					}
				}
			break;
			case "-":
				if(runningState.skip == 0){
					memories[runningState.pointer] -= runningTimes[runningState.readpoint];
					if(memories[runningState.pointer] < 0){
						//アンダーフロー
						memories[runningState.pointer] = (BFsetting.maxbit+1) - ((memories[runningState.pointer] * -1)% (BFsetting.maxbit+1));
						memories[runningState.pointer] %= (BFsetting.maxbit+1);
					}
				}				
			break;
			case ">":
				if(runningState.skip == 0){
					runningState.pointer += runningTimes[runningState.readpoint];
				}
			break;
			case "<":
				if(runningState.skip == 0){
					if(runningState)
					runningState.pointer -= runningTimes[runningState.readpoint];
					if(runningState.pointer < 0){
						success = false;
					}
				}
			break;
			case "[":
				if(memories[runningState.pointer] > 0 && runningState.skip == 0){
					loopHeader.push(runningState.readpoint+1);
				}else{
					runningState.skip++;
				}
			break;
			case "]":
				if(runningState.skip == 0){
					if(loopHeader.length > 0){
						if(memories[runningState.pointer] > 0){
							runningState.nextRead = loopHeader[loopHeader.length-1];
						}else{
							loopHeader.pop();
						}
					}else{
						success = false;
					}
				}else{
					runningState.skip--;
				}
			break;
			case ",":
				if(runningState.skip == 0){
					var inputC = prompt("ユーザ入力(最初の1文字目のみ有効)");
					if(inputC != null){
						if(inputC.length > 0){
							memories[runningState.pointer] = (inputC.charCodeAt(0) % (BFsetting.maxbit+1));
						}else{
							memories[runningState.pointer] = 0;
						}				
					}else{
						memories[runningState.pointer] = 0;
					}

				}
			break;
			case ".":
				if(runningState.skip == 0){
					var charFm = String.fromCharCode(memories[runningState.pointer]);
					Coutput = charFm;
					Noutput = memories[runningState.pointer];
				}
			break;
		}
		while(runningState.pointer >= memories.length){
			memories.push(0);
		}
		return {
			sucess:success,
			Char:Coutput,
			Num:Noutput
		};
	}
	
	function getValueType(target){
		var types = typeof(target);
		if(types != "object"){
			return types;
		}
		if(Array.isArray(target)){
			return "array";
		}
		var types = Object.prototype.toString(target);
		types = types.split("[object ").join("").split("]").join("");
		return types.toLowerCase();
	}
	
	function checkCommand(){
		var output = "";
		for(var i in BFcommand){
			output += i + ":" + BFcommand[i] + "\n";
			
		}
		return output;
	}
	
	function debugActualCommand(){
		var output = commandLine.join("");
		return output;		
	}
	
	function debugRunningTimes(){
		var output = runningTimes.join(",");
		return output;
	}
	function convertOriginalBF(sentence){
		sentence = sentence.split("\r\n").join("\n");
		sentence = sentence.split("\n").join("");
		var resultObj = {
			success:false,
			code:""
		}
		var result = "";
		var read = false;
		while(sentence.length > 0){
			read = false;
			for(var i in BFcommand){
				if(sentence.indexOf(i) == 0){
					result += BFcommand[i];
					sentence = sentence.substring(1,sentence.length);
					read = true;
					break;
				}
			}
			if(!read){
				result = "エラー:" + sentence.charAt(0) + "\n" + result;
				resultObj.code = result;
				resultObj.success = read;
				return resultObj;
			}
		}
		resultObj.code = result;
		resultObj.success = read;
		return resultObj;
		
	}
	
	function reconvertOriginalBF(sentence){
		var resultObj = {
			success:false,
			code:""
		}
		var result = "";
		var read = false;
		while(sentence.length > 0){
			for(var i in BFcommand){
				if(sentence.indexOf(BFcommand[i]) == 0){
					result += i;
					sentence = sentence.substring(BFcommand[i].length,sentence.length);
					read = true;
					break;
				}
			}
			if(!read){
				result += "エラー:" + sentence.charAt(0) + "\n" + result;
				resultObj.code = result;
				resultObj.success = read;
				return resultObj;
			}			
		}
		resultObj.code = result;
		resultObj.success = read;
		return resultObj;
		
	}
	
	this.reconvert = reconvertOriginalBF;
	this.convert = convertOriginalBF;
	this.debugActualCommandLine = debugActualCommand;
	this.debugTimesLine = debugRunningTimes;
	this.checkCommand = checkCommand;
	this.set = commandSet;
	this.command = commandCustom;
	this.analyse = analyseBFInner;
	this.run = autoRunInner;
	this.manualrun = runningBF;
}
