var Settings = {
  center: new Vector(0, 200),
  radius: 180,
};

function fitScreenXY(x, y) {
  switch (window.orientation) {
    case 0: return {x:x, y:y};
    case -90: return {x:480 - p.y, y:p.x};
    case 90: return {x:p.y, y:268 - p.x};
    default: throw 'Invalid orientation: ' + window.orientation;
  }
}

function fitScreen(p) {
  if (!touch || p == null) return p;
  switch (window.orientation) {
    case 0: return p;
    case -90: return new Vector(480 - p.y, p.x);
    case 90: return new Vector(p.y, 268 - p.x);
    default: throw 'Invalid orientation: ' + window.orientation;
  }
}

function fitPhysical(x, y) {
  switch (window.orientation) {
    case 0: return {x:x, y:y};
    case 90: return {x:268 - y, y:x};
    case -90: return {x:y, y:480 - x};
    default: throw 'Invalid orientation: ' + window.orientation;
  }
}

var CanvasCircle = Class.create(Circle, {
  initialize: function($super, x, y, radius, fixed, mass, elasticity, friction, gc) {
    $super(x, y, radius, fixed, mass, elasticity, friction);
    this.gc = gc;
  },
 
  paint: function() {
    var c = fitScreenXY(this.get_x(), this.get_y());
    this.gc.moveTo(c.x, c.y);
    this.gc.arc(c.x, c.y, this.radius, 0, 2 * Math.PI, true);
  },
});

var CanvasLineSpring = Class.create(Spring, {
  initialize: function($super, p1, p2, stiffness, gc) {
    $super(p1, p2, stiffness);
    this.gc = gc;
  },
 
  paint: function() {
    this.gc.moveTo(this.p1.curr.x, this.p1.curr.y);
    this.gc.lineTo(this.p2.curr.x, this.p2.curr.y);
  },
});

var Oppai = Class.create(Group, {
  initialize: function($super, gc, pressure, maxDeg) {
    $super();
 
    this.gc = gc;
    this.pressure = pressure || 10;
    this.maxDeg = maxDeg || 90;
    this.initializePartices();
  },

  initializePartices: function() {
    var maxRad = this.maxDeg / 180.0 * Math.PI;
    var step = this.maxDeg * 2.0 / 10;
    var baseLength = 180;
    var centerY = 200;
    var radius = baseLength / Math.sin(maxRad);
    var centerX = -baseLength / Math.tan(maxRad);

    this.particles = [];
    this.constraints = [];
    var prevBody = null;
    for (var deg = -this.maxDeg; deg <= this.maxDeg; deg += step) {
      var rad = (deg / 180.0) * Math.PI;
      var x = radius * Math.cos(rad) + centerX;
      var y = radius * Math.sin(rad) + centerY;
      var fixed = Math.abs(deg) == Math.abs(this.maxDeg);
      var body = new CanvasCircle(x, y, 10, fixed, 1, 0.3, 0, this.gc);
      this.particles.push(body);
      if (prevBody) {
        var spring = new CanvasLineSpring(body, prevBody, 1, this.gc);
        this.constraints.push(spring);
      }
      prevBody = body;
    }
  },

  applyPressure: function() {
    for (var i = 1; i < this.particles.length - 1; i++) {
      var prev = this.particles[i-1];
      var curr = this.particles[i];
      var next = this.particles[i+1];
      var p2c = curr.get_position().minus(prev.get_position()).normalize();
      var p2n = next.get_position().minus(prev.get_position()).normalize();
      var f;
      if (0 < p2c.cross(p2n)) {
        var n2c = curr.get_position().minus(next.get_position()).normalize();
        f = p2c.add(n2c).normalize().mult(this.pressure);
      } else {
        var c2p = prev.get_position().minus(curr.get_position()).normalize();
        var c2n = next.get_position().minus(curr.get_position()).normalize();
        f = c2p.add(c2n).normalize().mult(this.pressure);
      }
      curr.addForce(f);
    }
  },

  paint: function() {
    var first = fitScreen(this.particles[0].get_position());
    this.gc.moveTo(first.x, first.y);
    for (var i = 1; i < this.particles.length - 1; i++) {
      var prev = fitScreen(this.particles[i-1].get_position());
      var curr = fitScreen(this.particles[i].get_position());
      var next = fitScreen(this.particles[i+1].get_position());
      var pc = prev.add(curr).mult(0.5);
      var cn = curr.add(next).mult(0.5);
      if (i == 1) {
        this.gc.lineTo(pc.x, pc.y);
      }
      this.gc.quadraticCurveTo(curr.x, curr.y, cn.x, cn.y);
    }
    var last = fitScreen(this.particles[this.particles.length - 1].get_position());
    this.gc.lineTo(last.x, last.y);
  },

  setPressure: function(pressure) {
    this.pressure = pressure;
  },

  setMaxDeg: function(maxDeg) {
    this.maxDeg = maxDeg;
    this.initializePartices();
  },

  setSize: function(size) {
    var nextDeg = Math.floor(6 + 6 * size) * 10;
    if (this.maxDeg == nextDeg) return;
    this.setMaxDeg(nextDeg);
  },
});

var Wall = Class.create(Group, {
  initialize: function($super, gc) {
    $super();
    this.gc = gc;
    var radius = 90 * 1.41421356;
    var center = new Vector(-95, Settings.center.y);
    var body = new CanvasCircle(center.x, center.y, radius, true, 1, 0.3, 0, gc);
    this.particles.push(body);
  },
});

var Hand = Class.create(Group, {
  initialize: function($super, gc) {
    $super();
    for (var i = 0; i < 5; i++) {
      this.particles.push(new CanvasCircle(-100, -100 * i, 40, true, 1, 0.3, 0, gc));
    }
  },
 
  moveFingersTo: function(points) {
    for (var i = 0; i < this.particles.length; i++) {
      if (points[i]) {
        if (points[i].pageX) {
          var point = fitPhysical(points[i].pageX, points[i].pageY); 
          this.particles[i].set_x(point.x);
          this.particles[i].set_y(point.y);
        } else {
          this.particles[i].set_x(points[i].x);
          this.particles[i].set_y(points[i].y);
        }
      } else {
        this.particles[i].set_x(-100);
        this.particles[i].set_y(-100 * i);
      }
    }
  },
});

var World = Class.create({
  initialize: function(canvasId) {
    this.gc = $(canvasId).getContext('2d');
    this.gc.strokeStyle = '#ffffff';
    this.gc.fillStyle = '#ffffff';
    this.oppai = new Oppai(this.gc);
    this.wall = new Wall(this.gc);
    this.wall.collisionList = [this.oppai];
    this.hand = new Hand(this.gc);
    this.hand.collisionList = [this.oppai];
    this.engine = new Engine(1/4);
    this.engine.gravity = new Vector(0, 3);
    this.engine.groups = [this.oppai, this.wall, this.hand];
  },

  moveFingersTo: function(points) {
    this.hand.moveFingersTo(points);
  },

  adjustGravity: function() {
    switch (window.orientation) {
      case 0: this.engine.gravity = new Vector(0, 3); return;
      case 90: this.engine.gravity = new Vector(-4, 0); return;
      case -90: this.engine.gravity = new Vector(4, 0); return;
    }
  },
 
  run: function() {
    this.gc.clearRect(0, 0, 500, 500);
 
    this.engine.step();
    this.oppai.applyPressure();
 
    this.gc.beginPath();
    this.oppai.paint();
    //this.engine.paint(); // debug
    this.gc.closePath();
    //this.gc.stroke();
    this.gc.fill();
  },
});

var Console = Class.create({
  initialize: function(baseId, buttonsId, world) {
    this.db = this.setupDB();
    this.elm = $(baseId);
    this.world = world;
    this.load();
  },

  setupDB: function() {
    if (!window.openDatabase) return;
    var db = openDatabase('paiTouch');
    db.transaction(function(tx) {
      // Check the existence of the paiTouchDB
      tx.executeSql(
        'SELECT COUNT(*) FROM settings', [], 
        function(tx, rs) {},
        function(error) {
          // If the paiTouchDB does not exist, it will be created.
          tx.executeSql(
            'CREATE TABLE settings (id INTEGER PRIMARY KEY, size REAL, pressure REAL, selected INTEGER)', [],
            function(tx, rs) {},
            function(error) {alert('Database Creation Error: ' + error.message)}
          )
          tx.executeSql(
            'INSERT INTO settings (size, pressure, selected) VALUES (?, ?, ?)', [90, 10, 1],
            function(tx, rs) {},
            function(error) {alert('Insertion Default Value Error: ' + error.message)}
          )
        }
      )
    });
    return db;
  },

  setup: function(point) {
    var x = point.x || point.pageX;
    var y = point.y || point.pageY;
    var size = x / 320.0;
    var pressure = (480 - y) / 20;
    this.world.oppai.setSize(size);
    this.world.oppai.setPressure(pressure);
    $('cursor').style.left = (x - 38) + 'px';
    $('cursor').style.top = (y - 38) + 'px';
  },

  show: function() {
    this.elm.show();
    $('close-button').show();
    $('tools-button').hide();
    $('cursor').style.top = (480 - this.world.oppai.pressure * 20 - 38) + 'px';
    $('cursor').style.left = ((this.world.oppai.maxDeg - 55) / 60 * 320 - 38) + 'px';
    if (this.db) {
      $('save-button').show();
      $('load-button').show();
    }
  },

  hide: function() {
    this.elm.hide();
    $('close-button').hide();
    $('tools-button').show();
    if (this.db) {
      $('save-button').hide();
      $('load-button').hide();
    }
  },
  
  save: function() {
    if (!this.db) return;
    var size = this.world.oppai.maxDeg;
    var pressure = this.world.oppai.pressure;
    this.db.transaction(
      function(tx) {
        tx.executeSql('SELECT COUNT(*) AS count_selected FROM settings WHERE selected = 1', [], function(tx, rs) {
          if (rs.rows.item(0).count_selected == 0) {
            tx.executeSql('INSERT INTO settings (size, pressure, selected) values (?, ?, ?)', [size, pressure, 1]);
          } else {
            tx.executeSql('UPDATE settings SET size = ?, pressure = ? WHERE selected = 1', [size, pressure]);
          }
        });
      },
      function(error) {alert('Fail to store: ' + error.message)},
      function() {}
    );
  },

  load: function() {
    if (!this.db) return;
    var oppai = this.world.oppai;
    this.db.transaction(
      function(tx) {
        tx.executeSql('SELECT * FROM settings WHERE selected = 1', [], function(tx, rs) {
          if (rs.rows.length == 0) {
            alert('No stored setting');
            return;
          }
          var row = rs.rows.item(0);
          oppai.setMaxDeg(row.size);
          oppai.setPressure(row.pressure);
        });
      },
      function(error) {alert('Fail to load: ' + error.message)},
      function() {}
    );
  },
});

var canvasId = 'canvas';
var baseId = 'console';
var buttonsId = 'console-buttons';
var clicked = false;
Event.observe(window, 'load', function(e) {
  var world = new World(canvasId);
  var console = new Console(baseId, buttonsId, world);
  world.run();
  setInterval(world.run.bind(world), 10);
  setTimeout(scrollTo, 500, 0, 1);

  Event.observe('tools-button', 'click', function(e) {console.show()}, false);
  Event.observe('close-button', 'click', function(e) {console.hide()}, false);
  Event.observe('save-button', 'click', function(e) {console.save()}, false);
  Event.observe('load-button', 'click', function(e) {console.load()}, false);
  if (touch) {
    // to touch boob
    Event.observe(canvas, 'touchstart', function(e) {world.moveFingersTo(e.touches)}, false);
    Event.observe(canvas, 'touchend', function(e) {world.moveFingersTo([])}, false);
    Event.observe(canvas, 'touchmove', function(e) {e.preventDefault(); world.moveFingersTo(e.touches)}, false);
    Event.observe(window, 'orientationchange', function(e) {window.scrollTo(0, 1); world.adjustGravity()}, false);

    // to set up boob
    //Event.observe(baseId, 'touchend', function(e) {console.setup(e.touches[0])}, false);
    Event.observe(baseId, 'touchmove', function(e) {e.preventDefault(); console.setup(e.touches[0])}, false);
  } else {
    // to touch boob
    Event.observe(canvasId, 'mousedown', function(e) {clicked = true});
    Event.observe(canvasId, 'mouseup', function(e) {clicked = false; world.moveFingersTo([{x:-100, y:-100}])});
    Event.observe(canvasId, 'mousemove', function(e) {if (clicked) {world.moveFingersTo([Event.pointer(e)])}});

    // to set up boob
    Event.observe(baseId, 'click', function(e) {console.setup(Event.pointer(e))});
  }
}, false);

