/* Ook!/Brainfuck interpreter. 
 * Library implementation for all browsers
 * 
 * Author: Arve Bersvendsen
 * Info:   http://virtuelvis.com/archives/2006/04/ookjaxfuck
 *
 * Loosely based on a Ook! implementation written in Python by
 * Øyvind Grønnesby ( http://www.pvv.ntnu.no/~oyving/code/python/pook.py )
 * 
 */
var OokJaXFuck = function(source,source_type){
  var memory = [];
  var mem_pointer = 0;
  var call_stack = [];
  var run_index = 0;
  var self = this;
  this.output = document.body;
  this.parse = function(){
    // reinitialize call_stack, memory, pointers and run_index;
    call_stack = [];
    memory = [];
    run_index = 0;
    mem_pointer = 0;
    if (!tokenMap[source_type]){
      throw source_type+": UNRECOGNIZED_LANGUAGE";
    }
    switch (source_type){
      case "ook":
        var retval = [];
        var tokenList = source.split(/\n|\s|;/);
        
        for (var i=0; token = tokenList[i++];){
          if (token.match(/Ook[.!?]{1}/)){ 
            retval[retval.length] = token;
          }
        }
        if (retval.length%2 != 0){
          throw source_type+": SYNTAX_ERR_ODD_NUM_TOKENS";
        } else {
          // Normalize the token_list into actual tokens
          for (var i = 0; i < retval.length-1;i+=2){
            call_stack[call_stack.length] = retval[i]+" "+retval[i+1];
          }
        }
        break;
      case "brainfuck":
        for (var i=0,token; token = source[i++];){
          if (token.match(/[+\-\[\]<>.,]{1}/)){ 
            call_stack[call_stack.length] = token;
          }
        }
        break;
      default:
        break;
    }
  }

  this.run = function(){
    run_index = 0;
    while (run_index < call_stack.length){
      tokenMap[source_type][call_stack[run_index]]();
      run_index++
    }
  }
  
  this.pointerUp = function(){
    mem_pointer++;
  }

  this.pointerDown = function(){
    mem_pointer--;
  }
  
  this.incPointer = function(){
    if (!memory[mem_pointer]) memory[mem_pointer]=0;
    memory[mem_pointer]++;
  }

  this.decPointer = function(){
    if (!memory[mem_pointer]) memory[mem_pointer]=0;
    memory[mem_pointer]--;
  }

  this.getChar = function(){
    var v = window.prompt("Input char","");
    memory[mem_pointer] = v?v.charCodeAt(0):10;
  }

  this.putChar = function(){
    output.textContent += String.fromCharCode((memory[mem_pointer]));
  }

  this.startConditional = function(){
    var nestCount = 0;
    var i = run_index;
    
    if (memory[mem_pointer] != 0){
      return;
    }

    while (true){
      i++;
      if (tokenMap[call_stack[i]] == self.startConditional){
          nestCount++;
      }
      if (tokenMap[call_stack[i]] == self.endConditional){
        if (nestCount == 0) {
          run_index++;
          break;
        } else {       
          nestCount--;
        }
      }
      if (i >= call_stack.length){
        throw source_type+": SYNTAX_ERR_UNMATCHED_START_BLOCK"
      }
    }
  }

  this.endConditional = function(){
    var nestCount = 0;
    var i = run_index;
    if (memory[mem_pointer] == 0 || memory[mem_pointer] == undefined) {
      return;
    }
    while (true){
      i--;
      if (tokenMap[source_type][call_stack[i]] == self.endConditional){
          nestCount++;
      }
      if (tokenMap[source_type][call_stack[i]] == self.startConditional){
        if (nestCount == 0) {
          run_index = i;
          break;
        } else {       
          nestCount--;
        }
      }
      if (i >= call_stack.length){
        throw source_type+": SYNTAX_ERR_UNMATCHED_END_BLOCK"
      }
    }
    
  }

  // Map the right tokens to the right methods.
  var tokenMap = [];
  tokenMap["brainfuck"]  = {
    '>' : this.pointerUp,
    '<' : this.pointerDown,
    '+' : this.incPointer,
    '-' : this.decPointer,
    ',' : this.getChar,
    '.' : this.putChar,
    '[' : this.startConditional,
    ']' : this.endConditional
  }
  tokenMap["ook"]  = {
    'Ook. Ook?' : this.pointerUp,
    'Ook? Ook.' : this.pointerDown,
    'Ook. Ook.' : this.incPointer,
    'Ook! Ook!' : this.decPointer,
    'Ook. Ook!' : this.getChar,
    'Ook! Ook.' : this.putChar,
    'Ook! Ook?' : this.startConditional,
    'Ook? Ook!' : this.endConditional
  }
  this.parse();
}
