var w=54;
var h=54;

var ncols=7;
var nrows=7;

var firstrow=0;
var firstcol=0;

var rows=[];
var rowsT=[];


var tilesNode=document.getElementById('tiles');  

var above=null;
var choice;

var offsetx=0;
var offsety=0;
var offsettx=0;
var offsetty=0;

var iterationsper=1000;


var traces=[];





function trace(text) {
  var tracer=document.getElementById('trace');

  traces.push(text);
  while(traces.length > 20) traces.shift();
  tracer.innerHTML=traces.join('<br>');
}

function traceA(text) {
  if(!traces.length) trace('');
  traces[traces.length-1]+=text;
  var tracer=document.getElementById('trace');
  tracer.innerHTML=traces.join('<br>');
}

Array.prototype.mshift=function() {
  var tmp=this[0];
  for(var i=1;i<this.length;i++) this[i-1]=this[i];
  this.pop();
  return tmp;
}

Array.prototype.shuffle=function() {
  for(var i=0;i<this.length;i++) this.swap(i,Math.floor(Math.random()*this.length));
}

Array.prototype.swap=function(a,b) {
  if(a==b) return;
  var temp=this[a];
  this[a]=this[b];
  this[b]=temp;
}

var cache={};
var stats={};

var leadingEdge=true;

function getTile(edges) {
  var hash=(edges.top?edges.top:'_')+(edges.right?edges.right:'_')+(edges.bottom?edges.bottom:'_')+(edges.left?edges.left:' ');
  var matches;
  
  if(matches=cache[hash]) { return matches; }
  tiles.shuffle();
  matches=[];
  matches.index={};
  var counter=0;
  for(var i=0;i<tiles.length;i++) {
    var match=true;
    for(var j in edges) {
      if(!edges[j]) continue;
      if(tiles[i][j] != edges[j]) { match=false; break; }
    }
    if(match) {      
      var item={tile:tiles[i],failcount:0,toString:function() {return this.failcount}};
      matches.index[item.tile.imageclass]=counter;
      matches[counter]=item;
      counter++;
    }
  }
  matches.hash=hash;

  matches.sortme=function() {
    matches.sort(function(a,b) {
      return a.failcount>b.failcount?1:a.failcount==b.failcount?0:-1;
    });
    for(var i=0;i<matches.length;i++) matches.index[matches[i].tile]=i;
  };
  
  
  return cache[hash]=matches;  
}

function putTile(cell,edges) {
  ops++;  
//  if(cell.tile && cell.matches.length) fail(cell);
  
  
  if(cell.nomatch) return false;
  if(!cell.matches.length) {
    cell.matches=getTile(edges);
    cell.hash=cell.matches.hash;
    if(!cell.matches.length) return false;
    cell.choiceptr=0;
         
    //cell.matches.sortme();
    
  }
  cell.tile=cell.matches[cell.choiceptr++].tile;
  if(cell.matches.length==cell.choiceptr) cell.nomatch=true;
  return true;
}



function fail(cell) {
  var idx=cache[cell.hash].index[cell.tile.imageclass];
  //trace('fail at cell='+cell.i+','+cell.j);
  //trace('marking fail for '+idx+'/'+cache[cell.hash].length+' indexed by '+cell.tile.imageclass);
  cache[cell.hash][idx].failcount++;

  //trace('fail counts='+cell.matches.join(' '));
  
}




for(var i=0;i<nrows;i++) {
  var cols=new Array(ncols);
  for(var j=0;j<ncols;j++) cols[j]={i:i,j:j,matches:[],nomatch:false,choiceptr:0,tile:null};
  rows[i]=cols;
}
for(var j=0;j<ncols;j++) {
  var colsT=new Array(nrows);
  for(var i=0;i<nrows;i++) colsT[i]=rows[i][j];
  rowsT[j]=colsT;
}




var ops=0;

var renderHash={};

function placeRows() {
  var queue=[],ci,cj,firstrowl,firstcoll,offsetj,offseti,dir,done=true,firstrowW,firstcolW;
  
  var rowsW,nrowsW,ncolsW;
    
  this.begin=function(startrc,dir) {
    this.queue(startrc,dir);
    if(done) { if(this.processQueue()) this.scheduleIterations(iterationsper); }
  };
  
  this.queue=function(startrc,dir) {
    queue.push({startrc:startrc,firstrow:firstrow,firstcol:firstcol,offsettx:offsettx,offsetty:offsetty,dir:dir});
  };
  
  this.processQueue=function() {
    if(queue.length) {
      done=false;
      var item=queue.mshift();
      firstrowl=item.firstrow;
      firstcoll=item.firstcol;
      cj=0;
      ci=item.startrc;
      offsetj=item.offsettx;
      offseti=item.offsetty;
      dir=item.dir;
      if(dir=='up' || dir=='dn') {
        rowsW=rows; nrowsW=nrows; ncolsW=ncols;
        firstrowW=firstrowl;
        firstcolW=firstcoll;
      }
      else {
        rowsW=rowsT; nrowsW=ncols; ncolsW=nrows;
        firstrowW=firstcoll;
        firstcolW=firstrowl;
      }      
      return true;
    }
    return false;
  };
  
  this.scheduleIterations=function(nper) {    
    if(!this.iterate(nper)) {      
      setTimeout('placer.scheduleIterations('+nper+');',0);
    }
    else {
      done=true;
      renderTiles(offseti,offsetj,firstrowl,firstcoll);
      if(this.processQueue()) { traceA('|'); this.scheduleIterations(nper); }
      
    }
    
  };

  this.iterate=function(times) {
    traceA('.');
    var count=0;
    switch(dir) {
      default:
      break; case 'dn': case 'rt': var inci=1;
      break; case 'up': case 'lt': var inci=-1;
    }
    
    for(;;) {
      var i=(firstrowW+ci)%nrowsW;
      switch(dir) {
        default:
        break; case 'dn': case 'rt': var prvrow=(ci>0)?rowsW[(i+nrowsW-1)%nrowsW]:null;
        break; case 'up': case 'lt': var prvrow=(ci<nrowsW-1)?rowsW[(i+1)%nrowsW]:null;
      }

      var row=rowsW[i];
      var edges={};

      for(;;) {
        var j=(firstcolW+cj)%ncolsW;
        //trace('--> '+ci+','+j);
        if(++count>times) return false;
        
        if(prvrow) {          
          switch(dir) {
            default: case 'dn': edges.top=   prvrow[j].tile.bottom;
            break;   case 'up': edges.bottom=prvrow[j].tile.top;
            break;   case 'rt': edges.left  =prvrow[j].tile.right;
            break;   case 'lt': edges.right =prvrow[j].tile.left;
          }
        }
        if(cj>0) {
          switch(dir) {
            default: case 'up': case 'dn': edges.left=row[(j+ncolsW-1)%ncolsW].tile.right;
            break;   case 'lt': case 'rt': edges.top =row[(j+ncolsW-1)%ncolsW].tile.bottom;
          }
        }
        
        if(!putTile(row[j],edges)) {
          row[j].nomatch=false;
          renderHash[ci+'_'+cj]={cell:row[j],ok:false};
          if(row[j].tile && row[j].matches.length) {
            fail(row[j]);
            row[j].tile=null;             
          }
          row[j].matches=[];
          cj--;
        }
        else {
          renderHash[ci+'_'+cj]={cell:row[j],ok:true};
          cj++;
        }
        if(cj==ncolsW) { cj=0;        ci+=inci; break; }
        if(cj<0)       { cj=ncolsW-1; ci-=inci; break; }
      }
      if(ci==nrowsW || ci<0) {
        clearMatches();
        return true;
      }
    }
  };
}

function clearMatches() {
  
  for(var i=0;i<nrows;i++) {
    var row=rows[i];
    for(var j=0;j<ncols;j++) {
      row[j].matches=[];
      row[j].nomatch=false;
    }
  }

}        
          
          

  

var cnt=0;


function renderTiles(offseti,offsetj,firstrowl,firstcoll) {
  trace("Rendering after "+ops+" operations.");
  ops=0;
  //trace('offseti='+offseti+' firstrow='+firstrowl);
  for(var t in renderHash) {
    var item=renderHash[t];
    delete renderHash[t];
    
    var cell=item.cell;
    
    if(cell) {
      var tile=cell.tile;
      var i=cell.i;
      var j=cell.j;
      if(tile && item.ok) {
        if(cell.node) tilesNode.removeChild(cell.node);
        tilesNode.appendChild(ce('div',tile.imageclass,{left:(w*((j-firstcoll+ncols)%ncols+offsetj))+'px',top:(h*((i-firstrowl+nrows)%nrows+offseti))+'px'}));
        cell.node=tilesNode.lastChild;
      }
      else if(!item.ok) if(cell.node) { tilesNode.removeChild(cell.node); cell.node=null; }
    }      
  }
}


var mouse=false;
var mouseX=0,mouseY=0;

function recenter() {

  while(-offsety>=(offsetty+1)*h) {
    offsetty++;
    firstrow=(firstrow+1)%nrows;
    placer.begin(nrows-1,'dn');
  }
  while(offsety>(-offsetty)*h) {
    offsetty--;
    firstrow=(firstrow+nrows-1)%nrows;
    placer.begin(0,'up');
  }
  
  while(-offsetx>(offsettx+1)*w) {
    offsettx++;
    firstcol=(firstcol+1)%ncols;
    placer.begin(ncols-1,'rt');
  }
  while(offsetx>(-offsettx)*w) {
    offsettx--;
    firstcol=(firstcol+ncols-1)%ncols;
    placer.begin(0,'lt');
  }
}


tilesNode.onmousedown=function() {
  mouse=true;
  return false;
};
document.onmouseup=tilesNode.onmouseup=window.onmouseup=function() {
  mouse=false;
  return false;
};

document.onmouseover=function(evt) {
  //status.innerHTML+="over | ";
  return false;  
};

document.onmousemove=function(evt) {
  if(!evt) evt = window.event;
  
  var dx=evt.clientX-mouseX;
  var dy=evt.clientY-mouseY;

  mouseX=evt.clientX;
  mouseY=evt.clientY;
  
  if(mouse) {
    offsetx+=dx;
    offsety+=dy;

    tilesNode.style.left=offsetx+'px';
    tilesNode.style.top=offsety+'px';    
    
    recenter();
  }
  return false;
};



var status=document.getElementById('status');




var placer=new placeRows();
firstrow=0;
placer.begin(0,'dn');