// recursive animated javascript towers of hanoi
// (C)opyright 2007 mike@mccreavy.com

var plateColor = 
 [
  '#00FF00',
  '#00EE00',
  '#00DD00',
  '#00CC00',
  '#00BB00',
  '#00AA00',
  '#009900',
  '#008800',
  '#007700',
  '#006600'
  ];

function TowerModel(a)
{
  this.hanoi = a.hanoi;
  this.name = a.name;
  this.plate = {};
}

TowerModel.prototype.dump =
function towermodel_dump()
{
  dump("Tower: " + this.name + "\n");
  // dump("Hanoi: " + this.hanoi + "\n");

  var c = 0;
  for (var i in this.plate) {
    c++;
  }

  var op = this.orderedPlates();

  dump("Plates: " + op + "\n");
}

TowerModel.prototype.orderedPlates =
function towermodel_orderedPlates()
{
  var l = [];

  for (var i in this.plate)
    l.push(this.plate[i].name);

  return l.sort(function(a,b) { return a < b ? -1 : a > b ? 1 : 0; });
}

/* Return the offset to the top of this tower (for animating moves). */
TowerModel.prototype.topOffset =
function towermodel_topOffset()
{
  return this.orderedPlates().length;
}


TowerModel.prototype.plateBottomOffset =
function towermodel_plateBottomOffset(plateName)
{
  var f = 0;
  var d = 0;

  //dump("looking for plate " + plateName + " in tower " + this.name + "\n");

  //dump("plates: " + this.orderedPlates() + "\n");

  var o = this.orderedPlates();
  for (var i = 0 ; i < o.length ; i++)
  {
    if (plateName == o[i])
      f = 1;
    if (f)
      d++;
  }

  //dump("its offset " + d + "\n")

  return d;
}

TowerModel.prototype.clearPlate = 
function towermodel_clearPlate(p) {

  //dump("deleting " + p.name + " from tower " + this.name + "\n");

  delete this.plate[p.name];
}

TowerModel.prototype.takePlate = 
function towermodel_takeplate(p) {
  this.plate[p.name] = p;
}

TowerModel.prototype.canTakePlate =
function towermodel_canTakePlate(p) {
  var l = this.orderedPlates();

  //dump("Seeing if tower " + this.name + " can take plate " + p.name + "\n");
  return l.length == 0 || p.width <= this.plate[l[0]].width;
}
    
function PlateModel(a)
{
  this.hanoi = a.hanoi;
  this.name = a.name;
  this.width = a.name;
  this.setTower(a.tower);
}

PlateModel.prototype.dump =
function platemodel_dump()
{
  dump("Plate: " + this.name + "\n");
}

PlateModel.prototype.onTop =
function platemodel_onTop() {
  var v = (!this.tower) || (this.name == this.tower.orderedPlates()[0]);
  //dump("Checking if plate " + this.name + " is on top of tower " + this.tower.name + ": " + v + "\n");
  return v;
}

PlateModel.prototype.setTower =
function platemodel_setTower(tower)
{
  if (this.tower)
    this.tower.clearPlate(this);

  //dump("Setting plate " + this.name + " to tower " + tower.name + "\n");
  this.tower = tower;

  if (this.tower)
    tower.takePlate(this);
}

function HanoiModel(a)
{
  a.towers = a.towers || 3;
  a.plates = a.plates || 5;

  this.tower = [];
  for (var i = 1 ; i <= a.towers ; i++)
  {
    var ta = {
      hanoi: this,
      name: i,
      platespace: a.plates
    };

    this.tower.push(new TowerModel(ta));
  }

  this.plate = [];
  for (var i = 1 ; i <= a.plates ; i++)
  {
    var pa = { 
      hanoi: this, 
      name: i,
      tower: this.tower[Math.floor(Math.random() * this.tower.length)]
    };

    this.plate.push(new PlateModel(pa));
  }
}

HanoiModel.prototype.dump =
function hanoi_dump()
{
  dump("**** Dumping Model ****\n");
  dump("towers: " + this.tower.length + "\n");
  for (var t in this.tower) {
    this.tower[t].dump();
  }
}

HanoiModel.prototype.towerHash =
function hanoi_towerHash()
{
  var h = {};
  for (var t in this.tower) {
    //dump("Adding tower " + this.tower[t].name + " to towerHash\n");
    h[this.tower[t].name] = this.tower[t];
  }
  //dump("Returning: " + h + "\n");
  return h;
}

HanoiModel.prototype.solve =
function hanoi_solve(a, targetTower, currentPlateIndex)
{
  if (typeof currentPlateIndex == "undefined")
  {
    currentPlateIndex = this.plate.length - 1;
  }

  //dump("\n\n\n\n\n\n\nInspecting situation: target is: " + targetTower.name + "\n");
  //dump("Set of plates is from " + currentPlateIndex + " to zero\n");
  //this.dump();

  for (var i = currentPlateIndex ; i >= 0 ; i--)
    {
      //dump("Plate " + this.plate[i].name + " is on tower " + this.plate[i].tower.name + "\n");
      if (this.plate[i].tower == this.targetTower)
        {
          //dump("Cool, its in place -- continue to next guy.\n");
        }
      else
        {
          //dump("Desired move: plate " + this.plate[i].name + " to tower " + targetTower.name +"\n");


          if (!this.plate[i].onTop() || !targetTower.canTakePlate(this.plate[i]))
          {
            //dump("We need to move plates [" + (this.plate[i-1].name) + "...] to a tower other than " +
            //     targetTower.name + " and " + this.plate[i].tower.name + "\n");


            var towerHash = this.towerHash();
            delete towerHash[targetTower.name];
            delete towerHash[this.plate[i].tower.name];

            var subTargetTower;

            if (this.plate[i-1].tower.name in towerHash)
            {
              //dump("Using existing tower " + this.plate[i-1].tower.name + " since plate " +
              //     this.plate[i-1].name + " is there");
              subTargetTower = this.plate[i-1].tower;
            }
            else
            {
              for (var l in towerHash)
              {
                subTargetTower = towerHash[l];
                break;
              }
              //dump("Using tower " + subTargetTower.name + " for subsolution\n");
            }

            //dump("Going to solve for i-1: " + (i-1) + "\n");
            var aprime = a;
            this.solve(a, subTargetTower, i-1);
          }

          //dump("Now moving plate " + this.plate[i].name + " to tower " + targetTower.name + "\n");
          if (a.movecallback) { a.movecallback(this.plate[i].tower, this.plate[i], targetTower) };
          this.plate[i].setTower(targetTower);
        }
    }
}

function HanoiView(domParent, model, a)
{
  this.parent = domParent;
  this.model = model;
  
  if (typeof a == 'undefined')
    a = {};
  if (typeof a.plateHeight == 'undefined')
    a.plateHeight = 10;
  if (typeof a.towerWidth == 'undefined')
    a.towerWidth = 10;

  this.towerDom = {};
  for (var i = 0 ; i < model.tower.length ; i++)
  {
    var d = document.createElement('div');
    d.style.width = a.towerWidth + 'px';
    d.style.height = a.plateHeight * (model.plate.length + 1) + 'px';
    d.style.overflow = 'hidden';
    d.style.backgroundColor = '#004400';
    d.style.position = 'absolute';
    d.style.top = '20px';
    d.style.left = ((i+1) * 200) + 'px';
    this.towerDom[model.tower[i].name] = d;
    this.parent.appendChild(d);
    var b = document.createElement('div');
    b.style.width = a.towerWidth*15 + 'px';
    b.style.height = '1px';
    b.style.overflow = 'hidden';
    b.style.backgroundColor = '#004400';
    b.style.position = 'absolute';
    b.style.top = parseInt(d.style.top) + parseInt(d.style.height) + 1 + 'px';
    b.style.left = ((i+1) * 200) + parseInt(d.style.width)/2 - (parseInt(b.style.width)/2) + 'px';
    this.parent.appendChild(b);
                    

  }

  this.plateDom = {};
  for (var i = 0 ; i < model.plate.length ; i++)
  {
    var d = document.createElement('div');
    d.style.width = (20 * model.plate[i].width) + "px";
    d.style.height = a.plateHeight + 'px';
    d.style.overflow = 'hidden';
    d.style.margin = '1px';
    d.style.borderTop = 'solid black 1px';
    d.style.backgroundColor = plateColor[i%plateColor.length];
    d.style.position = 'absolute';

    var towerDom = this.getTowerDom(model.plate[i].tower.name);

    d.style.left = (parseInt(towerDom.style.left) + (parseInt(towerDom.style.width)/2) - parseInt(d.style.width)/2) + "px";

    var towerBase = parseInt(towerDom.style.top) + parseInt(towerDom.style.height);
    
    // need to find how far from the bottom this piece is.
    
    d.style.top =  towerBase - model.plate[i].tower.plateBottomOffset(model.plate[i].name) * parseInt(d.style.height) + "px";
    this.plateDom[model.plate[i].name] = d;
    this.parent.appendChild(d);
  }
}

HanoiView.prototype.getTowerDom =
function hanoiview_getTowerDom(name)
{
  return this.towerDom[name];
}

HanoiView.prototype.getPlateDom =
function hanoiview_getPlateDom(name)
{
  return this.plateDom[name];
}

HanoiView.prototype.updateDom =
function hanoiview_updateDom(model)
{
  for (var i = 0 ; i < model.tower.length ; i++)
  {
    var d = this.getTowerDom(model.tower[i].name);
    d.style.top = '20px';
    d.style.left = ((i+1) * 200) + 'px';
  }

  for (var i = 0 ; i < model.plate.length ; i++)
  {

    if (model.plate[i].tower)
    {
      var d = this.getPlateDom(model.plate[i].name);
      var towerDom = this.getTowerDom(model.plate[i].tower.name);
      var towerBase = parseInt(towerDom.style.top) + parseInt(towerDom.style.height);
      d.style.left = (parseInt(towerDom.style.left) + (parseInt(towerDom.style.width)/2) - parseInt(d.style.width)/2) + "px";
      d.style.top =  towerBase - model.plate[i].tower.plateBottomOffset(model.plate[i].name) * parseInt(d.style.height) + "px";
    }
  }
}

HanoiView.prototype.animateMove =
function hanoiview_animate(cycle, steps, plate, from, target, callback)
{
  var self = this;

  var f = function() {
    if (++cycle > steps)
      {
        callback();
        return;
      }

    var fd = self.getTowerDom(from.name);
    var fo = from.topOffset() + 1;
    var td = self.getTowerDom(target.name);
    var to = target.topOffset() + 1;
    var pd = self.getPlateDom(plate.name);

    var fx = parseInt(fd.style.left) + parseInt(fd.style.width) / 2;
    var fy = parseInt(fd.style.top) + parseInt(fd.style.height) - parseInt(pd.style.height) * fo;

    var tx = parseInt(td.style.left) + parseInt(td.style.width) / 2;
    var ty = parseInt(td.style.top) + parseInt(td.style.height) - parseInt(pd.style.height) * to;


    var cx = fx + ((tx - fx) * (cycle / steps));
    var cy = fy + ((ty - fy) * (cycle / steps));

    pd.style.left = cx - parseInt(pd.style.width)/2 + "px";
    pd.style.top = cy - 50*Math.sin(3.14*(cycle/steps)) + "px";

    setTimeout(f, 20);
  };

  setTimeout(f, 20);

  //  this.updateDom(this.model);
  // callback();
}

HanoiView.prototype.animateFinish =
function hanoiview_animatefinish(cycle, steps, callback)
{
  var self = this;
  var saved;
  var savedColor;
  var f = function() {
    if (saved)
      {
      saved.style.backgroundColor = savedColor;
      }

    if (++cycle >= steps)
      {
        callback();
        return;
      }

    saved = self.getPlateDom(self.model.plate[cycle%self.model.plate.length].name);
    savedColor = saved.style.backgroundColor;
    saved.style.backgroundColor = '#FFFF00';
    
    setTimeout(f, 100);
  }
  setTimeout(f, 20);
}
