(function() {

  /**
   *
   * Backbone Game Engine - An elementary HTML5 canvas game engine using Backbone.
   *
   * Copyright (c) 2014 Martin Drapeau
   * https://github.com/martindrapeau/backbone-game-engine
   *
   */

  var keyboardImg = new Image();
  keyboardImg.src = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMAAAAAwCAYAAABHTnUeAAACYklEQVR42u3dS5KCQBAEUNx5LJbcYs42t2DJsViOizFCnQH6U9Wd2ZW5JuQVmtAoEd4mRQmcW2+AovSMCqCEjgqghM5hAbZt++qNe808z9852zP7me1s/n8LgDZAyiCj+JntjP4/BUAd4GqQEfzMdla/CgDkZ7az+lUAID+zndWvAgD5me2sfhUAyM9sZ/WrAEB+ZjurXwUA8jPbWf0qAJCf2c7qVwGA/Mx2Vr8KAORntrP6VQAgf6798VpFhsd+zO0W/itX7vYpfvMCPJGlB7lkCEu/d1SAc/+RLWfbHL9pAV6RoxXAqtgqwPUMnz6LD/+R36QAVsDSIWr9uTPWzNb6HsDyvfE69mdGb39VAc7OQCMXoGbGlgWwPjF5Hvuj9X3tuv/KX1SAlEtvhAKUzNqqAB5X5ZYFONmHqT+rAKVrztSkDIdYACu/ld1rSep97K+OsUeBQxag5xzeBfC8H2tx8jl6b7wK7LIEut/v07qu1eDUIaz9tUEqgOVSVAVIxFqBU4co9edkWZZp3/fTbXovgby/jQu/BMqBj1YA9JvgFu9B+JvgnCEiFADla9BWJyB9DZqQ51Jh5AIg/RA2wtWX9oewHulZAIube4RHIX73ZWov8R/NQPcoRMvoYbi31yp2IBRguIfhWkQFeHutYgdiAYZ4HNo7KgCmndWvAgD5me2sfhUAyM9sZ/WrAEB+ZjurXwUA8jPbWf0qAJCf2c7qVwGA/Mx2Vr8KAORntrP6VQAgP7Od1a8CAPmZ7ax+/UkemJ/ZzujX36QC+pntbH79UbYSOiqAEjoqgBI6KoASOj+RrUxeKnOx4gAAAABJRU5ErkJggg==";

  Backbone.InputButton = Backbone.Element.extend({
    defaults: _.extend({}, Backbone.Element.prototype.defaults, {
      // Relative position references
      left: undefined,
      right: undefined,
      top: undefined,
      bottom: undefined,
      backgroundColor: "transparent",
      pressed: false
    }),
    onAttach: function() {
      Backbone.Element.prototype.onAttach.apply(this, arguments);
      this.stopListening();
      this.calculatePosition();
    },
    onDetach: function() {
      Backbone.Element.prototype.onDetach.apply(this, arguments);
      this.set("pressed", false);
    },
    calculatePosition: function() {
      // Set x and y based on relative position references
      if (!this.engine) return this;
      var canvas = this.engine.canvas,
          attrs = {};

      if (this.attributes.left !== undefined)
        attrs.x = this.attributes.left;
      else if (this.attributes.right !== undefined)
        attrs.x = canvas.width - this.attributes.right - this.attributes.width;
      else
        throw "InputButton " + this.id + " missing left or right attributes.";

      if (this.attributes.top !== undefined)
        attrs.y = this.attributes.top;
      else if (this.attributes.bottom !== undefined)
        attrs.y = canvas.height - this.attributes.bottom - this.attributes.height;
      else
        throw "InputButton " + this.id + " missing top or bottom attributes.";

      this.set(attrs);
      return this;
    }
  });

  Backbone.LeftInputButton = Backbone.InputButton.extend({
    onDraw: function(context) {
      var x = this.get("x"),
          y = this.get("y"),
          width = this.get("width"),
          height = this.get("height"),
          pressed = this.get("pressed");
      //drawRect(context, x, y, width, height, "rgba(64, 64, 64, 0.5)");
      context.save();
      context.beginPath();
      context.moveTo(x+130, y+20);
      context.lineTo(x+40, y+80);
      context.lineTo(x+130, y+140);
      context.lineTo(x+100, y+80);
      context.lineTo(x+130, y+20);
      context.lineJoin = 'bevel';
      context.fillStyle = pressed ? "#00FF00" : "#009900";
      context.fill();
      context.lineWidth = 5;
      context.strokeStyle = '#111';
      context.stroke();
      context.restore();
      return this;
    }
  });

  Backbone.RightInputButton = Backbone.InputButton.extend({
    onDraw: function(context) {
      var x = this.get("x"),
          y = this.get("y"),
          width = this.get("width"),
          height = this.get("height"),
          pressed = this.get("pressed");
      //drawRect(context, x, y, width, height, "rgba(128, 128, 128, 0.5)");
      context.save();
      context.beginPath();
      context.moveTo(x+30, y+20);
      context.lineTo(x+120, y+80);
      context.lineTo(x+30, y+140);
      context.lineTo(x+60, y+80);
      context.lineTo(x+30, y+20);
      context.lineJoin = 'bevel';
      context.fillStyle = pressed ? "#00FF00" : "#009900";
      context.fill();
      context.lineWidth = 5;
      context.strokeStyle = '#111';
      context.stroke();
      context.restore();
      return this;
    }
  });

  Backbone.AInputButton = Backbone.InputButton.extend({
    onDraw: function(context) {
      var x = this.get("x"),
          y = this.get("y"),
          width = this.get("width"),
          height = this.get("height"),
          pressed = this.get("pressed");
      //drawRect(context, x, y, width, height, "rgba(255, 255, 255, 0.5)");
      context.save();
      context.beginPath();
      context.arc(x+80, y+80, 60, 0, 2*Math.PI, false);
      context.fillStyle = "#111";
      context.fill();
      context.beginPath();
      context.arc(x+80, y+80, 55, 0, 2*Math.PI, false);
      context.fillStyle = pressed ? "#0000FF" : "#000099";
      context.fill();
      context.restore();
      return this;
    }
  });

  Backbone.BInputButton = Backbone.InputButton.extend({
    onDraw: function(context) {
      var x = this.get("x"),
          y = this.get("y"),
          width = this.get("width"),
          height = this.get("height"),
          pressed = this.get("pressed");
      //drawRect(context, x, y, width, height, "rgba(192, 192, 192, 0.5)");
      context.save();
      context.beginPath();
      context.arc(x+80, y+80, 60, 0, 2*Math.PI, false);
      context.fillStyle = "#111";
      context.fill();
      context.beginPath();
      context.arc(x+80, y+80, 55, 0, 2*Math.PI, false);
      context.fillStyle = pressed ? "#FF0000" : "#990000";
      context.fill();
      context.restore();
      return this;
    }
  });


  // Input class; a Backbone Model which captures input events
  // and stores them as model attributes with true if pressed.
  // Supports keyboard, and a drawn touchpad activated by touch
  // or mouse events.
  Backbone.Input = Backbone.Model.extend({
    defaults: {
      // Supported buttons
      left: false, // Left button pressed?
      right: false, // Right button pressed?
      buttonA: false, // A button pressed? (X on keyboard)
      buttonB: false, // B button pressed? (Z on keyboard)

      // Touch pad
      drawTouchpad: "auto", // Boolean to draw. Set to auto to draw only for touch devices.
      touchEnabled: false // Touch device? Automatically determined. Do not set.
    },
    buttons: [{
      id: "left", name: "left-input-button",
      left: 0, bottom: 0, width: 160, height: 160
    },  {
      id: "right", name: "right-input-button",
      left: 160, bottom: 0, width: 160, height: 160
    },  {
      id: "buttonA", name: "a-input-button",
      right: 0, bottom: 0, width: 160, height: 160
    },  {
      id: "buttonB", name: "b-input-button",
      right: 160, bottom: 0, width: 160, height: 160
    }],
    initialize: function(attributes, options) {
      options || (options = {});
      this._ongoingTouches = [];

      _.bindAll(this, "rightPressed", "leftPressed", "buttonBPressed", "buttonAPressed");

      for (var i = 0; i < this.buttons.length; i++) {
        var config = this.buttons[i],
            cls = _.classify(config.name);
        config.instance = new Backbone[cls](config);
      }

      // Handle touch events
      var touchEnabled =
        window.forceTouchpad ||
        "ontouchstart" in window ||
        "onorientationchange" in window ||
        window.navigator.msMaxTouchPoints ||
        window.navigator.maxTouchPoints > 0 ||
        window.navigator.isCocoonJS;
      this.set({touchEnabled: touchEnabled});

      // Debug panel
      var debugPanel = this.debugPanel = options.debugPanel;
      if (debugPanel) {
        this.on("change:pressed", function() {
          debugPanel.set({pressed: this.get("pressed")});
        });
        this.on("change:touched", function() {
          debugPanel.set({touched: this.get("touched")});
        });
        this.on("change:clicked", function() {
          debugPanel.set({clicked: this.get("clicked")});
        });
      }

      if (touchEnabled) {
        // Prevent touch scroll
        $(document).bind("touchmove.InputTouchScroll", function(e) {
          e.preventDefault();
          e.stopPropagation();
          return false;
        });

        // Prevent links from opening popup after a while
        document.documentElement.style.webkitTouchCallout = "none";
      }

      this.on("attach", this.onAttach, this);
      this.on("detach", this.onDetach, this);
    },
    onAttach: function() {
      this.onDetach();
      // Handle keyboard input
      $(document).on("keydown.Input", this.onKeydown.bind(this));
      $(document).on("keyup.Input", this.onKeyup.bind(this));
      
      if (this.hasTouchpad()) {
        if (this.get("touchEnabled")) {
          if (window.navigator.msMaxTouchPoints) {
            $(document).on("pointerdown.InputTouchpad", this.onTouchStart.bind(this));
            $(document).on("pointermove.InputTouchpad", this.onTouchMove.bind(this));
            $(document).on("pointerup.InputTouchpad", this.onTouchEnd.bind(this));
            $(document).on("pointercancel.InputTouchpad", this.onTouchEnd.bind(this));
          } else {
            $(document).on("touchstart.InputTouchpad", this.onTouchStart.bind(this));
            $(document).on("touchmove.InputTouchpad", this.onTouchMove.bind(this));
            $(document).on("touchend.InputTouchpad", this.onTouchEnd.bind(this));
            $(document).on("touchleave.InputTouchpad", this.onTouchEnd.bind(this));
            $(document).on("touchcancel.InputTouchpad", this.onTouchEnd.bind(this));
          }
        } else {
          // Fallback to handling mouse events
          $(document).on("mousedown.InputTouchpad", this.onMouseDown.bind(this));
          $(document).on("mousemove.InputTouchpad", this.onMouseDown.bind(this));
          $(document).on("mouseup.InputTouchpad", this.onMouseUp.bind(this));
        }
        for (var i = 0; i < this.buttons.length; i++) {
          this.buttons[i].instance.engine = this.engine;
          this.buttons[i].instance.trigger("attach");
        }
      }
    },
    onDetach: function() {
      $(document).off(".Input");
      this._ongoingTouches = [];
      this.set({
        left: false,
        right: false,
        buttonA: false,
        buttonB: false
      });
      if (this.hasTouchpad()) {
        $(document).off(".InputTouchpad");
        for (var i = 0; i < this.buttons.length; i++) {
          this.buttons[i].instance.trigger("detach");
          this.buttons[i].instance.engine = undefined;
        }
      }
    },
    hasTouchpad: function() {
      var drawTouchpad = this.get("drawTouchpad");
      if (_.isBoolean(drawTouchpad)) return drawTouchpad;
      if (drawTouchpad == "auto" && this.get("touchEnabled")) return true;
      return false;
    },

    // Engine core functions
    update: function(dt) {
      return true;
    },
    draw: function(context, options) {
      if (this.hasTouchpad()) {
        for (var i = 0; i < this.buttons.length; i++)
          this.buttons[i].instance.draw(context);
      } else{
        context.drawImage(keyboardImg, 8, context.canvas.height - 56);
      }

      return this;
    },

    // Keyboard events
    onKeydown: function(e) {
      var buttonId = this.keyCodeToButtonId(e.keyCode),
          attrs = {};
      attrs[e.keyCode] = true;
      if (buttonId) {
        attrs[buttonId] = true;
        if (this.hasTouchpad())
          for (var i = 0; i < this.buttons.length; i++)
            if (this.buttons[i].instance.id == buttonId)
              this.buttons[i].instance.set("pressed", true);
      }
      this.set(attrs);
    },
    onKeyup: function(e) {
      var buttonId = this.keyCodeToButtonId(e.keyCode),
          attrs = {};
      attrs[e.keyCode] = false;
      if (buttonId) {
        attrs[buttonId] = false;
        if (this.hasTouchpad())
          for (var i = 0; i < this.buttons.length; i++)
            if (this.buttons[i].instance.id == buttonId)
              this.buttons[i].instance.set("pressed", false);
      }
      this.set(attrs);
    },

    // Touch events
    detectTouched: function() {
      var attrs = {};
      var canvas = this.engine.canvas;
      // 计算 CSS 缩放比例
      var scaleX = canvas.width / canvas.offsetWidth;
      var scaleY = canvas.height / canvas.offsetHeight;

      for (var i = 0; i < this.buttons.length; i++) {
        var button = this.buttons[i].instance,
            touched = false;
        for (var t = 0; t < this._ongoingTouches.length; t++) {
          // 转换触摸坐标到 canvas 坐标系
          var touchX = (this._ongoingTouches[t].pageX - canvas.offsetLeft) * scaleX;
          var touchY = (this._ongoingTouches[t].pageY - canvas.offsetTop) * scaleY;
          touched = button.overlaps(touchX, touchY);
          if (touched) break;
        }
        attrs[button.id] = touched;
        button.set("pressed", touched);
      }

      this.set(attrs);

      return this;
    },
    onTouchStart: function(e) {
      var touches = e.changedTouches || [{
          identifier: e.pointerId,
          pageX: e.pageX,
          pageY: e.pageY
        }];

      for (var i = 0; i < touches.length; i++)
        this._ongoingTouches.push(this._copyTouch(touches[i]));
      this.detectTouched();
    },
    onTouchMove: function(e) {
      var touches = e.changedTouches || [{
          identifier: e.pointerId,
          pageX: e.pageX,
          pageY: e.pageY
        }];

      for (var i = 0; i < touches.length; i++) {
        var idx = this._ongoingTouchIndexById(touches[i].identifier);
        if (idx >= 0) this._ongoingTouches.splice(idx, 1, this._copyTouch(touches[i]));
      }

      this.detectTouched();
    },
    onTouchEnd: function(e) {
      var touches = e.changedTouches || [{
          identifier: e.pointerId,
          pageX: e.pageX,
          pageY: e.pageY
        }];

      for (var i=0; i < touches.length; i++) {
        var idx = this._ongoingTouchIndexById(touches[i].identifier);
        if (idx >= 0) this._ongoingTouches.splice(idx, 1);
      }

      this.detectTouched();
    },

    // Mouse events
    onMouseDown: function(e) {
      if (!e.which) return;

      var canvas = this.engine.canvas;
      // 计算 CSS 缩放比例
      var scaleX = canvas.width / canvas.offsetWidth;
      var scaleY = canvas.height / canvas.offsetHeight;
      var x = (e.pageX - canvas.offsetLeft) * scaleX,
          y = (e.pageY - canvas.offsetTop) * scaleY,
          attrs = {};

      for (var i = 0; i < this.buttons.length; i++) {
        var button = this.buttons[i].instance,
            clicked = button.overlaps(x, y);
        attrs[button.id] = clicked;
        button.set("pressed", clicked);
      }

      this.set(attrs);
    },
    onMouseUp: function(e) {
      for (var i = 0; i < this.buttons.length; i++) {
        var button = this.buttons[i].instance;
        if (this.get(button.id)) {
          this.set(button.id, false);
          button.set("pressed", false);
        }
      }
    },

    // Button helpers
    rightPressed: function() {
      return !!this.get("right");
    },
    leftPressed: function() {
      return !!this.get("left");
    },
    buttonBPressed: function() {
      return !!this.get("buttonB");
    },
    buttonAPressed: function() {
      return !!this.get("buttonA");
    },
    keyCodeToButtonId: function(keyCode) {
      switch (keyCode) {
        case 39:
        return "right";
        case 37:
        return "left";
        case 90:
        return "buttonB";
        case 88:
        return "buttonA";
      }
      return null;
    },
    // Touch event helpers.
    // Source: https://developer.mozilla.org/en-US/docs/Web/Guide/Events/Touch_events
    _copyTouch: function(touch) {
      return {
        identifier: touch.identifier,
        pageX: touch.pageX,
        pageY: touch.pageY
      };
    },
    _ongoingTouchIndexById: function(idToFind) {
      for (var i = 0; i < this._ongoingTouches.length; i++) {
        var id = this._ongoingTouches[i].identifier;
        if (id == idToFind) return i;
      }
      return -1;
    }
  });

}).call(this);