var ractive = new Ractive({
  el: 'container',
  template: '#template',
  gameOver: function() {
    // console.log("Game Over");
    clearInterval(this.get("timerId"));
    field.forEach(function(row) {
      row.forEach(function (cell) {
        if (!cell.isOpen) {
          cell.isOpen = true;
          cell.cl = "closed";
        }
        if (cell.val == "M") {
          cell.cl = cell.flagged ? "mine-true" : "mine";
        }  
      })
    })
    this.update("field");
    this.toggle("gameOver");
  },
  decreaseTime: function() {
    this.subtract("timeLeft");
    if (this.get("timeLeft") == 0) {
       this.set("gameOverMessage", "Time is up. Try to be faster.");
       this.gameOver();
    }
  },
  expand: function(cell) {
    // console.log(cell);
    var coords = neighbors(cell.r,cell.c);
    coords.forEach(function(coord) {
        var r = coord.row;
        var c = coord.col;
        if (!(field[r][c].isOpen || field[r][c].flagged)) this.open("field." + r + "." + c)            
    }.bind(this))
    return false;
  },
  flag: function(keypath) {
     var cell = this.get(keypath);
     // console.log(cell);
     if (cell.flagged) {
       cell.flagged = false;
       this.add("minesLeft");
       this.add("cellsLeft");
     }
     else {
       cell.flagged = true;
       this.set("lastCell", cell);
       this.subtract("minesLeft");
       this.subtract("cellsLeft");
     }     
     this.update(keypath)
  }, 
  open: function(keypath) {
      var cell = this.get(keypath);
      cell.isOpen = true;
      this.subtract("cellsLeft");
      var val = cell.val;
      if (val == "M") {
        cell.cl = "mine";
        this.set("gameOverMessage", "That was a mine. BOOM.");
        this.gameOver();
      }
      else {
        if (val == 0) {
          this.expand(cell);
        }
      }      
      this.update(keypath);     
  },
  data: { 
    showConfig: true,
    levels: { "Very Easy": {rows: 5,  cols: 8,  mines: 5},
              "Easy":      {rows: 6,  cols: 9, mines: 8},
              "Medium":    {rows: 10, cols: 16, mines: 32},
              "Hard":      {rows: 14, cols: 16, mines: 45} },
    timeLeft: 0,          
    minesLeft: 0,          
    cellsLeft: null,          
    gameOver: false,      
    gameOverMessage: "",    
    field: null,
    lastCell: null    
  },
  // http://docs.ractivejs.org/latest/computed-properties
  computed: {
    levelTime: function() {
      var r = this.get("rows");
      var c = this.get("cols");
      return Math.round( r*c + 1.5*(this.get("mines") - (r*c/8)) );
    }
  }
});

// http://docs.ractivejs.org/latest/observers
ractive.observe( 'chosenLevel', function ( newValue, oldValue, keypath ) {
   // console.log(newValue, oldValue, keypath);
   var cfg = this.get("levels." + newValue);
   this.set(cfg);
});

ractive.observe( 'cellsLeft', function ( newValue, oldValue, keypath ) {
   if (newValue === 0) {
       this.set("gameOverMessage", "WELL DONE.");
       this.gameOver();
   }
});

// http://docs.ractivejs.org/latest/proxy-events
ractive.on( 'open', function ( event ) {
  // console.log(event);
  var e = event.original;
  e.preventDefault();
  e.stopPropagation();
  this.set("lastCell", event.context);
  this.open(event.keypath);
  return false;
})

ractive.on( 'expand', function ( event ) {
  // console.log(event);
  var e = event.original;
  e.preventDefault();
  e.stopPropagation();
  clearSelection(); // when double clicked the text is selected by browser, this clears the selection
  this.set("lastCell", event.context);
  this.expand(event.context);
  return false;
})

ractive.on( 'flag', function ( event ) {
    var e = event.original;
    e.preventDefault();
    e.stopPropagation();
    this.flag(event.keypath);
    return false;
})

ractive.on( 'start-game restart', function ( event ) {
   setField(this.get("rows"), this.get("cols"), this.get("mines"));
   var self = this;
   this.set( { "timeLeft"  : this.get("levelTime"),
               "minesLeft" : this.get("mines"),
               "cellsLeft" : this.get("rows") * this.get("cols"),
               "field": field,
               "showConfig" : false,   // only for start-game actually
               "gameOver": false } ).then(function() {
     timerId = setInterval(self.decreaseTime.bind(self), 1000);
     self.set("timerId", timerId)
   })
});

var field;

function setField(rowCnt, colCnt, mineCnt) {
    var coords = [];
    field = new Array();
    for (var i=0; i<rowCnt; i++) {
      var newRow = new Array();
      for (var j=0; j<colCnt; j++) { 
        newRow.push({'isOpen': false, 'r': i, 'c':j, 'val':0});
        coords.push([i,j]);
      }
      field.push(newRow);  
    }

    shuffle(coords);
    var mineCoords = coords.slice(0,mineCnt);

    mineCoords.forEach(function(coord) {
      var r = coord[0];
      var c = coord[1];
      field[r][c].val = "M";
    })
    mineCoords.forEach(function(mineCoord) {        
      var coords = neighbors(mineCoord[0],mineCoord[1]);
      coords.forEach(function(coord) {
         if (field[coord.row][coord.col].val != "M") {
              field[coord.row][coord.col].val++;
         }
      })
    })
}

// utilities

function shuffle(array) {
    var counter = array.length, temp, index;
    while (counter > 0) {      // While there are elements in the array        
        index = Math.floor(Math.random() * counter);   // Pick a random index
        counter--;          // Decrease counter by 1
        // And swap the last element with it
        temp = array[counter];
        array[counter] = array[index];
        array[index] = temp;
    }
    return array;
}

function neighbors(r, c) {
  var result = [];
  for (var i=r-1; i<=r+1; i++) {
    if (i<0 || i>=field.length) continue;
    for (var j=c-1; j<=c+1; j++) {
      if (j<0 || j>=field[0].length) continue;
      if (i==r && j==c) continue;
      result.push({row:i, col:j});
    }
  }
  return result;
}

function clearSelection() {
    if (window.getSelection)
        window.getSelection().removeAllRanges();
    else if (document.selection)
        document.selection.empty();
}