/*!
 * Cropper v0.7.9
 * https://github.com/fengyuanchen/cropper
 *
 * Copyright 2014-2015 Fengyuan Chen
 * Released under the MIT license
 */

(function (factory) {
  if (typeof define === "function" && define.amd) {
    // AMD. Register as anonymous module.
    define(["jquery"], factory);
  } else if (typeof exports === "object") {
    // Node / CommonJS
    factory(require("jquery"));
  } else {
    // Browser globals.
    factory(jQuery);
  }
})(function ($) {

  "use strict";

  var $window = $(window),
      $document = $(document),
      location = window.location,

      // Constants
      TRUE = true,
      FALSE = false,
      NULL = null,
      NAN = NaN,
      INFINITY = Infinity,
      STRING_UNDEFINED = "undefined",
      STRING_DIRECTIVE = "directive",
      CROPPER_NAMESPACE = ".cropper",

      // RegExps
      REGEXP_DIRECTIVES = /^(e|n|w|s|ne|nw|sw|se|all|crop|move|zoom)$/,
      REGEXP_OPTIONS = /^(x|y|width|height)$/,
      REGEXP_PROPERTIES = /^(naturalWidth|naturalHeight|width|height|aspectRatio|ratio|rotate)$/,

      // Classes
      CLASS_MODAL = "cropper-modal",
      CLASS_HIDDEN = "cropper-hidden",
      CLASS_INVISIBLE = "cropper-invisible",
      CLASS_MOVE = "cropper-move",
      CLASS_CROP = "cropper-crop",
      CLASS_DISABLED = "cropper-disabled",

      // Events
      EVENT_MOUSE_DOWN = "mousedown touchstart",
      EVENT_MOUSE_MOVE = "mousemove touchmove",
      EVENT_MOUSE_UP = "mouseup mouseleave touchend touchleave touchcancel",
      EVENT_WHEEL = "wheel mousewheel DOMMouseScroll",
      EVENT_RESIZE = "resize" + CROPPER_NAMESPACE, // Bind to window with namespace
      EVENT_DBLCLICK = "dblclick",
      EVENT_BUILD = "build" + CROPPER_NAMESPACE,
      EVENT_BUILT = "built" + CROPPER_NAMESPACE,
      EVENT_DRAG_START = "dragstart" + CROPPER_NAMESPACE,
      EVENT_DRAG_MOVE = "dragmove" + CROPPER_NAMESPACE,
      EVENT_DRAG_END = "dragend" + CROPPER_NAMESPACE,

      // Functions
      isNumber = function (n) {
        return typeof n === "number";
      },

      toArray = function (obj, offset) {
        var args = [];

        if (isNumber(offset)) { // It's necessary for IE8
          args.push(offset);
        }

        return args.slice.apply(obj, args);
      },

      // Custom proxy to avoid jQuery's guid
      proxy = function (fn, context) {
        var args = toArray(arguments, 2);

        return function () {
          return fn.apply(context, args.concat(toArray(arguments)));
        };
      },

      addTimestamp = function (url) {
        var timestamp = "timestamp=" + (new Date()).getTime();

        return (url + (url.indexOf("?") === -1 ? "?" : "&") + timestamp);
      },

      // Constructor
      Cropper = function (element, options) {
        this.element = element;
        this.$element = $(element);
        this.defaults = $.extend({}, Cropper.DEFAULTS, $.isPlainObject(options) ? options : {});
        this.$original = NULL;
        this.ready = FALSE;
        this.built = FALSE;
        this.cropped = FALSE;
        this.rotated = FALSE;
        this.disabled = FALSE;
        this.replaced = FALSE;
        this.init();
      },

      // Others
      sqrt = Math.sqrt,
      min = Math.min,
      max = Math.max,
      abs = Math.abs,
      sin = Math.sin,
      cos = Math.cos,
      num = parseFloat;

  Cropper.prototype = {
    constructor: Cropper,

    support: {
      canvas: $.isFunction($("<canvas>")[0].getContext)
    },

    init: function () {
      var defaults = this.defaults;

      $.each(defaults, function (i, n) {
        switch (i) {
          case "aspectRatio":
            defaults[i] = abs(num(n)) || NAN; // 0 -> NaN
            break;

          case "autoCropArea":
            defaults[i] = abs(num(n)) || 0.8; // 0 | NaN -> 0.8
            break;

          case "minWidth":
          case "minHeight":
            defaults[i] = abs(num(n)) || 0; // NaN -> 0
            break;

          case "maxWidth":
          case "maxHeight":
            defaults[i] = abs(num(n)) || INFINITY; // 0 | NaN -> Infinity
            break;

          // No default
        }
      });

      // Set default image data
      this.image = {
        rotate: 0
      };

      this.load();
    },

    load: function () {
      var _this = this,
          $this = this.$element,
          element = this.element,
          image = this.image,
          crossOrigin = "",
          $clone,
          url;

      if ($this.is("img")) {
        url = $this.prop("src");
      } else if ($this.is("canvas") && this.support.canvas) {
        url = element.toDataURL();
      }

      if (!url) {
        return;
      }

      // Reset image rotate degree
      if (this.replaced) {
        image.rotate = 0;
      }

      if (this.defaults.checkImageOrigin && this.isCrossOriginURL(url)) {
        crossOrigin = " crossOrigin";
        url = addTimestamp(url); // Bust cache (#119, #148)
      }

      this.$clone = ($clone = $("<img" + crossOrigin + ' src="' + url + '">'));

      $clone.one("load", function () {
        image.naturalWidth = this.naturalWidth || $clone.width();
        image.naturalHeight = this.naturalHeight || $clone.height();
        image.aspectRatio = image.naturalWidth / image.naturalHeight;

        _this.url = url;
        _this.ready = TRUE;
        _this.build();
      });

      // Hide and prepend the clone iamge to the document body (Don't append to).
      $clone.addClass(CLASS_INVISIBLE).prependTo("body");
    },

    isCrossOriginURL: function (url) {
      var parts = url.match(/^(https?:)\/\/([^\:\/\?#]+):?(\d*)/i);

      if (parts && (parts[1] !== location.protocol || parts[2] !== location.hostname || parts[3] !== location.port)) {
        return TRUE;
      }

      return FALSE;
    },

    build: function () {
      var $this = this.$element,
          defaults = this.defaults,
          buildEvent,
          $cropper;

      if (!this.ready) {
        return;
      }

      if (this.built) {
        this.unbuild();
      }

      $this.one(EVENT_BUILD, defaults.build); // Only trigger once
      buildEvent = $.Event(EVENT_BUILD);
      $this.trigger(buildEvent);

      if (buildEvent.isDefaultPrevented()) {
        return;
      }

      // Create cropper elements
      this.$cropper = ($cropper = $(Cropper.TEMPLATE));

      // Hide the original image
      $this.addClass(CLASS_HIDDEN);

      // Show and prepend the clone iamge to the cropper
      this.$clone.removeClass(CLASS_INVISIBLE).prependTo($cropper);

      // Save original image for rotation
      if (!this.rotated) {
        this.$original = this.$clone.clone();

        // Append the image to document to avoid "NS_ERROR_NOT_AVAILABLE" error on Firefox when call the "drawImage" method.
        this.$original.addClass(CLASS_HIDDEN).prependTo(this.$cropper);

        this.originalImage = $.extend({}, this.image);
      }

      this.$container = $this.parent();
      this.$container.append($cropper);

      this.$canvas = $cropper.find(".cropper-canvas");
      this.$dragger = $cropper.find(".cropper-dragger");
      this.$viewer = $cropper.find(".cropper-viewer");

      defaults.autoCrop ? (this.cropped = TRUE) : this.$dragger.addClass(CLASS_HIDDEN);
      defaults.modal && this.$canvas.addClass(CLASS_MODAL);
      !defaults.dashed && this.$dragger.find(".cropper-dashed").addClass(CLASS_HIDDEN);
      !defaults.movable && this.$dragger.find(".cropper-face").data(STRING_DIRECTIVE, "move");
      !defaults.resizable && this.$dragger.find(".cropper-line, .cropper-point").addClass(CLASS_HIDDEN);

      this.addListeners();
      this.initPreview();

      this.built = TRUE; // Set `true` before update
      defaults.dragCrop && this.setDragMode("crop"); // Set after built
      this.update();
      this.replaced = FALSE; // Reset to `false` after update

      $this.one(EVENT_BUILT, defaults.built); // Only trigger once
      $this.trigger(EVENT_BUILT);
    },

    unbuild: function () {
      if (!this.built) {
        return;
      }

      this.built = FALSE;
      this.removeListeners();

      this.$preview.empty();
      this.$preview = NULL;

      this.$dragger = NULL;
      this.$canvas = NULL;
      this.$container = NULL;

      this.$cropper.remove();
      this.$cropper = NULL;
    },

    update: function (data) {
      this.initContainer();
      this.initCropper();
      this.initImage();
      this.initDragger();

      if (data) {
        this.setData(data, TRUE);
        this.setDragMode("crop");
      } else {
        this.setData(this.defaults.data);
      }
    },

    resize: function () {
      clearTimeout(this.resizing);
      this.resizing = setTimeout($.proxy(this.update, this, this.getData()), 200);
    },

    preview: function () {
      var image = this.image,
          dragger = this.dragger,
          width = image.width,
          height = image.height,
          left = dragger.left - image.left,
          top = dragger.top - image.top;

      this.$viewer.find("img").css({
        width: width,
        height: height,
        marginLeft: -left,
        marginTop: -top
      });

      this.$preview.each(function () {
        var $this = $(this),
            data = $this.data(),
            ratio = data.width / dragger.width,
            newWidth = data.width,
            newHeight = dragger.height * ratio;

        if (newHeight > data.height) {
          ratio = data.height / dragger.height,
          newWidth = dragger.width * ratio;
          newHeight = data.height;
        }

        $this.width(newWidth).height(newHeight).find("img").css({
          width: width * ratio,
          height: height * ratio,
          marginLeft: -left * ratio,
          marginTop: -top * ratio
        });
      });
    },

    addListeners: function () {
      var defaults = this.defaults;

      this.$element.on(EVENT_DRAG_START, defaults.dragstart).on(EVENT_DRAG_MOVE, defaults.dragmove).on(EVENT_DRAG_END, defaults.dragend);
      this.$cropper.on(EVENT_MOUSE_DOWN, $.proxy(this.dragstart, this)).on(EVENT_DBLCLICK, $.proxy(this.dblclick, this));

      if (defaults.zoomable) {
        this.$cropper.on(EVENT_WHEEL, $.proxy(this.wheel, this));
      }

      if (defaults.multiple) {
        this.$cropper.on(EVENT_MOUSE_MOVE, $.proxy(this.dragmove, this)).on(EVENT_MOUSE_UP, $.proxy(this.dragend, this));
      } else {
        $document.on(EVENT_MOUSE_MOVE, (this._dragmove = proxy(this.dragmove, this))).on(EVENT_MOUSE_UP, (this._dragend = proxy(this.dragend, this)));
      }

      $window.on(EVENT_RESIZE, (this._resize = proxy(this.resize, this)));
    },

    removeListeners: function () {
      var defaults = this.defaults;

      this.$element.off(EVENT_DRAG_START, defaults.dragstart).off(EVENT_DRAG_MOVE, defaults.dragmove).off(EVENT_DRAG_END, defaults.dragend);
      this.$cropper.off(EVENT_MOUSE_DOWN, this.dragstart).off(EVENT_DBLCLICK, this.dblclick);

      if (defaults.zoomable) {
        this.$cropper.off(EVENT_WHEEL, this.wheel);
      }

      if (defaults.multiple) {
        this.$cropper.off(EVENT_MOUSE_MOVE, this.dragmove).off(EVENT_MOUSE_UP, this.dragend);
      } else {
        $document.off(EVENT_MOUSE_MOVE, this._dragmove).off(EVENT_MOUSE_UP, this._dragend);
      }

      $window.off(EVENT_RESIZE, this._resize);
    },

    initPreview: function () {
      var url = this.url;

      this.$preview = $(this.defaults.preview);
      this.$viewer.html('<img src="' + url + '">');

      this.$preview.each(function () {
        var $this = $(this);

        $this.data({
          width: $this.width(),
          height: $this.height()
        }).html('<img src="' + url + '" style="display:block;width:100%;min-width:0!important;min-height:0!important;max-width:none!important;max-height:none!important;">');
      });
    },

    initContainer: function () {
      var $this = this.$element,
          $container = this.$container,
          $cropper = this.$cropper,
          defaults = this.defaults;

      $cropper.addClass(CLASS_HIDDEN);
      $this.removeClass(CLASS_HIDDEN);

      this.container = {
        width: max($container.width(), defaults.minContainerWidth),
        height: max($container.height(), defaults.minContainerHeight)
      };

      $this.addClass(CLASS_HIDDEN);
      $cropper.removeClass(CLASS_HIDDEN);
    },

    initCropper: function () {
      var container = this.container,
          image = this.image,
          cropper;

      if (((image.naturalWidth * container.height / image.naturalHeight) - container.width) >= 0) {
        cropper = {
          width: container.width,
          height: container.width / image.aspectRatio,
          left: 0
        };

        cropper.top = (container.height - cropper.height) / 2;
      } else {
        cropper = {
          width: container.height * image.aspectRatio,
          height: container.height,
          top: 0
        };

        cropper.left = (container.width - cropper.width) / 2;
      }

      this.$cropper.css({
        width: cropper.width,
        height: cropper.height,
        left: cropper.left,
        top: cropper.top
      });

      this.cropper = cropper;
    },

    initImage: function () {
      var image = this.image,
          cropper = this.cropper,
          defaultImage = {
            _width: cropper.width,
            _height: cropper.height,
            width: cropper.width,
            height: cropper.height,
            left: 0,
            top: 0,
            ratio: cropper.width / image.naturalWidth
          };

      this.defaultImage = $.extend({}, image, defaultImage);

      if (image._width !== cropper.width || image._height !== cropper.height) {
        $.extend(image, defaultImage);
      } else {
        image = $.extend({}, defaultImage, image);

        // Reset image ratio
        if (this.replaced) {
          image.ratio = defaultImage.ratio;
        }
      }

      this.image = image;
      this.renderImage();
    },

    renderImage: function (mode) {
      var image = this.image;

      if (mode === "zoom") {
        image.left -= (image.width - image.oldWidth) / 2;
        image.top -= (image.height - image.oldHeight) / 2;
      }

      image.left = min(max(image.left, image._width - image.width), 0);
      image.top = min(max(image.top, image._height - image.height), 0);

      this.$clone.css({
        width: image.width,
        height: image.height,
        marginLeft: image.left,
        marginTop: image.top
      });

      if (mode) {
        this.defaults.done(this.getData());
        this.preview();
      }
    },

    initDragger: function () {
      var defaults = this.defaults,
          cropper = this.cropper,
          // If not set, use the original aspect ratio of the image.
          aspectRatio = defaults.aspectRatio || this.image.aspectRatio,
          ratio = this.image.ratio,
          autoCropDragger,
          dragger;

      if (((cropper.height * aspectRatio) - cropper.width) >= 0) {
        dragger = {
          height: cropper.width / aspectRatio,
          width: cropper.width,
          left: 0,
          top: (cropper.height - (cropper.width / aspectRatio)) / 2,
          maxWidth: cropper.width,
          maxHeight: cropper.width / aspectRatio
        };
      } else {
        dragger = {
          height: cropper.height,
          width: cropper.height * aspectRatio,
          left: (cropper.width - (cropper.height * aspectRatio)) / 2,
          top: 0,
          maxWidth: cropper.height * aspectRatio,
          maxHeight: cropper.height
        };
      }

      dragger.minWidth = 0;
      dragger.minHeight = 0;

      if (defaults.aspectRatio) {
        if (isFinite(defaults.maxWidth)) {
          dragger.maxWidth = min(dragger.maxWidth, defaults.maxWidth * ratio);
          dragger.maxHeight = dragger.maxWidth / aspectRatio;
        } else if (isFinite(defaults.maxHeight)) {
          dragger.maxHeight = min(dragger.maxHeight, defaults.maxHeight * ratio);
          dragger.maxWidth = dragger.maxHeight * aspectRatio;
        }

        if (defaults.minWidth > 0) {
          dragger.minWidth = max(0, defaults.minWidth * ratio);
          dragger.minHeight = dragger.minWidth / aspectRatio;
        } else if (defaults.minHeight > 0) {
          dragger.minHeight = max(0, defaults.minHeight * ratio);
          dragger.minWidth = dragger.minHeight * aspectRatio;
        }
      } else {
        dragger.maxWidth = min(dragger.maxWidth, defaults.maxWidth * ratio);
        dragger.maxHeight = min(dragger.maxHeight, defaults.maxHeight * ratio);
        dragger.minWidth = max(0, defaults.minWidth * ratio);
        dragger.minHeight = max(0, defaults.minHeight * ratio);
      }

      // minWidth can't be greater than maxWidth, and minHeight too.
      dragger.minWidth = min(dragger.maxWidth, dragger.minWidth);
      dragger.minHeight = min(dragger.maxHeight, dragger.minHeight);

      // Center the dragger by default
      autoCropDragger = $.extend({}, dragger);

      // The width of auto crop area must large than minWidth, and the height too. (#164)
      autoCropDragger.width = max(dragger.minWidth, dragger.width * defaults.autoCropArea);
      autoCropDragger.height = max(dragger.minHeight, dragger.height * defaults.autoCropArea);
      autoCropDragger.left = (cropper.width - autoCropDragger.width) / 2;
      autoCropDragger.top = (cropper.height - autoCropDragger.height) / 2;

      autoCropDragger.oldLeft = dragger.oldLeft = dragger.left;
      autoCropDragger.oldTop = dragger.oldTop = dragger.top;

      this.autoCropDragger = autoCropDragger;
      this.defaultDragger = $.extend({}, dragger);
      this.dragger = dragger;
    },

    renderDragger: function () {
      var dragger = this.dragger,
          cropper = this.cropper;

      if (dragger.width > dragger.maxWidth) {
        dragger.width = dragger.maxWidth;
        dragger.left = dragger.oldLeft;
      } else if (dragger.width < dragger.minWidth) {
        dragger.width = dragger.minWidth;
        dragger.left = dragger.oldLeft;
      }

      if (dragger.height > dragger.maxHeight) {
        dragger.height = dragger.maxHeight;
        dragger.top = dragger.oldTop;
      } else if (dragger.height < dragger.minHeight) {
        dragger.height = dragger.minHeight;
        dragger.top = dragger.oldTop;
      }

      dragger.left = min(max(dragger.left, 0), cropper.width - dragger.width);
      dragger.top = min(max(dragger.top, 0), cropper.height - dragger.height);
      dragger.oldLeft = dragger.left;
      dragger.oldTop = dragger.top;

      // Re-render the dragger
      this.dragger = dragger;

      // #186
      if (this.defaults.movable) {
        this.$dragger.find(".cropper-face").data(STRING_DIRECTIVE, (dragger.width === cropper.width && dragger.height === cropper.height) ? "move" : "all");
      }

      if (!this.disabled) {
        this.defaults.done(this.getData());
      }

      this.$dragger.css({
        width: dragger.width,
        height: dragger.height,
        left: dragger.left,
        top: dragger.top
      });

      this.preview();
    },

    reset: function (deep) {
      if (!this.cropped || this.disabled) {
        return;
      }

      if (deep) {
        this.defaults.data = {};
      }

      this.image = $.extend({}, this.defaultImage);
      this.renderImage();
      this.dragger = $.extend({}, this.defaultDragger);
      this.setData(this.defaults.data);
    },

    clear: function () {
      if (!this.cropped || this.disabled) {
        return;
      }

      this.cropped = FALSE;

      this.setData({
        x: 0,
        y: 0,
        width: 0,
        height: 0
      });

      this.$canvas.removeClass(CLASS_MODAL);
      this.$dragger.addClass(CLASS_HIDDEN);
    },

    destroy: function () {
      var $this = this.$element;

      if (!this.ready) {
        this.$clone.off("load").remove();
      }

      this.unbuild();
      $this.removeClass(CLASS_HIDDEN).removeData("cropper");

      if (this.rotated) {
        $this.attr("src", this.$original.attr("src"));
      }
    },

    replace: function (url, /*INTERNAL*/ rotated) {
      var _this = this,
          $this = this.$element,
          element = this.element,
          context;

      if (!this.disabled && url && url !== this.url && url !== $this.attr("src")) {
        if (!rotated) {
          this.rotated = FALSE;
          this.replaced = TRUE;
        }

        if ($this.is("img")) {
          $this.attr("src", url);
          this.load();
        } else if ($this.is("canvas") && this.support.canvas) {
          context = element.getContext("2d");

          $('<img src="' + url + '">').one("load", function () {
            element.width = this.width;
            element.height = this.height;
            context.clearRect(0, 0, element.width, element.height);
            context.drawImage(this, 0, 0);
            _this.load();
          });
        }
      }
    },

    setData: function (data, /*INTERNAL*/ once) {
      var cropper = this.cropper,
          dragger = this.dragger,
          image = this.image,
          aspectRatio = this.defaults.aspectRatio;

      if (!this.built || this.disabled || typeof data === STRING_UNDEFINED) {
        return;
      }

      if (data === NULL || $.isEmptyObject(data)) {
        dragger = $.extend({}, this.autoCropDragger);
      }

      if ($.isPlainObject(data) && !$.isEmptyObject(data)) {

        if (!once) {
          this.defaults.data = data;
        }

        data = this.transformData(data);

        if (isNumber(data.x) && data.x <= cropper.width - image.left) {
          dragger.left = data.x + image.left;
        }

        if (isNumber(data.y) && data.y <= cropper.height - image.top) {
          dragger.top = data.y + image.top;
        }

        if (aspectRatio) {
          if (isNumber(data.width) && data.width <= dragger.maxWidth && data.width >= dragger.minWidth) {
            dragger.width = data.width;
            dragger.height = dragger.width / aspectRatio;
          } else if (isNumber(data.height) && data.height <= dragger.maxHeight && data.height >= dragger.minHeight) {
            dragger.height = data.height;
            dragger.width = dragger.height * aspectRatio;
          }
        } else {
          if (isNumber(data.width) && data.width <= dragger.maxWidth && data.width >= dragger.minWidth) {
            dragger.width = data.width;
          }

          if (isNumber(data.height) && data.height <= dragger.maxHeight && data.height >= dragger.minHeight) {
            dragger.height = data.height;
          }
        }
      }

      this.dragger = dragger;
      this.renderDragger();
    },

    getData: function (rounded) {
      var dragger = this.dragger,
          image = this.image,
          data = {};

      if (this.built) {
        data = {
          x: dragger.left - image.left,
          y: dragger.top - image.top,
          width: dragger.width,
          height: dragger.height
        };

        data = this.transformData(data, TRUE, rounded);
      }

      return data;
    },

    transformData: function (data, reversed, rounded) {
      var ratio = this.image.ratio,
          result = {};

      $.each(data, function (i, n) {
        n = num(n);

        if (REGEXP_OPTIONS.test(i) && !isNaN(n)) {
          result[i] = reversed ? (rounded ? Math.round(n / ratio) : n / ratio) : n * ratio;
        }
      });

      return result;
    },

    setAspectRatio: function (aspectRatio) {
      var freeRatio = aspectRatio === "auto";

      if (this.disabled) {
        return;
      }

      aspectRatio = num(aspectRatio);

      if (freeRatio || (!isNaN(aspectRatio) && aspectRatio > 0)) {
        this.defaults.aspectRatio = freeRatio ? NAN : aspectRatio;

        if (this.built) {
          this.initDragger();
          this.renderDragger();
          this.setData(this.defaults.data); // Reset to initial state
        }
      }
    },

    getImageData: function () {
      var data = {};

      if (this.ready) {
        $.each(this.image, function (name, value) {
          if (REGEXP_PROPERTIES.test(name)) {
            data[name] = value;
          }
        });
      }

      return data;
    },

    getDataURL: function (options, type, quality) {
      var canvas = $("<canvas>")[0],
          data = this.getData(),
          dataURL = "",
          context;

      if (!$.isPlainObject(options)) {
        quality = type;
        type = options;
        options = {};
      }

      options = $.extend({
        width: data.width,
        height: data.height
      }, options);

      if (this.cropped && this.support.canvas) {
        canvas.width = options.width;
        canvas.height = options.height;
        context = canvas.getContext("2d");

        if (type === "image/jpeg") {
          context.fillStyle = "#fff";
          context.fillRect(0, 0, options.width, options.height);
        }

        context.drawImage(this.$clone[0], data.x, data.y, data.width, data.height, 0, 0, options.width, options.height);
        dataURL = canvas.toDataURL(type, quality);
      }

      return dataURL;
    },

    setDragMode: function (mode) {
      var $canvas = this.$canvas,
          defaults = this.defaults,
          cropable = FALSE,
          movable = FALSE;

      if (!this.built || this.disabled) {
        return;
      }

      switch (mode) {
        case "crop":
          if (defaults.dragCrop) {
            cropable = TRUE;
            $canvas.data(STRING_DIRECTIVE, mode);
          }

          break;

        case "move":
          movable = TRUE;
          $canvas.data(STRING_DIRECTIVE, mode);

          break;

        default:
          $canvas.removeData(STRING_DIRECTIVE);
      }

      $canvas.toggleClass(CLASS_CROP, cropable).toggleClass(CLASS_MOVE, movable);
    },

    enable: function () {
      if (this.built) {
        this.disabled = FALSE;
        this.$cropper.removeClass(CLASS_DISABLED);
      }
    },

    disable: function () {
      if (this.built) {
        this.disabled = TRUE;
        this.$cropper.addClass(CLASS_DISABLED);
      }
    },

    rotate: function (degree) {
      var image = this.image;

      degree = num(degree) || 0;

      if (!this.built || degree === 0 || this.disabled || !this.defaults.rotatable || !this.support.canvas) {
        return;
      }

      this.rotated = TRUE;
      degree = (image.rotate = (image.rotate + degree) % 360);

       // replace with "true" to prevent to override the original image
      this.replace(this.getRotatedDataURL(degree), true);
    },

    getRotatedDataURL: function (degree) {
      var canvas = $("<canvas>")[0],
          context = canvas.getContext("2d"),
          originalImage = this.originalImage,
          naturalWidth = originalImage.naturalWidth,
          naturalHeight = originalImage.naturalHeight,
          deg = abs(degree) % 180,
          arc = (deg > 90 ? (180 - deg) : deg) * Math.PI / 180,
          width = naturalWidth * cos(arc) + naturalHeight * sin(arc),
          height = naturalWidth * sin(arc) + naturalHeight * cos(arc);

      canvas.width = width;
      canvas.height = height;
      context.save();
      context.translate(width / 2, height / 2);
      context.rotate(degree * Math.PI / 180);
      context.drawImage(this.$original[0], -naturalWidth / 2, -naturalHeight / 2, naturalWidth, naturalHeight);
      context.restore();

      return canvas.toDataURL();
    },

    zoom: function (delta) {
      var image = this.image,
          width,
          height,
          range;

      delta = num(delta);

      if (!this.built || !delta || this.disabled || !this.defaults.zoomable) {
        return;
      }

      width = image.width * (1 + delta);
      height = image.height * (1 + delta);
      range = width / image._width;

      if (range > 10) {
        return;
      }

      if (range < 1) {
        width = image._width;
        height = image._height;
      }

      if (range <= 1) {
        this.setDragMode("crop");
      } else {
        this.setDragMode("move");
      }

      image.oldWidth = image.width;
      image.oldHeight = image.height;

      image.width = width;
      image.height = height;
      image.ratio = image.width / image.naturalWidth;

      this.renderImage("zoom");
    },

    dblclick: function () {
      if (this.disabled) {
        return;
      }

      if (this.$canvas.hasClass(CLASS_CROP)) {
        this.setDragMode("move");
      } else {
        this.setDragMode("crop");
      }
    },

    wheel: function (event) {
      var e = event.originalEvent,
          delta = 1;

      if (this.disabled) {
        return;
      }

      event.preventDefault();

      if (e.deltaY) {
        delta = e.deltaY > 0 ? 1 : -1;
      } else if (e.wheelDelta) {
        delta = -e.wheelDelta / 120;
      } else if (e.detail) {
        delta = e.detail > 0 ? 1 : -1;
      }

      this.zoom(delta * 0.1);
    },

    dragstart: function (event) {
      var touches = event.originalEvent.touches,
          e = event,
          directive,
          dragStartEvent,
          touchesLength;

      if (this.disabled) {
        return;
      }

      if (touches) {
        touchesLength = touches.length;

        if (touchesLength > 1) {
          if (this.defaults.zoomable && touchesLength === 2) {
            e = touches[1];
            this.startX2 = e.pageX;
            this.startY2 = e.pageY;
            directive = "zoom";
          } else {
            return;
          }
        }

        e = touches[0];
      }

      directive = directive || $(e.target).data(STRING_DIRECTIVE);

      if (REGEXP_DIRECTIVES.test(directive)) {
        event.preventDefault();

        dragStartEvent = $.Event(EVENT_DRAG_START);
        this.$element.trigger(dragStartEvent);

        if (dragStartEvent.isDefaultPrevented()) {
          return;
        }

        this.directive = directive;
        this.cropping = FALSE;
        this.startX = e.pageX;
        this.startY = e.pageY;

        if (directive === "crop") {
          this.cropping = TRUE;
          this.$canvas.addClass(CLASS_MODAL);
        }
      }
    },

    dragmove: function (event) {
      var touches = event.originalEvent.touches,
          e = event,
          dragMoveEvent,
          touchesLength;

      if (this.disabled) {
        return;
      }

      if (touches) {
        touchesLength = touches.length;

        if (touchesLength > 1) {
          if (this.defaults.zoomable && touchesLength === 2) {
            e = touches[1];
            this.endX2 = e.pageX;
            this.endY2 = e.pageY;
          } else {
            return;
          }
        }

        e = touches[0];
      }

      if (this.directive) {
        event.preventDefault();

        dragMoveEvent = $.Event(EVENT_DRAG_MOVE);
        this.$element.trigger(dragMoveEvent);

        if (dragMoveEvent.isDefaultPrevented()) {
          return;
        }

        this.endX = e.pageX;
        this.endY = e.pageY;

        this.dragging();
      }
    },

    dragend: function (event) {
      var dragEndEvent;

      if (this.disabled) {
        return;
      }

      if (this.directive) {
        event.preventDefault();

        dragEndEvent = $.Event(EVENT_DRAG_END);
        this.$element.trigger(dragEndEvent);

        if (dragEndEvent.isDefaultPrevented()) {
          return;
        }

        if (this.cropping) {
          this.cropping = FALSE;
          this.$canvas.toggleClass(CLASS_MODAL, this.cropped && this.defaults.modal);
        }

        this.directive = "";
      }
    },

    dragging: function () {
      var directive = this.directive,
          image = this.image,
          cropper = this.cropper,
          maxWidth = cropper.width,
          maxHeight = cropper.height,
          dragger = this.dragger,
          width = dragger.width,
          height = dragger.height,
          left = dragger.left,
          top = dragger.top,
          right = left + width,
          bottom = top + height,
          renderable = TRUE,
          aspectRatio = this.defaults.aspectRatio,
          range = {
            x: this.endX - this.startX,
            y: this.endY - this.startY
          },
          offset;

      if (aspectRatio) {
        range.X = range.y * aspectRatio;
        range.Y = range.x / aspectRatio;
      }

      switch (directive) {
        // Move dragger
        case "all":
          left += range.x;
          top += range.y;

          break;

        // Resize dragger
        case "e":
          if (range.x >= 0 && (right >= maxWidth || aspectRatio && (top <= 0 || bottom >= maxHeight))) {
            renderable = FALSE;
            break;
          }

          width += range.x;

          if (aspectRatio) {
            height = width / aspectRatio;
            top -= range.Y / 2;
          }

          if (width < 0) {
            directive = "w";
            width = 0;
          }

          break;

        case "n":
          if (range.y <= 0 && (top <= 0 || aspectRatio && (left <= 0 || right >= maxWidth))) {
            renderable = FALSE;
            break;
          }

          height -= range.y;
          top += range.y;

          if (aspectRatio) {
            width = height * aspectRatio;
            left += range.X / 2;
          }

          if (height < 0) {
            directive = "s";
            height = 0;
          }

          break;

        case "w":
          if (range.x <= 0 && (left <= 0 || aspectRatio && (top <= 0 || bottom >= maxHeight))) {
            renderable = FALSE;
            break;
          }

          width -= range.x;
          left += range.x;

          if (aspectRatio) {
            height = width / aspectRatio;
            top += range.Y / 2;
          }

          if (width < 0) {
            directive = "e";
            width = 0;
          }

          break;

        case "s":
          if (range.y >= 0 && (bottom >= maxHeight || aspectRatio && (left <= 0 || right >= maxWidth))) {
            renderable = FALSE;
            break;
          }

          height += range.y;

          if (aspectRatio) {
            width = height * aspectRatio;
            left -= range.X / 2;
          }

          if (height < 0) {
            directive = "n";
            height = 0;
          }

          break;

        case "ne":
          if (aspectRatio) {
            if (range.y <= 0 && (top <= 0 || right >= maxWidth)) {
              renderable = FALSE;
              break;
            }

            height -= range.y;
            top += range.y;
            width = height * aspectRatio;
          } else {
            if (range.x >= 0) {
              if (right < maxWidth) {
                width += range.x;
              } else if (range.y <= 0 && top <= 0) {
                renderable = FALSE;
              }
            } else {
              width += range.x;
            }

            if (range.y <= 0) {
              if (top > 0) {
                height -= range.y;
                top += range.y;
              }
            } else {
              height -= range.y;
              top += range.y;
            }
          }

          if (width < 0 && height < 0) {
            directive = "sw";
            height = 0;
            width = 0;
          } else if (width < 0) {
            directive = "nw";
            width = 0;
          } else if (height < 0) {
            directive = "se";
            height = 0;
          }

          break;

        case "nw":
          if (aspectRatio) {
            if (range.y <= 0 && (top <= 0 || left <= 0)) {
              renderable = FALSE;
              break;
            }

            height -= range.y;
            top += range.y;
            width = height * aspectRatio;
            left += range.X;
          } else {
            if (range.x <= 0) {
              if (left > 0) {
                width -= range.x;
                left += range.x;
              } else if (range.y <= 0 && top <= 0) {
                renderable = FALSE;
              }
            } else {
              width -= range.x;
              left += range.x;
            }

            if (range.y <= 0) {
              if (top > 0) {
                height -= range.y;
                top += range.y;
              }
            } else {
              height -= range.y;
              top += range.y;
            }
          }

          if (width < 0 && height < 0) {
            directive = "se";
            height = 0;
            width = 0;
          } else if (width < 0) {
            directive = "ne";
            width = 0;
          } else if (height < 0) {
            directive = "sw";
            height = 0;
          }

          break;

        case "sw":
          if (aspectRatio) {
            if (range.x <= 0 && (left <= 0 || bottom >= maxHeight)) {
              renderable = FALSE;
              break;
            }

            width -= range.x;
            left += range.x;
            height = width / aspectRatio;
          } else {
            if (range.x <= 0) {
              if (left > 0) {
                width -= range.x;
                left += range.x;
              } else if (range.y >= 0 && bottom >= maxHeight) {
                renderable = FALSE;
              }
            } else {
              width -= range.x;
              left += range.x;
            }

            if (range.y >= 0) {
              if (bottom < maxHeight) {
                height += range.y;
              }
            } else {
              height += range.y;
            }
          }

          if (width < 0 && height < 0) {
            directive = "ne";
            height = 0;
            width = 0;
          } else if (width < 0) {
            directive = "se";
            width = 0;
          } else if (height < 0) {
            directive = "nw";
            height = 0;
          }

          break;

        case "se":
          if (aspectRatio) {
            if (range.x >= 0 && (right >= maxWidth || bottom >= maxHeight)) {
              renderable = FALSE;
              break;
            }

            width += range.x;
            height = width / aspectRatio;
          } else {
            if (range.x >= 0) {
              if (right < maxWidth) {
                width += range.x;
              } else if (range.y >= 0 && bottom >= maxHeight) {
                renderable = FALSE;
              }
            } else {
              width += range.x;
            }

            if (range.y >= 0) {
              if (bottom < maxHeight) {
                height += range.y;
              }
            } else {
              height += range.y;
            }
          }

          if (width < 0 && height < 0) {
            directive = "nw";
            height = 0;
            width = 0;
          } else if (width < 0) {
            directive = "sw";
            width = 0;
          } else if (height < 0) {
            directive = "ne";
            height = 0;
          }

          break;

        // Move image
        case "move":
          image.left += range.x;
          image.top += range.y;
          this.renderImage("move");
          renderable = FALSE;
          break;

        // Scale image
        case "zoom":
          this.zoom(function (x, y, x1, y1, x2, y2) {
            return (sqrt(x2 * x2 + y2 * y2) - sqrt(x1 * x1 + y1 * y1)) / sqrt(x * x + y * y);
          }(
            image.width,
            image.height,
            abs(this.startX - this.startX2),
            abs(this.startY - this.startY2),
            abs(this.endX - this.endX2),
            abs(this.endY - this.endY2)
          ));

          this.endX2 = this.startX2;
          this.endY2 = this.startY2;
          renderable = FALSE;
          break;

        // Crop image
        case "crop":
          if (range.x && range.y) {
            offset = this.$cropper.offset();
            left = this.startX - offset.left;
            top = this.startY - offset.top;
            width = dragger.minWidth;
            height = dragger.minHeight;

            if (range.x > 0) {
              if (range.y > 0) {
                directive = "se";
              } else {
                directive = "ne";
                top -= height;
              }
            } else {
              if (range.y > 0) {
                directive = "sw";
                left -= width;
              } else {
                directive = "nw";
                left -= width;
                top -= height;
              }
            }

            // Show the dragger if is hidden
            if (!this.cropped) {
              this.cropped = TRUE;
              this.$dragger.removeClass(CLASS_HIDDEN);
            }
          }

          break;

        // No default
      }

      if (renderable) {
        dragger.width = width;
        dragger.height = height;
        dragger.left = left;
        dragger.top = top;
        this.directive = directive;

        this.renderDragger();
      }

      // Override
      this.startX = this.endX;
      this.startY = this.endY;
    }
  };

  // Use the string compressor: Strmin (https://github.com/fengyuanchen/strmin)
  Cropper.TEMPLATE = (function (source, words) {
    words = words.split(",");
    return source.replace(/\d+/g, function (i) {
      return words[i];
    });
  })('<0 6="5-container"><0 6="5-canvas"></0><0 6="5-dragger"><1 6="5-viewer"></1><1 6="5-8 8-h"></1><1 6="5-8 8-v"></1><1 6="5-face" 3-2="all"></1><1 6="5-7 7-e" 3-2="e"></1><1 6="5-7 7-n" 3-2="n"></1><1 6="5-7 7-w" 3-2="w"></1><1 6="5-7 7-s" 3-2="s"></1><1 6="5-4 4-e" 3-2="e"></1><1 6="5-4 4-n" 3-2="n"></1><1 6="5-4 4-w" 3-2="w"></1><1 6="5-4 4-s" 3-2="s"></1><1 6="5-4 4-ne" 3-2="ne"></1><1 6="5-4 4-nw" 3-2="nw"></1><1 6="5-4 4-sw" 3-2="sw"></1><1 6="5-4 4-se" 3-2="se"></1></0></0>', "div,span,directive,data,point,cropper,class,line,dashed");

  /* Template source:
  <div class="cropper-container">
    <div class="cropper-canvas"></div>
    <div class="cropper-dragger">
      <span class="cropper-viewer"></span>
      <span class="cropper-dashed dashed-h"></span>
      <span class="cropper-dashed dashed-v"></span>
      <span class="cropper-face" data-directive="all"></span>
      <span class="cropper-line line-e" data-directive="e"></span>
      <span class="cropper-line line-n" data-directive="n"></span>
      <span class="cropper-line line-w" data-directive="w"></span>
      <span class="cropper-line line-s" data-directive="s"></span>
      <span class="cropper-point point-e" data-directive="e"></span>
      <span class="cropper-point point-n" data-directive="n"></span>
      <span class="cropper-point point-w" data-directive="w"></span>
      <span class="cropper-point point-s" data-directive="s"></span>
      <span class="cropper-point point-ne" data-directive="ne"></span>
      <span class="cropper-point point-nw" data-directive="nw"></span>
      <span class="cropper-point point-sw" data-directive="sw"></span>
      <span class="cropper-point point-se" data-directive="se"></span>
    </div>
  </div>
  */

  Cropper.DEFAULTS = {
    // Basic
    aspectRatio: "auto",
    autoCropArea: 0.8, // 80%
    data: {
      // x: 0,
      // y: 0,
      // width: 300,
      // height: 150
    },
    done: $.noop,
    preview: "",

    // Toggles
    multiple: FALSE,
    autoCrop: TRUE,
    dragCrop: TRUE,
    dashed: TRUE,
    modal: TRUE,
    movable: TRUE,
    resizable: TRUE,
    zoomable: TRUE,
    rotatable: TRUE,
    checkImageOrigin: TRUE,

    // Dimensions
    minWidth: 0,
    minHeight: 0,
    maxWidth: INFINITY,
    maxHeight: INFINITY,
    minContainerWidth: 300,
    minContainerHeight: 150,

    // Events
    build: NULL,
    built: NULL,
    dragstart: NULL,
    dragmove: NULL,
    dragend: NULL
  };

  Cropper.setDefaults = function (options) {
    $.extend(Cropper.DEFAULTS, options);
  };

  // Save the other cropper
  Cropper.other = $.fn.cropper;

  // Register as jQuery plugin
  $.fn.cropper = function (options) {
    var args = toArray(arguments, 1),
        result;

    this.each(function () {
      var $this = $(this),
          data = $this.data("cropper"),
          fn;

      if (!data) {
        $this.data("cropper", (data = new Cropper(this, options)));
      }

      if (typeof options === "string" && $.isFunction((fn = data[options]))) {
        result = fn.apply(data, args);
      }
    });

    return (typeof result !== STRING_UNDEFINED ? result : this);
  };

  $.fn.cropper.Constructor = Cropper;
  $.fn.cropper.setDefaults = Cropper.setDefaults;

  // No conflict
  $.fn.cropper.noConflict = function () {
    $.fn.cropper = Cropper.other;
    return this;
  };
});
