////////////////////////////////////////////////////
// Game state machine.
var State = new function() { this.name = null; this.current = null; this.module = null; };

State.go = function(name, context) {
  this.name = name;
  
  // Get the next module and state.
  var next_module = null;
  var next_state = null;
  
  var dot = name.indexOf('.');
  if (dot >= 0) {
    next_module = this[name.substring(0, dot)];
    next_state = next_module[name.substring(dot + 1)];
  } else {
    next_state = this[name];
  }
  
  // If state doesn't change, do nothing.
  if (this.current == next_state)
    return;
  
  // Exit current state.
  if (this.current && this.current.leave) 
    this.current.leave(context);

  // Switch module if necessary.  
  if (this.module != next_module) {
    if (this.module && this.module.leave) 
      this.module.leave(context);
    
    this.module = next_module;
  
    if (this.module && this.module.enter)
      this.module.enter(context);  
  }
  
  // Enter a new state.  
  this.current = next_state;
  if (this.current && this.current.enter)
    this.current.enter(context);
};

////////////////////////////////////////////////////
// Launcher.
State.launch = {
  enter: function(context) {
    Widgets.Transitions.hide($("#id_menu"));
    Widgets.Transitions.hide($("#id_scoreboard"));
    Widgets.Transitions.hide($("#id_lobby"));
    Widgets.Transitions.hide($("#id_end"));
    Widgets.Transitions.hide($("#id_help"));
    
    Widgets.Transitions.fadeIn($("#frame_lobby"), function() {      
      State.go("intro");
    });
  }  
};

////////////////////////////////////////////////////
// Visual renderer.
function Visual(width, height) {
  this.EndGame = { running: 0, tail: 1, noplace: 2, released: 3, out: 4 };
  
  this._width = width;
  this._height = height;    
   
  this._canvasBackground = $("#id_background_canvas");
  this._canvasMain = $("#id_main_canvas");
  
  var canvas = new Widgets.Canvas(this._canvasBackground, this._width, this._height);  
  canvas.fill('rgba(32, 52, 57, 1)', 0, 0, this._width, this._height);
  canvas = new Widgets.Canvas(this._canvasMain, this._width, this._height);  
  canvas.clear();
  
  this._scoreLabel = $("#id_score_label");
  this._scoreLabel.html("");
  
  this._timer = null;
  
  this._grow = null;
  
  this._score = 0;
  
  this._history = [];
  this._historyLimit = 100;
  
  this._endGame = this.EndGame.running;
  this._endGameSteps = 0;
  
  this._fill = [];
  for (var i = 0; i < height; ++i) {
    var r = [];
    for (var j = 0; j < width; ++j) {
      r.push(0); 
    }
    this._fill.push(r);
  }
  
  this.seed();
  
  this.render(true);
}

Visual.prototype.score = function() {
  return this._score;
}

Visual.prototype.seed = function() {  
  for (var tries = 50; tries > 0; -- tries)
  {
    var buffer = 40;
    
    var x = Math.floor(Math.random() * (this._width - buffer) + buffer * 0.5);
    var y = Math.floor(Math.random() * (this._height - buffer) + buffer * 0.5);
    
    this._seed = { x: x, y: y, radius: 4, radius_wave: 6 };    
    
    var intersects = false;
    
    for (var i = x - 7; i <= x + 7; ++i)
      for (var j = y - 7; j <= y + 7; ++j) {
        var p = this._fill[j][i];
        
        if (p > 0) {
          intersects = true;
          break;
        }
        if (intersects)
          break;
      }
      
    if (!intersects)
      return true;
  }
  
  this._seed = null;
  
  return false;
};

function sign(x) {
  if (x < 0)
    return -1;
  if (x > 0)
    return 1;
  
  return 0;
}

Visual.prototype.line = function(x1, y1, x2, y2, v) {
  var x = x1, y = y1, steps = Math.max(Math.abs(x2 - x1), Math.abs(y2 - y1));
  
  if (steps == 0)
    return null;
  
  var dx = (x2 - x1) / steps, dy = (y2 - y1) / steps;
  
  var check = 0;
  
  if (v == 1) {
    for (;;) {
      x += dx;
      y += dy;
      
      var ix = Math.round(x);
      var iy = Math.round(y);    
      
      if (check >= 0 && this._fill[iy][ix] == 1)
        return { x: ix, y: iy };
      
      check++;
      
      this._fill[iy][ix] = v;
      
      if (ix == x2 && iy == y2)
        break;
    }
  } else {
    for (;;) {
      x += dx;
      y += dy;
      
      var ix = Math.round(x);
      var iy = Math.round(y);    
      
      this._fill[iy][ix] = v;
      
      if (ix == x2 && iy == y2)
        break;
    }
  }
  
  return null;
};

Visual.prototype.intersects = function(x1, y1, x2, y2) {
  var x = x1, y = y1, steps = Math.max(Math.abs(x2 - x1), Math.abs(y2 - y1));
  
  if (steps == 0)
    return false;
  
  var dx = (x2 - x1) / steps, dy = (y2 - y1) / steps;
  
  for (;;) {
    x += dx;
    y += dy;
    
    var ix = Math.round(x);
    var iy = Math.round(y);
    
    if (this._fill[iy][ix] == 1)
      return true;
    
    if (ix == x2 && iy == y2)
      return false;
  }
};

Visual.prototype.render = function(first) {
  var This = this;
    
  var canvas = new Widgets.Canvas(this._canvasBackground, this._width, this._height);  
  canvas.fill('rgba(32, 52, 57, 0.2)', 0, 0, this._width, this._height);
  
  // Draw current seed.
  if (this._seed) {
    canvas.fillCircle('rgba(0, 0, 0, 1)', this._seed.x + 1, this._seed.y + 1, this._seed.radius);
    
    canvas.fillCircle('rgba(255, 255, 255, 1)', this._seed.x, this._seed.y, this._seed.radius);
    
    canvas.circle('rgba(233, 244, 193, 0.2)', 1, this._seed.x, this._seed.y, this._seed.radius_wave);
    
    this._seed.radius_wave += 1;
    if (this._seed.radius_wave > 30)
      this._seed.radius_wave = 6;
  }
  
  if (this._grow && this._grow.to) {
    var c = canvas.get();
    
    c.strokeStyle = 'rgba(255, 255, 255, 0.2)';
    c.lineWidth = 1;
    
    c.beginPath();
    c.moveTo(this._grow.to.x, 0);
    c.lineTo(this._grow.to.x, this._height);
    c.moveTo(0, this._grow.to.y)
    c.lineTo(this._width, this._grow.to.y);
    c.stroke();
  }
  
  // Draw stem.
  if (this._grow && this._endGame == this.EndGame.running) {
    var redraw = false;
    var updateScore = false;
    
    var step = 7;
    
    for (var i = 0; i < 2; i++) {
      if (this._grow.to) {
        var dx = this._grow.to.x - this._grow.x;
        var dy = this._grow.to.y - this._grow.y;
        
        var length = Math.sqrt(dx * dx + dy * dy);
        if (length > 0 && length < step) {
          this._grow.x = this._grow.to.x;
          this._grow.y = this._grow.to.y;
        } else if (length > 0) {
          this._grow.x += dx / length * step;
          this._grow.y += dy / length * step;
        }
      }
      
      var intX = Math.floor(this._grow.x);
      var intY = Math.floor(this._grow.y);
      
      if (this._grow.lastX != intX || this._grow.lastY != intY) {
        this._grow.lastX = intX;
        this._grow.lastY = intY;
        
        if (this._history.length >= 2) {
          var l = this._history.length;
          
          if (intX >= this._width || intX < 0 || intY >= this._height || intY < 0) {
            this._endGame = this.EndGame.out;
            this._endGameWhere = { x: intX, y: intY };
          } else {          
            var test = this.line(this._history[l - 2], this._history[l - 1], intX, intY, 1);
            if (test) {
              this._endGame = this.EndGame.tail;
              this._endGameWhere = test;
            }
          }
        }
        
        this._history.push(intX);
        this._history.push(intY);
        
        if (!updateScore) {
          this._score += 1;        
          updateScore = true;
        }
        
        redraw = true;
      }
      
      if (this._seed) {
        var dx = this._seed.x - this._grow.x;
        var dy = this._seed.y - this._grow.y;
        
        if (dx * dx + dy * dy < 64) {
          this._historyLimit += 20;
          
          this.seed();
          
          if (this._seed == null) {
            this._endGame = this.EndGame.noplace;
          }
          
          this._score += 5000;
          
          updateScore = true;
        }
      }
    }
    
    if (updateScore) {
      this._scoreLabel.html("Score: " + this._score);
    }
    
    if (redraw && this._history.length > 2) {
      var h = this._history;
      var limit = this._historyLimit;
      while (h.length > limit) {
        var x = h.shift();
        var y = h.shift();
        
        this.line(x, y, h[0], h[1], 0);
      }
      
      var main_canvas = new Widgets.Canvas(this._canvasMain, this._width, this._height);
      
      main_canvas.clear();
      
      var c = main_canvas.get();
        
      c.beginPath();
      c.moveTo(h[0], h[1]);
      for (var i = 2; i < h.length; i += 2)
        c.lineTo(h[i], h[i + 1]);
      
      c.strokeStyle = 'rgb(138, 185, 73)';
      c.lineWidth = 2;
      c.lineJoin = 'round';
      c.stroke();
      
      c.beginPath();
    }
  }
  
  // Detect end game.
  if (this._endGame != this.EndGame.running && this._endGameSteps < 8) {
    var main_canvas = new Widgets.Canvas(this._canvasMain, this._width, this._height);
    
    if (this._endGame == this.EndGame.noplace) {
      main_canvas.fill('rgba(255, 255, 255, 0.01)', 0, 0, this._width, this._height);
    } else {
      main_canvas.fill('rgba(255, 0, 0, 0.02)', 0, 0, this._width, this._height);
      
      if (this._endGameWhere)
        main_canvas.fillCircle('rgba(249, 198, 63, 0.03)', this._endGameWhere.x, this._endGameWhere.y, 30);
    }     
    
    this._endGameSteps++;
    
    if (this._endGameSteps == 8)
      State.go("game.end");
  }
  
  this._timer = setTimeout(function() {
    This.render();
  }, Widgets.isApple ? 5: 70);
};

Visual.prototype.startGrow = function(x, y) {
  if (this._endGame == this.EndGame.running) {
    this._grow = { x: x, y: y, lastX: x, lastY: y, to: { x: x, y: y } };
  }
};

Visual.prototype.growTo = function(x, y) {
  if (this._grow)
    this._grow.to = { x: x, y: y };
};

Visual.prototype.endGrow = function(x, y) {
  this._grow = null;
  
  if (this._endGame == this.EndGame.running) {
    this._endGame = this.EndGame.released;
    this._endGameWhere = { x: x, y: y };
  }
};

Visual.prototype.dispose = function() {
  if (this._timer)
    clearTimeout(this._timer);
};

////////////////////////////////////////////////////
// Intro module.
State.intro = {
  enter: function() {
    Widgets.Transitions.fadeIn($("#id_menu"));
    
    if (navigator && navigator.userAgent) {      	
      if ((navigator.userAgent + "").toLowerCase().indexOf("419.3") >= 0)
        Widgets.MessageBox.notice("<div style='margin-top: -10px;'>Sorry, this game requires <span style='color: #faa;'>iPhone software version 2.0</span> or higher. It's a free update in your iTunes!<br/>Or you can play in Safari or Firefox on your desktop.</div>", null, true);
    }
  },
  leave: function() {
    Widgets.Transitions.fadeOut($("#id_menu"));
  }
};

////////////////////////////////////////////////////
// Help module.
State.help = {
  enter: function() {
    Widgets.Transitions.fadeIn($("#id_help"));
  },
  leave: function() {
    Widgets.Transitions.fadeOut($("#id_help"));
  }
};

////////////////////////////////////////////////////
// Scoreboard.
State.scoreboard = {
  enter: function() {
    $("#id_scoreboard_all").html("");
    $("#id_scoreboard_today").html("");
    
    Widgets.Transitions.show($("#id_scoreboard_loading"));
    
    $.ajax({type: "POST", url: Utility.formatUrl("/scoreboard/"), data: {},
      success: function(json) {
        var response = JSON.parse(json);
        if (response.errors) {          
        }
        else {
          Widgets.Transitions.hide($("#id_scoreboard_loading"));
          
          State.scoreboard.processResults($("#id_scoreboard_all"), response.all);
          State.scoreboard.processResults($("#id_scoreboard_today"), response.today);
        }
      },
      error: function() {        
      }}
    );
    
    Widgets.Transitions.fadeIn($("#id_scoreboard"));    
  },
  leave: function() {
    Widgets.Transitions.fadeOut($("#id_scoreboard"));
  },
  processResults: function(element, data) {
    if ($.isArray(data)) {
      var sb = new StringBuilder();
      
      for (var i = 0; i < data.length; ++i) {
        var d = data[i];
        if (d == null)
          continue;
        
        sb.appendList('<div class="scoreLine"><div class="n">', $.encode(d.n), '</div><div class="s">', $.encode(d.s), '</div></div>');        
      }
      
      element.html(sb.toString());
    }
  }
};

////////////////////////////////////////////////////
// Game module.
State.game = { _visual: null,
  enter: function() {
    Widgets.Transitions.fadeIn($("#id_lobby"));
    
    this._visual = new Visual(320, 310);
  },
  leave: function() {
    Widgets.Transitions.fadeOut($("#id_lobby"));
    
    if (this._visual)
      this._visual.dispose();
  },
  mousedown: function(x, y) {
    if (this._visual)
      this._visual.startGrow(x, y);
  },
  mousemove: function(x, y) {
    if (this._visual)
      this._visual.growTo(x, y);
  },
  mouseup: function(x, y) {
    if (this._visual)
      this._visual.endGrow(x, y);
  },
  visual: function() {
    return this._visual;
  }
};

////////////////////////////////////////////////////
// Game main state.
State.game.game = {  
  enter: function() {
    
  },
  leave: function() {
    
  }
}

////////////////////////////////////////////////////
// Game end.
State.game.end = {  
  enter: function() {
    var v = State.game._visual;
    
    var message = "";
    if (v._endGame == v.EndGame.noplace)
      message = "Woah, you win";
    else if (v._endGame == v.EndGame.out)
      message = "Out of bounds";
    else if (v._endGame == v.EndGame.released)
      message = "Lost control";
    else if (v._endGame == v.EndGame.tail)
      message = "Crossed path";      
      
    $("#id_end_reason").html(message);
    
    Widgets.Transitions.fadeIn($("#id_end"));
  },
  leave: function() {
    Widgets.Transitions.fadeOut($("#id_end"));
  }
}

////////////////////////////////////////////////////
// Utilities.

var Utility = {
  formatUrl: function(requestUrl) {
    var location = window.location.protocol + "//" + window.location.host;
    
    return location + SITE_PATH + requestUrl.substring(1);
  },
  formatName: function(name, anonymizeGuest) {
    if (name == null)
      return "";
    if (anonymizeGuest)
      return name.startsWith("__Guest") ? "Guest" : name;
    else
      if (name.startsWith("__"))
        return name.substring(2);
        
    return name;
  },
  unpackMessage: function(message) {
    return message.replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&amp;/g, "&");
  }
};

////////////////////////////////////////////////////
// Touch events.

function touchHandler(event)
{
  var touches = event.changedTouches, first = touches[0], type = "";
  
  if (first.target.id != "id_main_canvas")
    return;
  
  switch(event.type)
  {
    case "touchstart":
      State.game.mousedown(first.clientX, first.clientY);
      break;
    case "touchmove":
      State.game.mousemove(first.clientX, first.clientY);
      break;
    case "touchend":
      State.game.mouseup(first.clientX, first.clientY);
      break;
    default: return;
  }
  
  event.preventDefault();
}

function initTouchHandlers()
{
    document.addEventListener("touchstart", touchHandler, true);
    document.addEventListener("touchmove", touchHandler, true);
    document.addEventListener("touchend", touchHandler, true);    
} 

////////////////////////////////////////////////////
// Initialization.

$(function() {  
  // Kick off preloader.
  Widgets.load(function() {
    // Map default buttons for states.
    Widgets.map_default_button("game.end", "#action_end_continue");
    
    // Patch up images after preloading.
    Widgets.FormBuilder.setBackground($("#id_menu"), Widgets.backgrounds.main);
    Widgets.FormBuilder.setBackground($("#id_scoreboard"), Widgets.backgrounds.scores);
    Widgets.FormBuilder.setBackground($("#id_help"), Widgets.backgrounds.help);
    
    ////////////////////////////////////////////////////
    // Main frame.
    (new Widgets.FormBuilder({x: 0, y: 0, width: 320, height: 356 }))
      .visible(false).container($("#frame_lobby")).sizeContainer()
    ;
    
    ////////////////////////////////////////////////////  
    // Main menu.
    (new Widgets.FormBuilder({x: 0, y: 0, width: 320, height: 356, container: $("#id_menu") }))
      .sizeContainer()
      .size(140, 28).position(90, 150).button($("#action_play"), function() {
        State.go("game.game");
      })
      .offset(0, 40).button($("#action_hiscores"), function() {
        State.go("scoreboard");
      })
      .offset(0, 40).button($("#action_help"), function() {
        State.go("help");
      })
      .offset(0, 50).button($("#action_more"), function() {
        window.location = "http://www.underclouds.com";
      })
      .size(300, 30).position(10, 315).multiline($("#id_menu-copyright"), "tiny", "center")
    ;
      
    ////////////////////////////////////////////////////  
    // Scoreboard.
    (new Widgets.FormBuilder({x: 0, y: 0, width: 320, height: 356, container: $("#id_scoreboard") }))
      .sizeContainer()
      .size(150, 60).position(5, 60).place($("#id_scoreboard_all"))
      .offset(160, 0).place($("#id_scoreboard_today"))
      .size(140, 28).position(90, 160).text($("#id_scoreboard_loading"), "main", "center")
      .size(140, 28).position(90, 320).button($("#action_scoreboard_continue"), function() {
        State.go("intro");
      })
    ;
    
    ////////////////////////////////////////////////////  
    // Help.
    (new Widgets.FormBuilder({x: 0, y: 0, width: 320, height: 356, container: $("#id_help") }))
      .sizeContainer()
      .size(170, 60).position(145, 55).multiline($("#id_help_start"))
      .size(170, 90).position(145, 140).multiline($("#id_help_draw"))
      .size(170, 90).position(145, 220).multiline($("#id_help_warning"))
      .size(140, 28).position(90, 320).button($("#action_help_continue"), function() {
        State.go("intro");
      })
    ;     
    
    ////////////////////////////////////////////////////  
    // Main lobby.
    (new Widgets.FormBuilder({x: 0, y: 0, width: 320, height: 356, container: $("#id_lobby") }))
      .sizeContainer()
      .size(124, 24).position(160, 8).multiline($("#id_drag_label"), "hint", "right")
      .size(120, 24).position(32, 8).text($("#id_score_label"), "main")      
      .position(290, 8).image("drag")
      .position(4, 8).image("menu", function() {
        State.go("intro");
      })
      .position(0, 40).placePosition($("#id_background_canvas")).placePosition($("#id_main_canvas"))
    ;
      
    (new Widgets.FormBuilder({x: 0, y: 0, width: 320, height: 356, container: $("#id_end") }))
      .sizeContainer()
      .size(300, 50).position(10, 100).text($("#id_end_reason"), "large", "center")
      .size(200, 28).position(60, 170).text($("#id_name-name_label"), "main", "center")
      .size(200, 28).offset(0, 28).input($("#id_name-name"))
      .size(140, 28).offset(30, 40).button($("#action_end_continue"), function() {
        createCookie("name", $("#id_name-name").val(), 365);
        
        $.ajax({type: "POST", url: Utility.formatUrl("/score/"), data: { name: $("#id_name-name").val(), score: State.game.visual().score() },
          success: function(json) {
          },
          error: function() {            
          }}
        );
        
        State.go("intro");
      })
    ; 
      
    $("#id_main_canvas").mousedown(function(e) {
      var p = Widgets.mousePosition(this, e);
      
      State.game.mousedown(p.x, p.y);
    });
    $("#id_main_canvas").mousemove(function(e) {
      var p = Widgets.mousePosition(this, e);
      
      State.game.mousemove(p.x, p.y);
    });
    $("#id_main_canvas").mouseup(function(e) {
      var p = Widgets.mousePosition(this, e);
      
      State.game.mouseup(p.x, p.y);
    });
    
    // Setup touch events.
    if (Widgets.isApple)
      initTouchHandlers();
    
    // Begin game.
    State.go("launch");
    
    var name = readCookie("name");

    if (name && name.length > 0)
      $("#id_name-name").val(name);  
    else
      $("#id_name-name").val("");
  });
});
