// This file is part of the Minesweeper game
// http://henrikfalck.com/minesweeper/
// Copyright Karl Henrik Falck <f@lck.nu> 2007

function setCookie(name, value) {
  var date = new Date();
  date.setTime(date.getTime() + 30 * 24 * 60 * 60 * 1000);
  var expires = date.toGMTString();
  var path = "/";
  var re = new RegExp("/[a-z]*?:\/\/(.*?)(\/.*/)/");
  var m = re.exec(location.href);
  if (m) path = m[2];
  document.cookie = name + "=" + value + "; expires=" + expires + "; path=" + path;
}

function getCookie(name) {
  var nameeq = name + "=";
  var s = document.cookie;
  s = s.split("; ");
  for (var i = s.length - 1; i >= 0; --i) {
    var c = s[i].split("=");
    if (c[0] == name) return c[1];
  }
  return null;
}

function getTime() {
  if (Date.now) return Date.now();
  return new Date().getTime();
}

function getTimeSecs() {
  return parseInt(getTime() / 1000);
}

function timeSince(when) {
  return getTime() - when;
}

function formatSeconds(time) {
  var secs = time % 60;
  var mins = parseInt(time / 60);
  secs = secs < 10 ? "0" + secs : secs;
  return mins + ":" + secs;
}

function formatAgo(ago) {
  var fn = function(x, y) {
    x = parseInt(x);
    if (x == 0) return null;
    if (x == 1) return strings["agoformat"].replace("%t", x).replace("%u", strings[y]);
    return strings["agoformat"].replace("%t", x).replace("%u", strings[y + "s"]);
  }
  if (ago < 10)
    return strings["justnow"];
  if (ago < 60) {
    var secs = fn(ago, "second");
    if (secs) return secs;
  }
  else if (ago < 60 * 60) {
    var mins = fn(ago / 60, "minute");
    if (mins) return mins;
  }
  var years = fn(ago / 60 / 60 / 24 / 365, "year");
  if (years) return years;
  var days = fn(ago / 60 / 60 / 24, "day");
  if (days) return days;
  var hours = fn(ago / 60 / 60, "hour");
  if (hours) return hours;
  return "";
}
var Game = Class.create();
Game.prototype = {
  self: this,

  initialize: function() {
    this.online = false;
    this.highscores = null;
  },

  construct: function(stats) {
    this.stats = stats;
    field = new Field(stats);
    field.construct();
    field.createElement();
    this.onResize();
    field.div.style.visibility = "visible";
    Event.observe(window, "resize", this.onResize.bind(this));
    this.reset();
    var to = Prototype.Browser.Opera ? 0.99 : 1;
    new Effect.Appear(field.div, {from: 0, to: to});
  },
  
  reset: function() {
    this.running = false;
    this.started = false;
    this.stopped = false;
    this.gameTime = 0;
    field.reset();
    field.generate();
    game.hideCurtain();
    this.updateTime();
    this.updateStatusBar();
    this.highscores = null;
    this.waitingForHighscores = false;
    this.onlineHighscore = false;
  },

  regenerate: function(t) {
    while (t.mine) {
      field.reset();
      field.generate();
      t.reveal();
    }
  },

  start: function() {
    this.startTime = new Date().getTime();
    this.updateTimeTimeout = setInterval(this.updateTime.bind(this), 1000);
    this.running = true;
    this.started = true;
    this.updateTime();
  },

  onResize: function() {
    var w = window.innerWidth ? window.innerWidth : (document.documentElement.clientWidth ? document.documentElement.clientWidth : document.body.clientWidth);
    var h = window.innerHeight ? window.innerHeight : (document.documentElement.clientHeight ? document.documentElement.clientHeight : document.body.clientHeight);
    var w2 = field.tileWidth * field.width / 2;
    var topbary = $("top") ? 100 : 20;
    field.div.setStyle({
      left: w / 2 - w2,
	  top: topbary + 65
	  });
    var pos = Position.cumulativeOffset(field.div);
    $("topbar").setStyle({top: topbary, left: w / 2 - 239});
    $("bottom").setStyle({top: pos[1] + field.tileHeight * field.height + 20, left: w / 2 - 300});
    var elm = $("curtain");
    elm.setStyle({
      top: pos[1],
	  left: pos[0],
	  width: field.div.getWidth(),
	  height: field.div.getHeight()
	  });
  },

  gameOver: function(won) {
    this.running = false;
    this.stopped = true;
    this.gameTime = parseInt(timeSince(this.startTime) / 1000);
    this.won = won;
    reportGameover(won, this.gameTime);
    if (!won)
      new Effect.Shake(field.div, { duration: 0.5 });
    setTimeout(this.revealTiles.bind(this, won), won ? 10 : 1000);
  },

  revealTiles: function(won) {
    $("outcome").innerHTML = strings[won ? "goodwork" : "gameover"];
    var i = 0;
    field.tiles.each(function(t) {
		       if (!t.revealed())
			 setTimeout(t.reveal.bind(t), 1000 + 500 * Math.random() + 10 * ++i);
		     });
    setTimeout(this.showCurtain.bind(this), (won ? 500 : 1500) + 10 * i);
  },
  
  showCurtain: function() {
    scheduleReport(null); // just in case
    if (this.waitingForHighscores) return;
    if (this.online && !this.highscores) {
      this.waitingForHighscores = true;
      return;
    }
    stopReporting();
    var elm = $("curtain");
    var pos = Position.cumulativeOffset(field.div);
    elm.setStyle({
      top: pos[1],
	  left: pos[0],
	  width: field.div.getWidth(),
	  height: field.div.getHeight()
	  });
    elm.style.visibility = "visible";
    var div = $("highscores");
    div.innerHTML = "";
    div.setStyle({width: field.div.getWidth(), height: field.div.getHeight() - 150, left: 0});
    $("restartactions").setStyle({visibility: "hidden"});
    var newhs = false;
    if (!game.online) {
      this.loadLocalHighscores();
      newhs = this.isHighscore(this.gameTime);
      if (newhs) this.insertHighscore(this.gameTime, null);
    }
    else {
      newhs = !!this.onlineHighscore;
    }
    $("highscoreslbl").innerHTML = strings[newhs ? "newhighscore" : "highscores"];
    new Effect.Appear(elm, {from: 0.0, to: 0.6, afterFinish: (this.onlineHighscore ? this.showNameInput.bind(this) : this.showHighscores.bind(this))});
  },

  showNameInput: function() {
    var div = $("nameinput");
    div.setStyle({visibility: "visible", display: "block", opacity: 0});
    n = getCookie("ms-name");
    $("name").value = n ? n : "";
    $("name").focus();
    if (Prototype.Browser.Gecko)
      div.setStyle({opacity: 1});
    else
      new Effect.Appear(div);
  },

  submitHighscoreName: function(name) {
    if (name.length > 12) name = name.substr(0, 12);
    this.insertHighscore(this.gameTime, name);
    new Effect.Fade($("nameinput"), {afterFinish: this.showHighscores.bind(this)});
    setCookie("ms-name", name);
  },

  showHighscores: function() {
    $("nameinput").setStyle({visibility: "hidden", display: "none"});
    var div = $("highscores");
    var pos = Position.positionedOffset(div);
    for (var i = 0; (h = this.highscores[i]) && i < this.stats.highscores; ++i) {
      var hs = $(document.createElement("div"));
      hs.addClassName("highscore");
      hs.setStyle({top: field.div.getHeight()});
      hs.innerHTML = (h[3] ? "<em>" : "") +
	formatSeconds(h[0]) + " " +
	(h[1] ? h[1] : "") + " " +
	'<small>' + formatAgo(h[2]) + '</small>' +
	(h[3] ? "</em>" : "");
      div.appendChild(hs);
      var offy = Prototype.Browser.IE ? 5 : 10 + pos[1];
      new Effect.Move(hs, {duration: 1 + 0.5 * i, y: offy + i * 25, mode: "absolute"});
    }
    var fn = function() {
      var elm = $("restartactions");
      elm.setStyle({opacity: 0, visibility: "visible"});
      new Effect.Appear(elm);
    }
    setTimeout(fn, 500 * this.highscores.length);
  },

  hideCurtain: function() {
    var elm = $("curtain");
    new Effect.Fade(elm);
  },

  updateTime: function() {
    if (!this.started) {
      $("time").innerHTML = "";
      return;
    }
    else if (!this.running) {
      clearTimeout(this.updateTimeTimeout);
      if (this.gameTime)
	$("time").innerHTML = "<b>" + formatSeconds(this.gameTime) + "</b>";
      else
	$("time").innerHTML = "<b>" + $("time").innerHTML + "</b>";
      return;
    }
    var time = parseInt(timeSince(this.startTime) / 1000);
    $("time").innerHTML = formatSeconds(time);
    if (time >= 6039) this.gameOver(false);
  },

  updateStatusBar: function() {
    var p = Math.min((field.revealed + field.flagged) / field.size, 1.0);
    $("progressbar").style.width = parseInt(p * 79);
    $("flagged").innerHTML = field.flagged;
    $("marked").innerHTML = field.marked;
  },

  destruct: function() {
    field.removeElements();
    $("curtain").setStyle({opacity: 0, visibility: "hidden"});
    $("field").setStyle({opacity: 0, visibility: "hidden"});
  },

  loadLocalHighscores: function() {
    this.highscores = $A();
    var c = getCookie("ms-hs-" + this.stats.level);
    if (!c) return;
    var s = $A(c.split("|"));
    this.highscores = s.collect(function(x) {
				  var y = x.split("@");
				  return [parseInt(y[0]), null, parseInt(timeSince(parseInt(y[1]) * 1000) / 1000)];
				});
  },
  
  isHighscore: function(secs) {
    return this.highscores.length < this.stats.highscores || secs <= this.highscores[this.highscores.length - 1][0];
  },

  insertHighscore: function(secs, name) {
    if (this.highscores.length == 0) {
      this.highscores = $A([[secs, name, 0, true]]);
      return;
    }
    if (this.highscores.length < this.stats.highscores &&
	secs > this.highscores.last()[0] && secs) {
      this.highscores.push([secs, name, 0, true]);
      return;
    }
    var ary = $A();
    for (var i = 0; (h = this.highscores[i]) && i < this.stats.highscores; ++i) {
      if (secs <= h[0] && secs) {
	ary.push([secs, name, 0, true]); secs = null;
      }
      if (ary.length < this.stats.highscores)
	ary.push(h);
    }
    this.highscores = ary;
  },

  saveLocalHighscores: function() {
    var s = this.highscores.map(function(h) {
				  var t = getTimeSecs() + h[2];
				  return h[0] + "@" + t;
				}).join("|");
    setCookie("ms-hs-" + this.stats.level, s);
  },

  goOffline: function() {
    this.online = false;
  },

  goOnline: function() {
    this.online = true;
  },

  highscoresReceived: function(hs, newhs) {
    this.highscores = $A(hs);
    this.onlineHighscore = newhs;
    if (this.waitingForHighscores) {
      this.waitingForHighscores = false;
      this.showCurtain();
    }
  }
};
var Tile = Class.create();
Tile.prototype = {
  HIDDEN: 0,
  REVEALED: 1,
  FLAGGED: 2,
  MARKED: 3,
  self: this,

  initialize: function(r, c) {
    this.r = r;
    this.c = c;
    this.neighbors = $A();
  },

  construct: function() {
    for (var r = this.r - 1; r <= this.r + 1; ++r)
      for (var c = this.c - 1; c <= this.c + 1; ++c)
	if (r != this.r || c != this.c) {
	  var t = field.get(r, c);
	  if (t) this.neighbors.push(t);
	}
  },

  reset: function() {
    this.count = 0;
    this.mine = false;
    this.div.style.backgroundImage = "url(grid." + imgext + ")";
    this.state = this.HIDDEN;
  },
  
  generate: function() {
    var c = 0;
    this.neighbors.each(function(t) {
			  if (t.mine) ++c;
			});
    this.count = c;
  },

  createElement: function() {
    this.div = $(document.createElement("div"));
    this.div.addClassName("tile");
    this.div.setStyle({top: this.r * field.tileHeight,
			  left: this.c * field.tileWidth});
    this.div.observe("mouseover", this.onMouseOver.bind(this));
    this.div.observe("mouseout", this.onMouseOut.bind(this));
    if (Prototype.Browser.IE)
      this.div.observe("mouseup", this.onClick.bind(this));
    else
      this.div.observe("click", this.onClick.bind(this));
    return this.div;
  },

  onMouseOver: function(evt) {
    if (!this.hidden()) return;
    this.div.style.backgroundImage = "url(grid-active." + imgext + ")";
  },

  onMouseOut: function(evt) {
    if (!this.hidden()) return;
    this.div.style.backgroundImage = "url(grid." + imgext + ")";
  },

  onClick: function(evt) {
    if (this.revealed() || game.stopped) return;
    if (!Event.isLeftClick(evt) || evt.ctrlKey || evt.shiftKey) {
      if (evt.ctrlKey && this.hidden())
	this.setState(this.FLAGGED);
      else if ((evt.shiftKey && !this.marked()) ||
	       (evt.ctrlKey && this.flagged()))
	this.setState(this.MARKED);
      else if (evt.shiftKey && this.marked())
	this.setState(this.FLAGGED);
      else if ((evt.ctrlKey && evt.shiftKey) || (evt.ctrlKey && this.marked()))
	this.setState(this.HIDDEN);
    }
    else if (!this.flagged()) {
      reportSweep(this.r, this.c, this.mine || !game.started);
      this.reveal();
    }
  },

  reveal: function() {
    if (this.revealed()) return;
    this.setState(this.REVEALED);
    if (!this.mine && this.count == 0)
      this.neighbors.each(function(t) {
			    if (!t.revealed())
			      setTimeout(t.reveal.bind(t), 10 + 100 * Math.random());
			  });
  },

  hidden: function() { return this.state == this.HIDDEN; },
  revealed: function() { return this.state == this.REVEALED; },
  flagged: function() { return this.state == this.FLAGGED; },
  marked: function() { return this.state == this.MARKED; },

  setState: function(state) {
    if (state == this.REVEALED) {
      if (this.mine)
	this.div.style.backgroundImage = "url(grid-mine." + imgext + ")";
      else if (this.count != 0)
	this.div.style.backgroundImage = "url(grid-" + this.count + "." + imgext +")";
      else
	this.div.style.background = "transparent";
    }
    else { 
      var imgs = ["grid." + imgext, null, "grid-flag." + imgext, "grid-mark." + imgext];
      this.div.style.backgroundImage = "url(" + imgs[state] + ")";
    }
    field.onStateChange(this, this.state, state);
    this.state = state;
  }
};
var Field = Class.create();
Field.prototype = {
  tileWidth: 20,
  tileHeight: 20,

  initialize: function(stats) {
    this.tiles = $A();
    this.stats = stats;
    this.height = stats.rows;
    this.width = stats.cols;
    this.mines = stats.mines;
    this.size = stats.rows * stats.cols;
    for (var r = 0; r < stats.rows; ++r)
      for (var c = 0; c < stats.cols; ++c)
	this.tiles[r * stats.cols + c] = new Tile(r, c);
  },

  construct: function() {
    this.tiles.each(function(t) {
		      t.construct();
		    });
  },

  reset: function() {
    this.tiles.each(function(t) {
		      t.reset();
		    });
    this.swept = 0;
    this.flagged = 0;
    this.marked = 0;
    this.revealed = 0;
    this.id = 0;
  },

  generate: function() {
    if (!game.standalone &&
	minesource &&
	minesource.has(this.stats.level)) {
      if (!game.online) game.goOnline();
      var data = minesource.get(this.stats.level);
      var x = data.split("|");
      this.id = parseInt(x[0]);
      var str = x[1];
      for (var i = 0; i < str.length; ++i)
	this.tiles[i].mine = str.charAt(i) == "x";
    }
    else {
      if (game.online) game.goOffline();
      for (var i = 0; i < this.mines; ++i) {
	var r = Math.floor(this.height * Math.random());
	var c = Math.floor(this.width * Math.random());
	var t = this.get(r, c);
	if (t.mine) --i;
	else t.mine = true;
      }
    }
    this.tiles.each(function(t) {
		      t.generate();
		    });
  },

  get: function(r, c) {
    if (c < 0 || r < 0 ||
	c >= this.width || r >= this.height)
      return null;
    return this.tiles[r * this.width + c];
  },

  createElement: function() {
    //    var div = document.createElement("div");
    var div = $("field");
    //        div.addClassName("field");
    div.setStyle({width: this.width * this.tileWidth,
    		     height: this.height * this.tileHeight});
    this.tiles.each(function(t) {
		      div.appendChild(t.createElement());
		    });
    this.div = div;
    //    var body = document.getElementsByTagName("body")[0];
    //    body.className = "foo";
    //    body.className = "";
    return div;
  },

  removeElements: function() {
    var div = $("field");
    div.innerHTML = "";
  },

  onSweep: function(t) {
    if (!game.started) game.start();
    ++this.swept;
    if (game.running && this.swept == this.width * this.height - this.mines)
      game.gameOver(true); // win
  },

  onStateChange: function(t, from, to) {
    if (from == t.FLAGGED)
      this.flagged--;
    else if (from == t.MARKED)
      this.marked--;
    else if (from == t.REVEALED)
      this.revealed--; // can't happen?
    if (to == t.REVEALED) {
      this.revealed++;
      if (!t.mine)
	this.onSweep(t);
      else if (game.running)
	game.gameOver(false);
      else if (!game.started)
	game.regenerate(t);
    }
    else if (to == t.FLAGGED)
      this.flagged++;
    else if (to == t.MARKED)
      this.marked++;
    game.updateStatusBar();
  }
};

var field = null;
var game = null;
var fieldStats = [
  {level: 1, rows: 10, cols: 10, mines: 10, highscores: 2},
  {level: 2, rows: 12, cols: 12, mines: 18, highscores: 3},
  {level: 3, rows: 14, cols: 16, mines: 30, highscores: 5},
  {level: 4, rows: 16, cols: 20, mines: 48, highscores: 6},
  {level: 5, rows: 20, cols: 32, mines: 128, highscores: 9}
];
var difficulties = ["beginner", "easy", "normal", "hard", "extreme"];
var difficulty = null;
var minesource = null;
var strings = null;
var lastUpdate = 0;

function loadStrings() {
  var lang = getCookie("ms-lang");
  if (!lang) lang = getCookie("lang");
  if (!lang && navigator && (navigator.language || navigator.userLanguage)) {
    var l = navigator.language ? navigator.language : navigator.userLanguage;
    var i = l.indexOf("-");
    if (i != -1)
      l = l.substr(0, i);
    if (langstrings[l])
      lang = l;
  }
  if (!langstrings[lang])
    lang = "en";
  if (!getCookie("lang"))
    setCookie("lang", lang);
  setCookie("ms-lang", lang);
  if (strings == langstrings[lang]) return;
  strings = langstrings[lang];
  $("difficultylbl").innerHTML = strings["choosedifficulty"];
  $("intro0").innerHTML = strings["intro0"];
  $("intro1").innerHTML = strings["intro1"];
  $("intro2").innerHTML = strings["intro2"];
  $("introlbl").innerHTML = strings["minesweeper"];
  $("highscoreslbl").innerHTML = strings["highscores"];
  $("nameinput").observe("keypress", onNameInput);
  $("namelbl").innerHTML = strings["name"];
  $("playagain").innerHTML = strings["playagain"];
  $("changedifficulty").innerHTML = strings["changedifficulty"];
  $("instructions").innerHTML = strings["instructions"];
  $("newslbl").innerHTML = strings["recenthighscores"];
  $("commentz").innerHTML = strings["commentsplz"];
  $("commentz2").innerHTML = strings["commentsplz"];
  for (var i = 0; d = difficulties[i]; ++i) {
    var s = fieldStats[i];
    $(d + "lbl").innerHTML = strings[d];
    $(d + "desc").innerHTML = "<b>" + strings["size"] + "</b>: " +
      s.rows + "x" + s.cols + "&nbsp;&nbsp;<b>" +
      strings["mines"] + "</b>: " +
      parseInt(100 * s.mines / s.rows / s.cols) + "%";
  }
}

function initialize() {
  loadStrings();
  for (var i = 0; d = difficulties[i]; ++i) {
    var fn = function(j) {
      $(d).observe("click", function() { onDifficulty(j); });
    };
    fn(i);
  }
  new Effect.Appear($("start"));
  minesource = new Minesource();
  if (initialFields)
    initialFields.each(function(f) { minesource.push(f); });
  var langs = langstrings.keys();
  $("languages").innerHTML =
    langs.collect(function(l) {
		    return '<a href="javascript:changeLanguage(\'' + l + '\')">' + langstrings[l]["languagename"] + '</a>';
		  }).join(" &mdash; ");
  $("start").setStyle({visibility: "visible"});
  lastUpdate = getTime();
  showRecentHighscores();
}

function changeLanguage(l) {
  setCookie("ms-lang", l);
  loadStrings();
  showRecentHighscores();
}

function onDifficulty(lvl) {
  if (difficulty != null) return;
  difficulty = lvl;
  if (!standalone && !minesource.has(lvl + 1))
    minesource.request(lvl + 1);
  var effects = [];
  for (var i = 0; i < difficulties.length; ++i)
    if (i != lvl)
      effects.push(new Effect.Fade($(difficulties[i]), { from: 1, to: 0.1, duration: 0.4, sync: true }));
  new Effect.Parallel(effects, { afterFinish: fadeoutStart, delay: 0.01 });
}

function fadeoutStart() {
  if (Prototype.Browser.IE) {
    var effects = [];
    effects.push(new Effect.Fade($("start"), { sync: true }));
    for (var i = 0; d = difficulties[i]; ++i)
      effects.push(new Effect.Fade($(d + "lbl"), { sync: true }));
    new Effect.Parallel(effects, { delay: 0.1, afterFinish: showGame });
  }
  else
    new Effect.Fade($("start"), { delay: 0.1, afterFinish: showGame });
}

function showRecentHighscores() {
  if (!recentHighscores) return;
  var str = '<table>';
  var lastlvl = 0;
  var lines = 0;
  var since = parseInt((getTime() - lastUpdate) / 1000);
  for (var i = 0; (h = recentHighscores[i]) && lines < 12; ++i) {
    if (h[0] != lastlvl) {
      str += '<tr><td' + (lastlvl != 0 ? ' style="padding-top: 8px"' : '') + '><h3>' + strings[difficulties[h[0] - 1]] + "</h3></td></tr>";
      lastlvl = h[0];
      ++lines;
    }
    str += '<tr><td>' + formatSeconds(h[1]) + ' <b>' + h[2] + "</b></td></tr>";
    str += '<tr><td><small>' + formatAgo(h[3] + since) + '</small></td></tr>';
    lines += 2;
  }
  str += '</table>';
  $("newsdiv").innerHTML = str;
}

function showGame() {
  $("start").style.visibility = "hidden";
  $("game").style.visibility = "visible";
  constructGame();
}

function constructGame() {
  game = new Game();
  game.construct(fieldStats[difficulty]);
}

function playAgain() {
  game.reset();
  game.hideCurtain();
}

function changeDifficulty() {
  game.hideCurtain();
  showStart();
}

function showStart() {
  difficulty = null;
  $("game").style.visibility = "hidden";
  game.destruct();
  $("start").setStyle({opacity: 0});
  $("start").style.visibility = "visible";
  difficulties.each(function(id) {
		      $(id).setStyle({opacity: 1});
		    });
  if (Prototype.Browser.IE) {
    var effects = [];
    effects.push(new Effect.Appear($("start"), { sync: true }));
    for (var i = 0; d = difficulties[i]; ++i)
      effects.push(new Effect.Appear($(d + "lbl"), { sync: true }));
    new Effect.Parallel(effects, { delay: 0.1 });
  }
  else
    new Effect.Appear($("start"), { delay: 0.1 });
  showRecentHighscores();
}

function onNameInput(evt) {
  if (evt.keyCode != Event.KEY_RETURN) return;
  var name = $F("name");
  if (name.length == 0) return;
  registerHighscore(name);
  if (game) game.submitHighscoreName(name);
}

var Minesource = Class.create();
Minesource.prototype = {
  initialize: function() {
    this.data = $A([$A(), $A(), $A(), $A(), $A()]);
    this.requesting = false;
  },

  push: function(x) {
    this.data[x.lvl - 1].push(x.grid);
  },

  has: function(lvl) {
    return this.data[lvl - 1].length > 0;
  },

  get: function(lvl) {
    var ary = this.data[lvl - 1];
    if (ary.length <= 3) this.request(lvl);
    return ary.shift();
  },

  count: function(lvl) {
    return this.data[lvl - 1].length;
  },

  request: function(lvl) {
    if (this.requesting) return;
    var url = baseUrl + 'fetch/' + lvl + '?x=' + getTime();
    new Ajax.Request(url, {
      method: "post",
			 parameters: {
		       level: lvl
			   },
			 onSuccess: this.fieldFetched.bind(this),
			 onFailure: this.onFailure.bind(this)
			 });
  },

  fieldFetched: function(req) {
    var res = req.responseText.evalJSON();
    for (var i = 0; i < res.fields.length; ++i)
      this.push(res.fields[i]);
    this.requesting = false;
  },

  onFailure: function() {
    this.requesting = false;
  }
};

var sweeps = $A();

function reportGameover(won, time) {
  sweeps.push(["gameover", won ? "won" : "lost", time]);
  scheduleReport(null);
}

function reportSweep(r, c, immediate) {
  if (!game.online) return;
  sweeps.push([r, c]);
  if (immediate) scheduleReport(null);
}

var reportTimeout = null;
var reporting = false;

function scheduleReport(secs) {
  if (!game.online) return;
  var int = secs ? secs * 1000 : 50;
  clearTimeout(reportTimeout);
  reportTimeout = setTimeout(sendReport, int);
}

function stopReporting() {
  clearTimeout(reportTimeout);
}

function sendReport() {
  if (!game.online) return;
  if (reporting) {
    scheduleReport(1);
    return;
  }
  if (sweeps.length == 0) {
    scheduleReport(10);
    return;
  }
  reporting = true;
  var old = sweeps;
  sweeps = $A();
  var str = old.flatten().join("x");
  var url = baseUrl + 'sweep?x=' + getTime();
  new Ajax.Request(url,
		   {
		   method: "post",
		       parameters: {
		     level: field.stats.level,
			 field: field.id,
			 coords: str
			 },
		       onSuccess: onReportSuccess,
		       onFailure: onReportFailure
		       });
}

function onReportSuccess(req) {
  reporting = false;
  scheduleReport(30);
  var res = req.responseText.evalJSON();
  debugmsg("res: " + res.r);
  if (res.r != "ok" && res.r != "end")
    game.goOffline();
  if (res.ping)
    sendPing();
  debugmsg("lag: " + res.lag);
  if (res.r == "end") {
    game.gameTime = res.time;
    game.highscoresReceived(res.hs, res.gratz);
    game.updateTime();
    if (res.rhs) {
      recentHighscores = res.rhs;
      lastUpdate = getTime();
    }
  }
}

function sendPing() {
  setTimeout(function() {
	       var url = baseUrl + 'ping?x=' + getTime();
	       new Ajax.Request(url, {
		 method: "post"
				    });
	     }, 17000);
}

function onReportFailure(req) {
  reporting = false;
  scheduleReport(1);
  debugmsg("report failure");
}

function debugmsg(msg) {
  if (debug)
    window.status = msg;
}

function registerHighscore(name) {
  var url = baseUrl + 'register?x=' + getTime();
  new Ajax.Request(url, {
    method: "post",
		       parameters: {
		     name: name
			 }
    });
  if (!recentHighscores) return;
  var diff = parseInt((getTime() - lastUpdate) / 1000);
  recentHighscores.unshift([field.stats.level, game.gameTime, name, diff]);
}

