/*
 * Elements with the following class names are automatically detected and decorated:
 *  -light_bot_board
 *    -reset([prog])
 *    -pause()
 *    -go(prog,freq)
 *    -step(prog)
 *  -light_bot_controls
 *    -Buttons are set to observe with the correct functions
 *    -If attributes "board" and "prog" are present, the controls are automatically connected
 *      to elements specified by those ids
 *  -light_bot_program
 *    -img elements decorated with "name" attribute
 */

function decorate(board){

  board.reset=function(prog){
    this.step_counter=0;
    this.pause();
    this.PC=0;
    this.stack=[];
    this.pos=eval(this.readAttribute("pos"));
    this.ori=eval(this.readAttribute("ori"));
    this.update_position();
    this.history=[];
    $A(this.getElementsByClassName("grid-lit")).each(function(el){el.removeClassName("grid-lit");});
    /* Not sure if this belongs here */
    prog.each(function(el){el.removeClassName("command-active")});
    prog[0].addClassName("command-active");
  }

  board.pause=function(){
    if(this.pe){
      this.pe.stop();
      this.pe=undefined;
    }
  }

  board.go=function(prog){
    this.pause();
    var selph=this;
    this.pe=new PeriodicalExecuter(function(){selph.step(prog)},this.delay);
  }

  board.turn_left=function(){
    var hsh=3*this.ori[0]+5*this.ori[1];
    if(hsh==3)this.ori=[0,1];
    else if(hsh==5)this.ori=[-1,0];
    else if(hsh==-3)this.ori=[0,-1];
    else if(hsh==-5)this.ori=[1,0];
  }

  board.back=function(prog){
    this.pause();
    if(this.step_counter==0)return;
    this.step_counter--;
    var his=this.history.pop();
    if(this.PC > -1)prog[this.PC].removeClassName("command-active");
    this.PC=his.PC;
    this.stack=his.stack;
    this.pos=his.pos;
    this.ori=his.ori;
    this.update_position();
    prog[this.PC].addClassName("command-active");
    if(prog[this.PC].name=="light")board.light(board.pos);
  }

  board.to_end=function(prog){
    this.pause();
    while(this.PC > -1)this.step(prog);
  }

  board.step=function(prog){
    if(this.PC==-1){this.pause();return;}
    this.last=(new Date()).getTime();
    this.history.push({PC: this.PC, stack: this.stack, pos:this.pos, ori:this.ori});
    this.step_counter++;
    prog[this.PC].removeClassName("command-active");
    var command=prog[this.PC].name;
    this.next_command(prog);
    if(this.PC>-1)prog[this.PC].addClassName("command-active");
    if(command=="left"){
      this.turn_left();
    }
    else if(command=="right"){
      this.turn_left();
      this.turn_left();
      this.turn_left();
    }
    else if(command=="forward"){
      var newPos=[this.pos[0]+this.ori[0],this.pos[1]+this.ori[1]];
      if(!(newPos[0]<0||newPos[1]<0||newPos[0]>7||newPos[1]>7||this.get(board.pos)!=this.get(newPos)))this.pos=newPos;
    }
    else if(command=="jump"){
      var newPos=[this.pos[0]+this.ori[0],this.pos[1]+this.ori[1]];
      if(newPos.all(function(x){return $R(0,7).include(x)}))
      {
        var diff=this.get(this.pos)-this.get(newPos); 
        if(diff==-1||diff > 0)this.pos=newPos;
      }
    }
    else if(command=="light")this.light(this.pos);
    this.update_position();
  }

  board.next_command=function(prog,popping){
    var command=prog[this.PC].name;
    if(command=="f1"&&!popping){
      this.stack.push(this.PC);
      this.PC=12;
    }
    else if(command=="f2"&&!popping){
      this.stack.push(this.PC);
      this.PC=20;
    }
    else if(this.PC==19||this.PC==27){
      this.PC=this.stack.pop();
      this.next_command(prog,true);
    }
    else if(this.PC==11){
      this.PC=-1;
    }
    else
      this.PC++;
  }

  var why_is_this_assignment_necessary=(["top","targets","pos","ori"]).each(function(a){board[a]=eval(board.readAttribute(a))});
  board.update_position=function(){
    var imgs=$A(board.getElementsByClassName("grid")).map(function(el){return el.childElements().first();});
    imgs.select(function(el){return el.src.match(/robot/);}).each(function(el){changeSource(el,"blank.png");});
    changeSource(imgs[this.pos[0]*8+this.pos[1]],"robotB"+this.ori[0]+""+this.ori[1]+".png");
  }
  board.light=function(pos){
    var el=$A(this.getElementsByClassName("grid"))[pos[0]*8+pos[1]];
    if(!el.hasClassName("grid-target"))return;
    el.toggleClassName("grid-lit");
  }
  board.get=function(a){
    return this.top[a[0]][a[1]];
  }
  board.delay=0.25;
  var editors=board.select(".light_bot_board_editor_controls");
  if(editors.length>0)
  {
    var edit=editors.first();
    $A(board.getElementsByClassName("grid")).each(function(td){
      td.select(".grid_editor_height").first().clonePosition(td.select("img").first(),{setWidth:false,setHeight:false});
      td.observe('click',function(ev){
        //var val=$A(edit.childElements().first().radio_editor).select(function(el){return el.checked}).first().value;
        var val=$A(edit.select("form").first().radio_editor).select(function(el){return el.checked}).first().value;
        var i=this.parentNode.childElements().indexOf(this);
        var j=this.parentNode.parentNode.childElements().indexOf(this.parentNode);
        if(val=="Raise"&&board.top[j][i]<64)board.top[j][i]++;
        else if(val=="Lower"&&board.top[j][i]>1)board.top[j][i]--;
        else if(val=="Target")this.toggleClassName("grid-target");
        else if(val=="Start"){
          if(board.pos[0]==j&&board.pos[1]==i){
            board.turn_left();
          }
          else
          {
            board.pos=[j,i];
          }
          board.update_position();
        }
        if(val=="Lower"||val=="Raise")editor_refresh_heights(board);
      });
    });
    editor_refresh_heights(board);
  }
}

function prepare_editor_submission(some_form){
  var base64="0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!@";
  var board =$(some_form).ancestors().select(function(el){return el.hasClassName("light_bot_board");}).first();
  var ret=board.top.map(function(a){return a.map(function(n){return base64.charAt(n-1);}).join("");}).join("");
  ret=[ret,board.pos[0],board.pos[1],board.ori[0],board.ori[1]].join(" ");
  var tds=board.getElementsByClassName("grid");
  for(var i=0;i<64;i++)if(tds[i].hasClassName("grid-target"))ret+=" "+Math.floor(i/8)+" "+i%8;
  return ret;
}

function editor_raise_all(board){
  $R(0,7).each(function(i){
    $R(0,7).each(function(j){
      var v=board.top[j][i];
      board.top[j][i]=Math.min(v+1,64);
    });
  });
  editor_refresh_heights(board);
}

function editor_lower_all(board){
  $R(0,7).each(function(i){
    $R(0,7).each(function(j){
      var v=board.top[j][i];
      board.top[j][i]=Math.max(v-1,1);
    });
  });
  editor_refresh_heights(board);
}

function editor_refresh_heights(board){
  var tds=board.select(".grid");
  for(var i=0;i<8;i++){
    for(var j=0;j<8;j++){
      var td=tds[j*8+i];
      v=board.top[j][i];
      td.className.split(" ").select(function(clz){
        if(clz.match(/light_bot_[lrtb](mid|((light|dark)(red)?))/))td.removeClassName(clz);
      });
      [[0,1,"right"],[1,0,"bottom"],[-1,0,"top"],[0,-1,"left"]].each(function(a){
        var jj=a[0];
        var ii=a[1];
        var s=a[2];
        var color="mid";
        if(!($R(0,7).include(ii+i)&&$R(0,7).include(jj+j))){
          if(v>1)color="high";
        }
        else{
          if(board.top[j+jj][i+ii]>v)color="low";
          if(board.top[j+jj][i+ii]>v+1)color="verylow";
          if(board.top[j+jj][i+ii]<v)color="high";
        }
        if((color=="high"&&ii+jj==1)||(color=="low"&&ii+jj==-1))color="dark";
        if((color=="low"&&ii+jj==1)||(color=="high"&&ii+jj==-1))color="light";
        if(color=="verylow"){
          if(ii+jj==1)color="lightred";
          else color="darkred";
        }
        td.addClassName("light_bot_"+s.substring(0,1)+color);

      });  
      td.select(".grid_editor_height").first().innerHTML=board.top[j][i];
    }
  }
}

function changeSource(el, filename){
  var s=el.src+"";
  el.src=s.replace(/\/[\w\.\-]+\??\d*$/,"/"+filename);
}

function decorate_prog(el){
  var re=/\/(\w+)\.png/;
  var edit=false;
  if(el.readAttribute("editable"))edit=true;
  var count=0;
  $A(el.getElementsByClassName("command")).each(function(img){
    img.name=re.exec(img.src)[1];
    count++;
    if(edit){
      img.index=count;
      Droppables.add(img,{hoverclass: "command-hover", onDrop:
        function(drag,drop){
          if((drop.index > 12 && drag.name == "f1") || (drop.index > 20 && drag.name=="f2"))return;
          drop.name=drag.name;
          drop.src=drag.src;
          interact.board.reset(interact.prog);
        }});
    }
  });
}

document.observe("dom:loaded",function(){
  $$(".light_bot_board").select(function(el){return !el.hasClassName("light_bot_small_board");}).each(function(el){decorate(el);});
  $$(".light_bot_program").each(function(el){decorate_prog(el)});
  $$(".light_bot_controls").each(function(el){
    var board=el.readAttribute("board");
    var prog=el.readAttribute("prog");
    if(board && prog)
    {
      el.board=$(board);el.prog=$A($(prog).getElementsByClassName("command"));
      el.board.reset(el.prog);
    }
    el.getElementsByClassName("backback")[0].observe("click",function(){el.board.reset(el.prog)});
    el.getElementsByClassName("back")[0].observe("click",function(){el.board.back(el.prog)});
    el.getElementsByClassName("play")[0].observe("click",function(){el.board.go(el.prog)});
    el.getElementsByClassName("pause")[0].observe("click",function(){el.board.pause()});
    el.getElementsByClassName("forward")[0].observe("click",function(){el.board.step(el.prog)});
    el.getElementsByClassName("forwardforward")[0].observe("click",function(){el.board.to_end(el.prog)});
    $A(el.getElementsByClassName("light_bot_control_button")).each(function(but){
      but.observe("mousedown",function(){this.addClassName("light_bot_control_button_down");});
      but.observe("mouseup",function(){this.removeClassName("light_bot_control_button_down");});
    });
    var slider=$A(el.getElementsByClassName("light_bot_control_slider")).first();
    var handle=$A(el.getElementsByClassName("light_bot_control_handle")).first();
    new Control.Slider(handle,slider,{
        sliderValue: 0.5,
        onSlide: function(v){
          var del=-2*Math.pow(v-1,3);
          if(el.board.delay==del)return;
          el.board.delay=del;
          if(el.board.pe){
            el.board.pause();
            var wait=[0,del-((new Date).getTime() - el.board.last)/1000].max();
            el.board.pe=new PeriodicalExecuter(function(){
              el.board.pause();
              el.board.step(el.prog);
              el.board.go(el.prog);
            },wait);
          }
        }
      });
  });
  com_map={"f":"forward","l":"left","r":"right","x":"light","j":"jump","n":"noop","1":"f1","2":"f2"};
  $$(".light_bot_snapshot").each(function(tr){
    tr.observe("click",function(){
      $$(".light_bot_snapshot_selected").first().removeClassName("light_bot_snapshot_selected");
      tr.addClassName("light_bot_snapshot_selected");
      var coms=tr.readAttribute("value").split("").map(function(ch){return com_map[ch]});
      var imgs=$("prog_div").select(".command");
      for(var i=0;i<28;i++){
        imgs[i].src=imgs[i].src.sub(/\b\w+(?=\.png)/,coms[i]);
        imgs[i].name=coms[i];
      };
      $("board_div").reset(imgs);
      Effect.ScrollTo("board_div");
    });
    tr.observe("mouseover",function(){tr.addClassName("light_bot_snapshot_hover");});
    tr.observe("mouseout",function(){tr.removeClassName("light_bot_snapshot_hover");});
  });
});


/* INTERACTIVE LIGHT_BOT */

var interact={};

document.observe("dom:loaded",function(){
  lbep=$("light_bot_editor_palette");
  if(!lbep)return;
  decorate_prog(lbep);
  $A(lbep.getElementsByClassName("command")).each(function(but){
    new Draggable(but,{revert:true, ghosting:true});
  });
  interact.board=$("board_div");
  interact.prog=$A($("prog_div").getElementsByClassName("command"));
});

function rand(n){
  return Math.floor(Math.random()*n);
}

function get_command(name){
  var ret=document.createElement("img");
  ret.addClassName("command");
  ret.src=image_path.gsub("noop",name);
  return ret;
}

function get_random_program(){
  var ret=$(document.createElement("div"));
  ret.addClassName("light_bot_active_program");
  var commands=["left","right","forward","noop","light","jump","f2","f1"];
  var arr=new Array();
  for(var i=0;i<12;i++){
    arr[i]=rand(8);
    ret.appendChild(get_command(commands[arr[i]]));
  }
  for(var i=12;i<20;i++){
    arr[i]=rand(7);
    ret.appendChild(get_command(commands[arr[i]]));
  }
  for(var i=20;i<28;i++){
    arr[i]=rand(6);
    ret.appendChild(get_command(commands[arr[i]]));
  }
  ret.prog=arr;
  decorate_active_prog(ret);
  ret.value=evaluate_prog($("board_div"),ret);
  ret.appendChild(Builder.node("span",{className:"light_bot_active_prog_value"},""+ret.value[1]+" ("+ret.value[2]+")"));
  return ret;
}

var alg_params={};

function setup_alg(){
  //Get params from form
  var or_zero=function(s){var i=parseInt(s);if(i)return i;return 0;};
  alg_params.pop_size=or_zero($("param_pop_size").value);
  alg_params.duplicates=$("param_duplicates").checked;
  alg_params.protect_leader=$("param_protect_leader").checked;
  alg_params.recombine=or_zero($("param_recombine").value)-1;
  alg_params.mutate=or_zero($("param_mutation_width").value);
  alg_params.programs=[];
  alg_params.total=0;
  
  var popdiv=$("population");
  $A(popdiv.childElements()).each(function(el){el.remove()});
  var elems=[];
  for(var i=0;i<alg_params.pop_size;i++){
    var prog=get_random_program();
    elems.push(prog);
  }
  elems=$A(elems).sortBy(function(el){return el.value;}).reverse();
  elems.each(function(el){
    popdiv.appendChild(el);
    alg_params.programs.push(el);
    alg_params.total+=el.value.first();
  });
  $("active_controls").toggle();
  $("parameter_form").toggle();
  (function(){Effect.ScrollTo("gen_head");}).defer();
  alg_params.step=active_step_recombine;
  $("generations").innerHTML="1";
  $("osl_generation").show();
  $("osl_mutation").hide();
  $("osl_recombination").hide();
}

function decorate_active_prog(prog){
  prog.observe("click",function(){
    var re=/\/(\w+)\.png/;
    var imgs_src=prog.getElementsByClassName("command");
    var imgs_to=$("prog_div").getElementsByClassName("command");
    for(var i=0;i<28;i++){
      imgs_to[i].src=imgs_src[i].src;
      imgs_to[i].name=re.exec(imgs_src[i].src)[1];
    }
    $("board_div").reset($A(imgs_to));
    Effect.ScrollTo("board_div");
  });
}

function decorate_targets(board){
  board.targets=new Array();
  var sqs=board.getElementsByClassName("grid");
  for(var i=0;i<8;i++)
    for(var j=0;j<8;j++)
      if(sqs[j*8+i].hasClassName("grid-target"))
        board.targets.push([j,i]);
}
  
function evaluate_prog(board,progarg){
  var map={left:0,right:1,forward:2,noop:3,light:4,jump:5,f2:6,f1:7};
  var prog=progarg.prog;
  if(!board.targets)decorate_targets(board);
  state={turn_left:board.turn_left,pos:eval(board.readAttribute("pos")),ori:eval(board.readAttribute("ori")),targets:new Object()};
  board.targets.each(function(t){state.targets[t]=false});
  var main=prog.slice(0,12);
  var f1=prog.slice(12,20);
  var f2=prog.slice(20);
  var rr=$R(0,7);
  var do_commands=function(commands){
    commands.each(function(command){
      if(command==3)return;
      if(command==4){
        if(state.targets[state.pos]!=undefined)state.targets[state.pos]=!state.targets[state.pos];
        return;
      }
      if(command==5||command==2){
        newPos=[state.pos[0]+state.ori[0],state.pos[1]+state.ori[1]];
        if(newPos.any(function(i){return !rr.include(i)}))return;
        var diff=board.get(newPos)-board.get(state.pos);
        if(command==5&&(diff > 1 || diff ==0))return;
        if(command==2&&diff!=0)return;
        state.pos=newPos;
        return;
      }
      if(command==1){
        state.turn_left();state.turn_left();state.turn_left();
        return;
      }
      if(command==0){
        state.turn_left();
        return;
      }
      if(command==6)do_commands(f2);
      else if(command==7)do_commands(f1);
    });
  }
  do_commands(main);
  var total=lit=0;
  Object.values(state.targets).each(function(v){total++;if(v)lit++;});
  /* Compute total commands */
  var f2comm=f1comm=maincomm=0;
  f2.each(function(n){if(n<6 && n != 3)f2comm++;});
  f1.each(function(n){if(n<6 && n != 3)f1comm++; else if(n==6)f1comm+=f2comm;});
  main.each(function(n){if(n<6 && n !=3)maincomm++; else if(n==6)maincomm+=f2comm; else if(n==7)maincomm+=f1comm;});
  var val = lit+1;
  if(val==total+1)val+=((768-maincomm)/total);
  return [val,lit,maincomm];
  //return lit/total;
}

function active_reset(){
  $("active_controls").toggle();
  $("parameter_form").toggle();
  $("offspring_label").siblings().each(function(el){el.remove()});
}

function active_step_recombine(){
  $("osl_generation").hide();
  /* TODO *** alg_params.total never gets updated!! */
  /* quick fix - compute manually each time */
  var tot=alg_params.total;
  var tot=alg_params.programs.inject(0, function(acc,proggy){return acc+proggy.value[0];});
  var i=0;
  var sel=Math.random()*tot;
  while(i<alg_params.programs.length && sel > alg_params.programs[i].value[0]){
    sel=sel-alg_params.programs[i].value[0];
    i++;
  }
  if(i>=alg_params.programs.length)i=alg_params.programs.length-1;
  var mother=alg_params.programs[i];
  var motheri=i;
  tot=tot-mother.value[0];
  sel=Math.random()*tot;
  i=0;if(motheri==0)i=1;
  while(i<alg_params.programs.length && sel > alg_params.programs[i].value[0]){
    sel=sel-alg_params.programs[i].value[0];
    i++;
    if(i==motheri)i++;
  }
  if(i>=alg_params.programs.length)i=alg_params.programs.length-1;
  var father=alg_params.programs[i];
  var fatheri=i;

  father.addClassName("light_bot_active_prog_highlighted");
  mother.addClassName("light_bot_active_prog_highlighted");

  /* Randomly choose slice points */

  cuts=new Array(27);
  for(var i=0;i<alg_params.recombine;i++){
    s=Math.floor(Math.random()*(27-i));
    var j=0;
    while(s>0||cuts[j]){
      if(!cuts[j++])s--;
    }
    cuts[j]=true;
  }

  /* Assemble new program */

  var coms=new Array();
  var parents=[mother,father].map(function(el){return el.getElementsByClassName("command")});
  var p=0;
  var progArr=[];
  for(var i=0;i<28;i++)
  {
    coms.push(Builder.node("img",{className:"command",src:parents[p][i].src}));
    progArr[i]=[mother,father][p].prog[i];
    parents[p^1][i].setOpacity(0.35);
    if(cuts[i])p^=1;
  }
  /*
  for(var i=0;i<alg_params.programs.length;i++)
    if(i!=motheri&&i!=fatheri)
      alg_params.programs[i].setOpacity(0.35);
  */
  
  var offspring=Builder.node("div",{className:"light_bot_active_program"},coms);
  offspring.prog=progArr;
  offspring.value=evaluate_prog($("board_div"),offspring);
  offspring.appendChild(Builder.node("span",{className:"light_bot_active_prog_value"},""+offspring.value[1]+" ("+offspring.value[2]+")"));
  decorate_active_prog(offspring);
  $("osl_recombination").show();
  var props = {style: "text-align:center;font-size:x-large;margin:0px;padding:0px;font-weight:bold;"};
  var the_display=Builder.node("div",
        [clone_program(father),
         Builder.node("div",props,"+"),
         clone_program(mother),
         Builder.node("div",props,"="),
         offspring]);
  $("offspring").appendChild(the_display);

  /* Next step */
  alg_params.step=function(){
    the_display.remove();
    offspring.removeClassName("light_bot_active_prog_improved");
    $("osl_recombination").hide();
    alg_params.programs.pop().remove();

    mother.select(".command").each(function(el){el.setOpacity(1);});
    father.select(".command").each(function(el){el.setOpacity(1);});
    /*
    for(var i=0;i<alg_params.programs.length;i++)
      if(i!=motheri&&i!=fatheri)
        alg_params.programs[i].setOpacity(1);
      else
        $A(alg_params.programs[i].getElementsByClassName("command")).each(function(el){el.setOpacity(1);});
    */
    offspring.remove();
    insert(offspring);
    mother.removeClassName("light_bot_active_prog_highlighted");
    father.removeClassName("light_bot_active_prog_highlighted");
    alg_params.step=active_step_mutate;
    return alg_params.step();
  }
  
  if(offspring.value[0] > alg_params.programs.first().value[0]){
    offspring.addClassName("light_bot_active_prog_improved");
    return offspring.value[0];
  }
}

function active_step_mutate(){
  var prevBest=alg_params.programs.first().value[0];
  if(alg_params.protect_leader)
    var chosei=1+Math.floor(Math.random()*(alg_params.programs.length-1));
  else
    var chosei=Math.floor(Math.random()*alg_params.programs.length);
  var chosen=alg_params.programs[chosei];
  var where=Math.floor(Math.random()*(28-alg_params.mutate));
  var comms=chosen.select(".command");
  var commands=["left","right","forward","noop","light","jump","f2","f1"];
  var re=/\b(\w+)(?=\.png)/;
  var valspan=$A(chosen.childElements()).last();
  
  var show_old=function(){
    for(var j=where;j<where+alg_params.mutate;j++)
      comms[j].src=comms[j].old_src;
    valspan.innerHTML=chosen.old_val[1]+" ("+chosen.old_val[2]+")";
  }
  var show_new=function(){
    for(var j=where;j<where+alg_params.mutate;j++)
      comms[j].src=comms[j].new_src;
    valspan.innerHTML=chosen.value[1]+" ("+chosen.value[2]+")";
  }

  var before=clone_program(chosen);
  var before_comms=before.select(".command");

  for(var j=where;j<where+alg_params.mutate;j++){
    comms[j].old_src=comms[j].src;
    var avail=8;
    if(j>11)avail--;
    if(j>19)avail--;
    var r=Math.floor(Math.random()*avail);
    chosen.prog[j]=r;
    comms[j].src=comms[j].src.gsub(re,commands[r]);
    comms[j].observe('mouseover',show_old);
    comms[j].observe('mouseout',show_new);
    comms[j].new_src=comms[j].src;
  }

  /*
  for(var i=0;i<alg_params.programs.length;i++)
    if(chosei!=i)
      alg_params.programs[i].setOpacity(0.35);
  */
  for(var i=0;i<28;i++)
    if(i<where || i >= where+alg_params.mutate)
    {
      comms[i].setOpacity(0.35);
      before_comms[i].setOpacity(0.35);
    }

  chosen.old_val=chosen.value;
  chosen.value=evaluate_prog($("board_div"),chosen);
  show_new();

  /* Display */
  chosen.remove();
  $("osl_mutation").show();
  var the_display=Builder.node("div",
      [
        Builder.node("span","Before:"),
        before,
        Builder.node("span","After:"),
        chosen]);
  $("offspring").appendChild(the_display);
  alg_params.programs=alg_params.programs.slice(0,chosei).concat(alg_params.programs.slice(chosei+1));


  alg_params.step=function(){
    the_display.remove();
    $("osl_mutation").hide();
    $("osl_generation").show();
    show_new();
    /*
    for(var i=0;i<alg_params.programs.length;i++)
      alg_params.programs[i].setOpacity(1);
    */
    for(var i=0;i<28;i++){
      if(i<where || i >= where+alg_params.mutate)
        comms[i].setOpacity(1);
      else{
        comms[i].stopObserving('mouseover',show_old);
        comms[i].stopObserving('mouseout',show_new);
      }
    }
    generation_tick();
  
    insert(chosen);
    /*
    if( (chosei < alg_params.programs.length -1 && chosen.value[0] < alg_params.programs[chosei+1].value[0]) ||
        (chosei > 0 && chosen.value[0] > alg_params.programs[chosei-1].value[0])){
      chosen.remove();
      alg_params.programs=alg_params.programs.slice(0,chosei).concat(alg_params.programs.slice(chosei+1));
      insert(chosen);
    }
    */
    chosen.removeClassName("light_bot_active_prog_improved");
    alg_params.step=active_step_recombine;
  };

  if(chosen.value[0] > prevBest){
    chosen.addClassName("light_bot_active_prog_improved");
    return chosen.value[0];
  }
}

function generation_tick(){
  var g=$("generations");
  g.innerHTML=1+parseInt(g.innerHTML);
}

/* 
 * Inserts the supplied program-div into the #population container and the alg_params.programs array,
 * checking for duplicates if specified by alg_params.duplicates
 */

function insert(prog){
  var i=0;
  while(i < alg_params.programs.length && alg_params.programs[i].value[0] > prog.value[0])i++;
  if(i==alg_params.programs.length){
    alg_params.programs.push(prog);
    $("population").appendChild(prog);
  } else {
    /* First insert */
    $("population").insertBefore(prog,alg_params.programs[i]);
    alg_params.programs = [alg_params.programs.slice(0,i),prog,alg_params.programs.slice(i)].flatten();

    /* Then check duplicates */
    if(!alg_params.duplicates){
      i++;
      while(i < alg_params.programs.length && alg_params.programs[i].value[0] == prog.value[0]){
        if($R(0,27).all(function(j){return prog.prog[j]==alg_params.programs[i].prog[j];})){
          alg_params.programs[i].remove();
          alg_params.programs=alg_params.programs.slice(0,i).concat(alg_params.programs.slice(i+1));
        } else i++;
      }
      while(alg_params.programs.length < alg_params.pop_size)
        insert(get_random_program());
    }
  }
}

function lbi_init(){
  var play=$("lbi_control_play");
  var pause=$("lbi_control_pause");
  var forward=$("lbi_control_forward");
  var reset_but=$("lbi_control_reset");
  var playing=false;
  pause.hide();
  [play,pause,forward].each(function(but){
    but.observe("mousedown",function(){but.addClassName("light_bot_control_button_down")});
    but.observe("mouseup",function(){but.removeClassName("light_bot_control_button_down")});
  });

  var goal=$("board_div").targets.length+1;
  var highest=0;
  var reset=false;
  var solution_yet=false;
  var run=function(){
    var improved=alg_params.step();
    var lbi_mode=get_lbi_mode();
    var improvement=(lbi_mode=="improvement");
    var solution=(lbi_mode=="solution");
    var should_stop=function(){
      if(improvement && improved && improved > highest)return true;
      if(solution && !solution_yet && improved && improved >= goal)return true;
    }
    var res=false;
    while(alg_params.step!=active_step_recombine && !(res=should_stop()))improved=alg_params.step();
    if(playing && !res){
      run.delay(0.1);
    }
    else if(reset){
      reset=false;
      active_reset();
      play.show();
      pause.hide();
      forward.setOpacity(1);
    }
    if(res){
      play.show();
      pause.hide();
      playing=false;
      forward.setOpacity(1);
    }
  }

  reset_but.observe("click",
    function(){
      if(playing){
        playing=false;
        reset=true;
      } else {
        active_reset();
      }
  });

  play.observe("click",function(){
    play.hide();
    pause.show();
    forward.setOpacity(.25);
    playing=true;
    highest=alg_params.programs.first().value[0];
    solution_yet = (highest >= goal);
    run();
  }); 
  pause.observe("click",function(){
    play.show();
    pause.hide();
    forward.setOpacity(1);
    playing=false;
  });
  forward.observe("click",function(){
    if(!playing)alg_params.step();
  });
}

function get_lbi_mode(){
  return $A(document.forms.lbi_mode_form.lbi_mode).select(function(el){return el.checked;}).first().value;
}

function clone_program(proggie){
  var ret=document.createElement("div");
  ret.className="light_bot_active_program";
  ret.innerHTML=proggie.innerHTML;
  decorate_active_prog(ret);
  return ret;
}
