if (!RedactorPlugins) var RedactorPlugins = {};

RedactorPlugins.definedlinks = function()
{
	return {
		init: function()
		{
			if (!this.opts.definedLinks) return;

			this.modal.addCallback('link', $.proxy(this.definedlinks.load, this));

		},
		load: function()
		{
			var $select = $('<select id="redactor-defined-links" />');
			$('#redactor-modal-link-insert').prepend($select);

			this.definedlinks.storage = {};

			$.getJSON(this.opts.definedLinks, $.proxy(function(data)
			{
				$.each(data, $.proxy(function(key, val)
				{
					this.definedlinks.storage[key] = val;
					$select.append($('<option>').val(key).html(val.name));

				}, this));

				$select.on('change', $.proxy(this.definedlinks.select, this));

			}, this));

		},
		select: function(e)
		{
			var key = $(e.target).val();
			var name = '', url = '';
			if (key !== 0)
			{
				name = this.definedlinks.storage[key].name;
				url = this.definedlinks.storage[key].url;
			}

			$('#redactor-link-url').val(url);

			var $el = $('#redactor-link-url-text');
			if ($el.val() === '') $el.val(name);
		}
	};
};

RedactorPlugins.fontcolor = function()
{
	return {
		init: function()
		{
			var colors = [
				'#ffffff', '#000000', '#eeece1', '#1f497d', '#4f81bd', '#c0504d', '#9bbb59', '#8064a2', '#4bacc6', '#f79646', '#ffff00',
				'#f2f2f2', '#7f7f7f', '#ddd9c3', '#c6d9f0', '#dbe5f1', '#f2dcdb', '#ebf1dd', '#e5e0ec', '#dbeef3', '#fdeada', '#fff2ca',
				'#d8d8d8', '#595959', '#c4bd97', '#8db3e2', '#b8cce4', '#e5b9b7', '#d7e3bc', '#ccc1d9', '#b7dde8', '#fbd5b5', '#ffe694',
				'#bfbfbf', '#3f3f3f', '#938953', '#548dd4', '#95b3d7', '#d99694', '#c3d69b', '#b2a2c7', '#b7dde8', '#fac08f', '#f2c314',
				'#a5a5a5', '#262626', '#494429', '#17365d', '#366092', '#953734', '#76923c', '#5f497a', '#92cddc', '#e36c09', '#c09100',
				'#7f7f7f', '#0c0c0c', '#1d1b10', '#0f243e', '#244061', '#632423', '#4f6128', '#3f3151', '#31859b',  '#974806', '#7f6000'
			];

			var buttons = ['fontcolor', 'backcolor'];

			for (var i = 0; i < 2; i++)
			{
				var name = buttons[i];

				var button = this.button.addBefore('deleted', name, this.lang.get(name));
				var $dropdown = this.button.addDropdown(button);

				$dropdown.width(242);
				this.fontcolor.buildPicker($dropdown, name, colors);

			}
		},
		buildPicker: function($dropdown, name, colors)
		{
			var rule = (name == 'backcolor') ? 'background-color' : 'color';

			var len = colors.length;
			var self = this;
			var func = function(e)
			{
				e.preventDefault();
				self.fontcolor.set($(this).data('rule'), $(this).attr('rel'));
			};

			for (var z = 0; z < len; z++)
			{
				var color = colors[z];

				var $swatch = $('<a rel="' + color + '" data-rule="' + rule +'" href="#" style="float: left; font-size: 0; border: 2px solid #fff; padding: 0; margin: 0; width: 22px; height: 22px;"></a>');
				$swatch.css('background-color', color);
				$swatch.on('click', func);

				$dropdown.append($swatch);
			}

			var $elNone = $('<a href="#" style="display: block; clear: both; padding: 5px; font-size: 12px; line-height: 1;"></a>').html(this.lang.get('none'));
			$elNone.on('click', $.proxy(function(e)
			{
				e.preventDefault();
				this.fontcolor.remove(rule);

			}, this));

			$dropdown.append($elNone);
		},
		set: function(rule, type)
		{
			this.inline.format('span', 'style', rule + ': ' + type + ';');
		},
		remove: function(rule)
		{
			this.inline.removeStyleRule(rule);
		}
	};
};

RedactorPlugins.fontfamily = function()
{
	return {
		init: function ()
		{
			var fonts = [ 'Arial', 'Helvetica', 'Georgia', 'Times New Roman', 'Monospace' ];
			var that = this;
			var dropdown = {};

			$.each(fonts, function(i, s)
			{
				dropdown['s' + i] = { title: '<span style="font-family:' + s.toLowerCase() + ';">' +
                    s + '</span>', func: function() { that.fontfamily.set(s); }};
			});

			dropdown.remove = { title: __('Remove Font Family'), func: that.fontfamily.reset };

			var button = this.button.addBefore('bold', 'fontfamily', __('Change Font Family'));
			this.button.addDropdown(button, dropdown);

		},
		set: function (value)
		{
			this.inline.format('span', 'style', 'font-family:' + value + ';');
		},
		reset: function()
		{
			this.inline.removeStyleRule('font-family');
		}
	};
};

RedactorPlugins.fullscreen = function()
{
	return {
		init: function()
		{
			this.fullscreen.isOpen = false;

			var button = this.button.add('fullscreen', 'Fullscreen');
			this.button.addCallback(button, this.fullscreen.toggle);

			if (this.opts.fullscreen) this.fullscreen.toggle();
		},
		enable: function()
		{
			this.button.changeIcon('fullscreen', 'normalscreen');
			this.button.setActive('fullscreen');
			this.fullscreen.isOpen = true;

			if (this.opts.toolbarExternal)
			{
				this.fullscreen.toolcss = {};
				this.fullscreen.boxcss = {};
				this.fullscreen.toolcss.width = this.$toolbar.css('width');
				this.fullscreen.toolcss.top = this.$toolbar.css('top');
				this.fullscreen.toolcss.position = this.$toolbar.css('position');
				this.fullscreen.boxcss.top = this.$box.css('top');
			}

			this.fullscreen.height = this.$editor.height();

			if (this.opts.maxHeight) this.$editor.css('max-height', '');
			if (this.opts.minHeight) this.$editor.css('min-height', '');

			if (!this.$fullscreenPlaceholder) this.$fullscreenPlaceholder = $('<div/>');
			this.$fullscreenPlaceholder.insertAfter(this.$box);

			this.$box.appendTo(document.body);

			this.$box.addClass('redactor-box-fullscreen');
			$('body, html').css('overflow', 'hidden');

			this.fullscreen.resize();
			$(window).on('resize.redactor.fullscreen', $.proxy(this.fullscreen.resize, this));
			$(document).scrollTop(0, 0);

			this.$editor.focus();
			this.observe.load();
		},
		disable: function()
		{
			this.button.removeIcon('fullscreen', 'normalscreen');
			this.button.setInactive('fullscreen');
			this.fullscreen.isOpen = false;

			$(window).off('resize.redactor.fullscreen');
			$('body, html').css('overflow', '');

			this.$box.insertBefore(this.$fullscreenPlaceholder);
			this.$fullscreenPlaceholder.remove();

			this.$box.removeClass('redactor-box-fullscreen').css({ width: 'auto', height: 'auto' });

			this.code.sync();

			if (this.opts.toolbarExternal)
			{
				this.$box.css('top', this.fullscreen.boxcss.top);
				this.$toolbar.css({
					'width': this.fullscreen.toolcss.width,
					'top': this.fullscreen.toolcss.top,
					'position': this.fullscreen.toolcss.position
				});
			}

			if (this.opts.minHeight) this.$editor.css('minHeight', this.opts.minHeight);
			if (this.opts.maxHeight) this.$editor.css('maxHeight', this.opts.maxHeight);

			this.$editor.css('height', 'auto');
			this.$editor.focus();
			this.observe.load();
		},
		toggle: function()
		{
			if (this.fullscreen.isOpen)
			{
				this.fullscreen.disable();
			}
			else
			{
				this.fullscreen.enable();
			}
		},
		resize: function()
		{
			if (!this.fullscreen.isOpen) return;

			var toolbarHeight = this.$toolbar.height();

			var height = $(window).height() - toolbarHeight;
			this.$box.width($(window).width() - 2).height(height + toolbarHeight);

			if (this.opts.toolbarExternal)
			{
				this.$toolbar.css({
					'top': '0px',
					'position': 'absolute',
					'width': '100%'
				});

				this.$box.css('top', toolbarHeight + 'px');
			}

			this.$editor.height(height - 14);
		}
	};
};

RedactorPlugins.imagemanager = function()
{
	return {
		init: function()
		{
			if (!this.opts.imageManagerJson) return;

			this.modal.addCallback('image', this.imagemanager.load);
		},
		load: function()
		{
			var $modal = this.modal.getModal();

			this.modal.createTabber($modal);
			this.modal.addTab(1, 'Upload', 'active');
			this.modal.addTab(2, 'Choose');

			$('#redactor-modal-image-droparea').addClass('redactor-tab redactor-tab1');

			var $box = $('<div id="redactor-image-manager-box" style="overflow: auto; height: 300px;" class="redactor-tab redactor-tab2">').hide();
			$modal.append($box);

			$.ajax({
			  dataType: "json",
			  cache: false,
			  url: this.opts.imageManagerJson,
			  success: $.proxy(function(data)
				{
					$.each(data, $.proxy(function(key, val)
					{
						// title
						var thumbtitle = '';
						if (typeof val.title !== 'undefined') thumbtitle = val.title;

						var img = $('<img src="' + val.thumb + '" rel="' + val.image + '" title="' + thumbtitle + '" style="width: 100px; height: 75px; cursor: pointer;" />');
						$('#redactor-image-manager-box').append(img);
						$(img).click($.proxy(this.imagemanager.insert, this));

					}, this));


				}, this)
			});


		},
		insert: function(e)
		{
			this.image.insert('<img src="' + $(e.target).attr('rel') + '" alt="' + $(e.target).attr('title') + '">');
		}
	};
};

RedactorPlugins.table = function()
{
	return {
		getTemplate: function()
		{
			return String()
			+ '<section id="redactor-modal-table-insert">'
				+ '<label>' + this.lang.get('rows') + '</label>'
				+ '<input type="text" size="5" value="2" id="redactor-table-rows" />'
				+ '<label>' + this.lang.get('columns') + '</label>'
				+ '<input type="text" size="5" value="3" id="redactor-table-columns" />'
			+ '</section>';
		},
		init: function()
		{

			var dropdown = {};

			dropdown.insert_table = { title: this.lang.get('insert_table'), func: this.table.show };
			dropdown.insert_row_above = { title: this.lang.get('insert_row_above'), func: this.table.addRowAbove };
			dropdown.insert_row_below = { title: this.lang.get('insert_row_below'), func: this.table.addRowBelow };
			dropdown.insert_column_left = { title: this.lang.get('insert_column_left'), func: this.table.addColumnLeft };
			dropdown.insert_column_right = { title: this.lang.get('insert_column_right'), func: this.table.addColumnRight };
			dropdown.add_head = { title: this.lang.get('add_head'), func: this.table.addHead };
			dropdown.delete_head = { title: this.lang.get('delete_head'), func: this.table.deleteHead };
			dropdown.delete_column = { title: this.lang.get('delete_column'), func: this.table.deleteColumn };
			dropdown.delete_row = { title: this.lang.get('delete_row'), func: this.table.deleteRow };
			dropdown.delete_table = { title: this.lang.get('delete_table'), func: this.table.deleteTable };

			this.observe.addButton('td', 'table');
			this.observe.addButton('th', 'table');

			var button = this.button.addBefore('link', 'table', this.lang.get('table'));
			this.button.addDropdown(button, dropdown);
		},
		show: function()
		{
			this.modal.addTemplate('table', this.table.getTemplate());

			this.modal.load('table', this.lang.get('insert_table'), 300);
			this.modal.createCancelButton();

			var button = this.modal.createActionButton(this.lang.get('insert'));
			button.on('click', this.table.insert);

			this.selection.save();
			this.modal.show();

			$('#redactor-table-rows').focus();

		},
		insert: function()
		{

			var rows = $('#redactor-table-rows').val(),
				columns = $('#redactor-table-columns').val(),
				$tableBox = $('<div>'),
				tableId = Math.floor(Math.random() * 99999),
				$table = $('<table id="table' + tableId + '"><tbody></tbody></table>'),
				i, $row, z, $column;

			for (i = 0; i < rows; i++)
			{
				$row = $('<tr>');

				for (z = 0; z < columns; z++)
				{
					$column = $('<td>' + this.opts.invisibleSpace + '</td>');

					// set the focus to the first td
					if (i === 0 && z === 0)
					{
						$column.append(this.selection.getMarker());
					}

					$($row).append($column);
				}

				$table.append($row);
			}

			$tableBox.append($table);
			var html = $tableBox.html();


			this.modal.close();
			this.selection.restore();

			if (this.table.getTable()) return;

			this.buffer.set();

			var current = this.selection.getBlock() || this.selection.getCurrent();
			if (current && current.tagName != 'BODY')
			{
				if (current.tagName == 'LI') current = $(current).closest('ul, ol');
				$(current).after(html);
			}
			else
			{
				this.insert.html(html);
			}

			this.selection.restore();

			var table = this.$editor.find('#table' + tableId);

			if (!this.opts.linebreaks && (this.utils.browser('mozilla') || this.utils.browser('msie')))
			{
				var $next = table.next();
				if ($next.length === 0)
				{
					 table.after(this.opts.emptyHtml);
				}
			}

			this.observe.buttons();

			table.find('span.redactor-selection-marker').remove();
			table.removeAttr('id');

			this.code.sync();
			this.core.setCallback('insertedTable', table);
		},
		getTable: function()
		{
			var $table = $(this.selection.getParent()).closest('table');

			if (!this.utils.isRedactorParent($table)) return false;
			if ($table.size() === 0) return false;

			return $table;
		},
		restoreAfterDelete: function($table)
		{
			this.selection.restore();
			$table.find('span.redactor-selection-marker').remove();
			this.code.sync();
		},
		deleteTable: function()
		{
			var $table = this.table.getTable();
			if (!$table) return;

			this.buffer.set();


			var $next = $table.next();
			if (!this.opts.linebreaks && $next.length !== 0)
			{
				this.caret.setStart($next);
			}
			else
			{
				this.caret.setAfter($table);
			}


			$table.remove();

			this.code.sync();
		},
		deleteRow: function()
		{
			var $table = this.table.getTable();
			if (!$table) return;

			var $current = $(this.selection.getCurrent());

			this.buffer.set();

			var $current_tr = $current.closest('tr');
			var $focus_tr = $current_tr.prev().length ? $current_tr.prev() : $current_tr.next();
			if ($focus_tr.length)
			{
				var $focus_td = $focus_tr.children('td, th').first();
				if ($focus_td.length) $focus_td.prepend(this.selection.getMarker());
			}

			$current_tr.remove();
			this.table.restoreAfterDelete($table);
		},
		deleteColumn: function()
		{
			var $table = this.table.getTable();
			if (!$table) return;

			this.buffer.set();

			var $current = $(this.selection.getCurrent());
			var $current_td = $current.closest('td, th');
			var index = $current_td[0].cellIndex;

			$table.find('tr').each($.proxy(function(i, elem)
			{
				var $elem = $(elem);
				var focusIndex = index - 1 < 0 ? index + 1 : index - 1;
				if (i === 0) $elem.find('td, th').eq(focusIndex).prepend(this.selection.getMarker());

				$elem.find('td, th').eq(index).remove();

			}, this));

			this.table.restoreAfterDelete($table);
		},
		addHead: function()
		{
			var $table = this.table.getTable();
			if (!$table) return;

			this.buffer.set();

			if ($table.find('thead').size() !== 0)
			{
				this.table.deleteHead();
				return;
			}

			var tr = $table.find('tr').first().clone();
			tr.find('td').html(this.opts.invisibleSpace);
			$thead = $('<thead></thead>').append(tr);
			$table.prepend($thead);

			this.code.sync();

		},
		deleteHead: function()
		{
			var $table = this.table.getTable();
			if (!$table) return;

			var $thead = $table.find('thead');
			if ($thead.size() === 0) return;

			this.buffer.set();

			$thead.remove();
			this.code.sync();
		},
		addRowAbove: function()
		{
			this.table.addRow('before');
		},
		addRowBelow: function()
		{
			this.table.addRow('after');
		},
		addColumnLeft: function()
		{
			this.table.addColumn('before');
		},
		addColumnRight: function()
		{
			this.table.addColumn('after');
		},
		addRow: function(type)
		{
			var $table = this.table.getTable();
			if (!$table) return;

			this.buffer.set();

			var $current = $(this.selection.getCurrent());
			var $current_tr = $current.closest('tr');
			var new_tr = $current_tr.clone();

			new_tr.find('th').replaceWith(function()
			{
				var $td = $('<td>');
				$td[0].attributes = this.attributes;

				return $td.append($(this).contents());
			});

			new_tr.find('td').html(this.opts.invisibleSpace);

			if (type == 'after')
			{
				$current_tr.after(new_tr);
			}
			else
			{
				$current_tr.before(new_tr);
			}

			this.code.sync();
		},
		addColumn: function (type)
		{
			var $table = this.table.getTable();
			if (!$table) return;

			var index = 0;
			var current = $(this.selection.getCurrent());

			this.buffer.set();

			var $current_tr = current.closest('tr');
			var $current_td = current.closest('td, th');

			$current_tr.find('td, th').each($.proxy(function(i, elem)
			{
				if ($(elem)[0] === $current_td[0]) index = i;

			}, this));

			$table.find('tr').each($.proxy(function(i, elem)
			{
				var $current = $(elem).find('td, th').eq(index);

				var td = $current.clone();
				td.html(this.opts.invisibleSpace);

				if (type == 'after')
				{
					$current.after(td);
				}
				else
				{
					$current.before(td);
				}

			}, this));

			this.code.sync();
		}
	};
};

RedactorPlugins.textdirection = function() {
  return {
    init: function()
    {
        var that = this;
        var dropdown = {};

        dropdown.ltr = { title: __('Left to Right'), callback: this.setLtr };
        dropdown.rtl = { title: __('Right to Left'), callback: this.setRtl };

        var button = this.button.add('textdirection', __('Change Text Direction'),
            false, dropdown);

        if (this.opts.direction == 'rtl')
            this.setRtl();
    },
    setRtl: function()
    {
        var c = this.getCurrent(), s = this.getSelection();
        this.bufferSet();
        if (s.type == 'Range' && s.focusNode.nodeName != 'div') {
            this.linebreakHack(s);
        }
        else if (!c) {
            var repl = '<div dir="rtl">' + this.get() + '</div>';
            this.set(repl, false);
        }
        $(this.getCurrent()).attr('dir', 'rtl');
        this.sync();
    },
    setLtr: function()
    {
        var c = this.getCurrent(), s = this.getSelection();
        this.bufferSet();
        if (s.type == 'Range' && s.focusNode.nodeName != 'div') {
            this.linebreakHack(s);
        }
        else if (!c) {
            var repl = '<div dir="ltr">' + this.get() + '</div>';
            this.set(repl, false);
        }
        $(this.getCurrent()).attr('dir', 'ltr');
        this.sync();
    },
    linebreakHack: function(sel) {
        var range = sel.getRangeAt(0);
        var wrapper = document.createElement('div');
        wrapper.appendChild(range.extractContents());
        range.insertNode(wrapper);
        this.selectionElement(wrapper);
    }
  };
};

RedactorPlugins.video = function()
{
	return {
		reUrlYoutube: /https?:\/\/(?:[0-9A-Z-]+\.)?(?:youtu\.be\/|youtube\.com\S*[^\w\-\s])([\w\-]{11})(?=[^\w\-]|$)(?![?=&+%\w.-]*(?:['"][^<>]*>|<\/a>))[?=&+%\w.-]*/ig,
		reUrlVimeo: /https?:\/\/(www\.)?vimeo.com\/(\d+)($|\/)/,
		getTemplate: function()
		{
			return String()
			+ '<section id="redactor-modal-video-insert">'
				+ '<label>' + this.lang.get('video_html_code') + '</label>'
				+ '<textarea id="redactor-insert-video-area" style="height: 160px;"></textarea>'
			+ '</section>';
		},
		init: function()
		{
			var button = this.button.addAfter('image', 'video', this.lang.get('video'));
			this.button.addCallback(button, this.video.show);
		},
		show: function()
		{
			this.modal.addTemplate('video', this.video.getTemplate());

			this.modal.load('video', this.lang.get('video'), 700);
			this.modal.createCancelButton();

			var button = this.modal.createActionButton(this.lang.get('insert'));
			button.on('click', this.video.insert);

			this.selection.save();
			this.modal.show();

			$('#redactor-insert-video-area').focus();

		},
		insert: function()
		{
			var data = $('#redactor-insert-video-area').val();
			data = this.clean.stripTags(data);

			// parse if it is link on youtube & vimeo
			var iframeStart = '<iframe style="width: 500px; height: 281px;" src="',
				iframeEnd = '" frameborder="0" allowfullscreen></iframe>';

			if (data.match(this.video.reUrlYoutube))
			{
				data = data.replace(this.video.reUrlYoutube, iframeStart + '//www.youtube.com/embed/$1' + iframeEnd);
			}
			else if (data.match(this.video.reUrlVimeo))
			{
				data = data.replace(this.video.reUrlVimeo, iframeStart + '//player.vimeo.com/video/$2' + iframeEnd);
			}

			this.selection.restore();
			this.modal.close();

			var current = this.selection.getBlock() || this.selection.getCurrent();

			if (current) $(current).after(data);
			else
			{
				this.insert.html(data);
			}

			this.code.sync();
		}

	};
};

RedactorPlugins.imagepaste = function() {
  return {
    init: function() {
      if (this.utils.browser('webkit') && navigator.userAgent.indexOf('Chrome') === -1)
      {
        var arr = this.utils.browser('version').split('.');
        if (arr[0] < 536)
          return true;
      }

      // paste except opera (not webkit)
      if (this.utils.browser('opera'))
          return true;

      this.$editor.on('paste.imagepaste', $.proxy(this.imagepaste.buildEventPaste, this));
    },
    buildEventPaste: function(e)
    {
      var event = e.originalEvent || e,
          fileUpload = false,
          files = [],
          i, file,
          cd = event.clipboardData;

      if (typeof(cd) === 'undefined') return;

      if (cd.items && cd.items.length)
      {
        for (i = 0, k = cd.items.length; i < k; i++) {
          if (cd.kind == 'file' && cd.type.indexOf('image/') !== -1) {
            file = cd.items[i].getAsFile();
            if (file !== null)
              files.push(file);
            }
        }
      }
      else if (cd.files && cd.files.length)
      {
        files = cd.files
      }
      else if (cd.types.length) {
        for (i = 0, k = cd.types.length; i < k; i++) {
          if (cd.types[i].indexOf('image/') != -1) {
            var data = cd.getData(cd.types[i]);
            if (data.length)
                files.push(new Blob([data], {type: cd.types[i]}));
          }
        }
      }
      if (files.length) {
        // clipboard upload
        this.selection.save();
        this.buffer.set();
        this.clean.singleLine = false;
        for (i = 0, k = files.length; i < k; i++)
          this.upload.directUpload(files[i], event);
        return false;
      }
    }
  };
};

var loadedFabric = false;
RedactorPlugins.imageannotate = function() {
  return {
    annotateButton: false,
    init: function() {
      var redactor = this,
          self = this.imageannotate;
      if (typeof window.fabric === 'undefined' && !loadedFabric) {
          $.getScript('../js/fabric.min.js');
          loadedFabric = true;
      }
      $(document).on('click', '.redactor-box img', function() {
        var $image = $(this),
            image_box = $('#redactor-image-box');
        if (!image_box.length || !redactor.image.editter)
            return;

        var edit_size = redactor.image.editter.outerWidth();

        self.annotateButton = redactor.image.editter
          .on('remove.annotate',
            function() { self.teardownAnnotate.call(redactor, image_box); })
          .clone()
          .text(' '+__('Annotate'))
          .prepend('<i class="icon-pencil"></i>')
          .addClass('annotate-button')
          .insertAfter(redactor.image.editter)
          .data('image', this)
          .on('click',
            function() { self.startAnnotate.call(redactor, $image) });
        var diff = (edit_size - self.annotateButton.outerWidth()) / 2;
        self.annotateButton.css('margin-left',
          (diff + 5) + 'px');
        redactor.image.editter.css('margin-left',
          (-edit_size + diff - 5) + 'px');
      });
    },
    startAnnotate: function(img) {
        canvas = this.imageannotate.initCanvas(img);
        this.imageannotate.buildToolbar(img);
        this.image.editter.hide();
        this.imageannotate.annotateButton.hide();
    },
    teardownAnnotate: function(box) {
        this.image.editter.off('.annotate');
        this.opts.keydownCallback = false;
        this.opts.keyupCallback = false;
        box.find('.annotate-toolbar').remove();
        box.find('.annotate-button').remove();
        var img = box.find('img')[0],
            $img = $(img),
            fcanvas = $img.data('canvas'),
            state = fcanvas.toObject();
        // Capture current annotations
        delete state.backgroundImage;
        $img.attr('data-annotations', btoa(JSON.stringify(state)));
        // Drop the canvas
        fcanvas.dispose();
        box.find('canvas').parent().remove();
        $img.data('canvas', false);
        // Deselect the image
        this.image.hideResize();
        // Show the original image
        $img.removeClass('hidden');
    },
    buildToolbar: function(img) {
        var box = img.parent(),
            redactor = this,
            plugin = this.imageannotate,
            shapes = $('<span>')
              .attr('data-redactor', 'verified')
              .attr('contenteditable', 'false')
              .css({'display': 'inline-block', 'vertical-align': 'top'}),
            swatches = shapes.clone(),
            actions = shapes.clone(),
            container = $('<div></div>')
              .addClass('annotate-toolbar')
              .attr('data-redactor', 'verified')
              .attr('contenteditable', 'false')
              .css({position: 'absolute', bottom: 0, 'min-height': '28px',
                width: '100%', 'background-color': 'rgba(0,0,0,0.5)',
                margin: 0, 'padding-top': '4px' })
              .appendTo(box)
              .append(shapes)
              .append(swatches)
              .append(actions);

        var button = $('<a></a>')
            .attr('href', '#')
            .attr('data-redactor', 'verified')
            .attr('contenteditable', 'false')
            .css({color: 'white', padding: '0 7px 1px', margin: '1px 3px',
                'text-decoration': 'none', 'vertical-align': 'top'});

        shapes
            .append(button.clone()
              .append($('<i class="icon-arrow-right icon-large"></i>')
              .on('click', plugin.drawArrow.bind(redactor))
              .attr('title', __('Add Arrow')))
            )
            .append(button.clone()
              .append($('<i class="icon-check-empty icon-large"></i>')
              .on('click', plugin.drawBox.bind(redactor))
              .attr('title', __('Add Rectangle')))
            )
            .append(button.clone()
              .append($('<i class="icon-circle-blank icon-large"></i>')
              .on('click', plugin.drawEllipse.bind(redactor))
              .attr('title', __('Add Ellipse')))
            )
            .append(button.clone()
              .append($('<i class="icon-text-height icon-large"></i>')
              .on('click', plugin.drawText.bind(redactor))
              .attr('title', __('Add Text')))
            );

      var colors = [
          '#ffffff', '#888888', '#000000', 'fuchsia', 'blue', 'red',
          'lime', 'blueviolet', 'cyan', '#f4a63b', 'yellow']
          len = colors.length;

      swatches.append(
        $('<span><i class="icon-ellipsis-vertical icon-large"></i></span>')
          .css({color: 'white', padding: '0 3px 1px', margin: '1px 3px',
            height: '21px', position: 'relative', bottom: '8px'}
          )
      );
      for (var z = 0; z < len; z++) {
        var color = colors[z];

        var $swatch = $('<a rel="' + color + '" href="#" style="font-size: 0; padding: 0; margin: 2px; width: 22px; height: 22px;"></a>');
        $swatch.css({'background-color': color, 'border': '1px dotted rgba(255,255,255,0.4)'});
        $swatch.attr('data-redactor', 'verified');
        $swatch.attr('contenteditable', 'false');
        $swatch.on('click', plugin.setColor.bind(redactor));

        swatches.append($swatch);
      }

        actions
            .append(
              $('<span><i class="icon-ellipsis-vertical icon-large"></i></span>')
                .css({color: 'white', padding: '0 3px 1px', margin: '1px 3px',
                  height: '21px'}
                )
            )
            .append(button.clone()
              .css('padding-left', '1px')
              .append($('<span></span>').css('position','relative')
                .append($('<i class="icon-font"></i>'))
                .append($('<i class="icon-minus"></i>')
                  .css({position: 'absolute', right: '-4px', top: '5px',
                    'text-shadow': '0 0 2px black', 'font-size':'80%'})
                )
              )
              .on('click', plugin.smallerFont.bind(redactor))
              .attr('title', __('Decrease Font Size'))
            )
            .append(button.clone()
              .css('padding-left', '1px')
              .append($('<span></span>').css('position','relative')
                .append($('<i class="icon-font icon-large"></i>'))
                .append($('<i class="icon-plus"></i>')
                  .css({position: 'absolute', right: '-8px', top: '4px',
                    'text-shadow': '0 0 2px black'})
                )
              )
              .on('click', plugin.biggerFont.bind(redactor))
              .attr('title', __('Increase Font Size'))
            )
            .append(button.clone()
              .attr('id', 'annotate-set-stroke')
              .append($('<span></span>').css({'position': 'relative', 'top': '2px'})
                .append($('<i class="icon-check-empty icon-large"></i>')
                  .css('font-size', '120%')
                ).append($('<i class="icon-tint"></i>')
                  .css({position: 'absolute', left: '4.5px', top: 0})
                )
              )
              .on('click', plugin.paintStroke.bind(redactor))
              .attr('title', __('Set Stroke'))
            )
            .append(button.clone()
              .attr('id', 'annotate-set-fill')
              .append($('<span></span>').css('position','relative')
                .append($('<i class="icon-sign-blank icon-large"></i>'))
                .append($('<i class="icon-tint icon-dark"></i>')
                  .css({position: 'absolute', left: '4px', top: '2px'})
                )
              )
              .on('click', plugin.paintFill.bind(redactor))
              .attr('title', __('Set Fill'))
            )
            .append(button.clone()
              .append($('<i class="icon-eye-close icon-large"></i>'))
              .on('click', plugin.setOpacity.bind(redactor))
              .attr('title', __('Toggle Opacity'))
            )
            .append(button.clone()
              .append($('<i class="icon-double-angle-up icon-large"></i>'))
              .on('click', plugin.bringForward.bind(redactor))
              .attr('title', __('Bring Forward'))
            )
            .append(button.clone()
              .append($('<i class="icon-trash icon-large"></i>'))
              .on('click', plugin.discard.bind(redactor))
              .attr('title', __('Delete Object'))
            );

        container.append(button.clone()
          .append($('<i class="icon-save icon-large"></i>'))
          .on('click', plugin.commit.bind(redactor))
          .addClass('pull-right')
          .attr('title', __('Commit Annotations'))
        );
        plugin.paintStroke();
    },

    setColor: function(e) {
      e.preventDefault();
      var plugin = this.imageannotate,
          redactor = this,
          swatch = e.target,
          image_box = $('#redactor-image-box'),
          img = image_box.find('img')[0],
          fcanvas = $(img).data('canvas');
      $.each(fcanvas.getObjects(), function() {
        if (this.get('active')) {
          if (plugin.paintMode == 'fill')
            this.setFill($(e.target).attr('rel'));
          else
            this.setStroke($(e.target).attr('rel'));
        }
      });
      fcanvas.renderAll();
    },

    // Shapes
    drawShape: function(ondown, onmove, onup, cursor) {
      // @see http://jsfiddle.net/URWru/
      var plugin = this.imageannotate,
          redactor = this,
          image_box = $('#redactor-image-box'),
          img = image_box.find('img')[0],
          fcanvas = $(img).data('canvas'),
          isDown, shape,
          mousedown = function(o) {
            isDown = true;
            plugin.setBuffer();
            var pointer = fcanvas.getPointer(o.e);
            shape = ondown(pointer, o.e);
            fcanvas.add(shape);
          },
          mousemove = function(o) {
            if (!isDown) return;
            var pointer = fcanvas.getPointer(o.e);
            onmove(shape, pointer, o.e);
            fcanvas.renderAll();
          },
          mouseup = function(o) {
            isDown = false;
            if (onup) {
              if (shape2 = onup(shape, fcanvas.getPointer(o.e))) {
                shape.remove();
                fcanvas.add(shape2);
                shape = shape2;
              }
            }
            shape.setCoords()
              .set({
                transparentCorners: false,
                borderColor: 'rgba(102,153,255,0.9)',
                cornerColor: 'rgba(102,153,255,0.5)',
                cornerSize: 10
              });
            fcanvas.calcOffset()
              .off('mouse:down', mousedown)
              .off('mouse:up', mouseup)
              .off('mouse:move', mousemove)
              .deactivateAll()
              .setActiveObject(shape)
              .renderAll();
            fcanvas.selection = true;
            fcanvas.defaultCursor = 'default';
          };

        fcanvas.selection = false;
        fcanvas.defaultCursor = cursor || 'crosshair';
        // Ensure double presses of same button are squelched
        fcanvas.off('mouse:down');
        fcanvas.off('mouse:up');
        fcanvas.off('mouse:move');
        fcanvas.on('mouse:down', mousedown);
        fcanvas.on('mouse:up', mouseup);
        fcanvas.on('mouse:move', mousemove);
        return false;
    },

    drawArrow: function(e) {
      e.preventDefault();
      var top, left;
      return this.imageannotate.drawShape(
        function(pointer) {
          top = pointer.y;
          left = pointer.x;
          return new fabric.Group([
            new fabric.Line([0, 5, 0, 5], {
              strokeWidth: 5,
              fill: 'red',
              stroke: 'red',
              originX: 'center',
              originY: 'center',
              selectable: false,
              hasBorders: false
            }),
            new fabric.Polygon([
              {x: 20, y: 0},
              {x: 0, y: -5},
              {x: 0, y: 5}
              ], {
              strokeWidth: 0,
              fill: 'red',
              originX: 'center',
              originY: 'center',
              selectable: false,
              hasBorders: false
            })
          ], {
            left: pointer.x,
            top: pointer.y,
            originX: 'center',
            originY: 'center'
          });
        },
        function(group, pointer) {
          var dx = pointer.x - left,
              dy = pointer.y - top,
              angle = Math.atan(dy / dx),
              d = Math.sqrt(dx * dx + dy * dy) - 10,
              sign = dx < 0 ? -1 : 1,
              dy2 = Math.sin(angle) * d * sign;
              dx2 = Math.cos(angle) * d * sign,
          group.item(0)
            .set({ x2: dx2, y2: dy2 });
          group.item(1)
            .set({
              angle: angle * 180 / Math.PI,
              flipX: dx < 0,
              flipY: dy < 0
            })
            .setPositionByOrigin(new fabric.Point(dx, dy),
                'center', 'center');
        },
        function(shape, pointer) {
          var dx = pointer.x - left,
              dy = pointer.y - top,
              angle = Math.atan(dy / dx),
              d = Math.sqrt(dx * dx + dy * dy);
          // Mess with the next two lines and you *will* be sorry!
          shape.forEachObject(function(e) { shape.removeWithUpdate(e); });
          return new fabric.Path(
            'M '+left+' '+top+' l '+(d-20)+' 0 0 -3 15 3 -15 3 0 -3 z', {
            angle: angle * 180 / Math.PI + (dx < 0 ? 180 : 0),
            strokeWidth: 5,
            fill: 'red',
            stroke: 'red'
          });
        }
      );
    },

    drawEllipse: function(e) {
      e.preventDefault();
      return this.imageannotate.drawShape(
        function(pointer) {
          return new fabric.Ellipse({
            top: pointer.y,
            left: pointer.x,
            strokeWidth: 5,
            fill: 'transparent',
            stroke: 'red',
            originX: 'left',
            originY: 'top'
          });
        },
        function(circle, pointer, event) {
          var x = circle.get('left'), y = circle.get('top'),
              dx = pointer.x - x, dy = pointer.y - y,
              sw = circle.getStrokeWidth()/2;
          // Use SHIFT to draw circles
          if (event.shiftKey) {
            dy = dx = Math.max(dx, dy);
          }
          circle.set({
            rx: Math.max(0, Math.abs(dx/2) - sw),
            ry: Math.max(0, Math.abs(dy/2) - sw),
            originX: dx < 0 ? 'right' : 'left',
            originY: dy < 0 ? 'bottom' : 'top'});
        }
      );
    },

    drawBox: function(e) {
      e.preventDefault();
      return this.imageannotate.drawShape(
        function(pointer) {
          return new fabric.Rect({
            top: pointer.y,
            left: pointer.x,
            strokeWidth: 5,
            fill: 'transparent',
            stroke: 'red',
            originX: 'left',
            originY: 'top'
          });
        },
        function(rect, pointer, event) {
          var x = rect.get('left'), y = rect.get('top'),
              dx = pointer.x - x, dy = pointer.y - y;
          // Use SHIFT to draw squares
          if (event.shiftKey) {
            dy = dx = Math.max(dx, dy);
          }
          rect.set({ width: Math.abs(dx), height: Math.abs(dy),
            originX: dx < 0 ? 'right' : 'left',
            originY: dy < 0 ? 'bottom' : 'top'});
        }
      );
    },

    drawText: function(e) {
      e.preventDefault();
      return this.imageannotate.drawShape(
        function(pointer) {
          return new fabric.IText(__('Text'), {
            top: pointer.y,
            left: pointer.x,
            fill: 'red',
            originX: 'left',
            originY: 'top',
            fontFamily: 'sans-serif',
            fontSize: 30
          });
        },
        function(rect, pointer, event) {
          var x = rect.get('left'), y = rect.get('top'),
              dx = pointer.x - x, dy = pointer.y - y;
          // Use SHIFT to draw squares
          if (event.shiftKey) {
            dy = dx = Math.max(dx, dy);
          }
          rect.set({ width: Math.abs(dx), height: Math.abs(dy),
            originX: dx < 0 ? 'right' : 'left',
            originY: dy < 0 ? 'bottom' : 'top'});
        },
        function(shape) {
          shape.on('editing:exited', function() {
            if (!shape.getText())
              shape.remove();
          });
        },
        'text'
      );
    },

    // Action buttons
    biggerFont: function(e) {
      e.preventDefault();
      var image_box = $('#redactor-image-box'),
          img = image_box.find('img')[0],
          fcanvas = $(img).data('canvas');
      $.each(fcanvas.getObjects(), function() {
        if (this.get('active') && this instanceof fabric.IText) {
          if (this.getSelectedText()) {
            this.setSelectionStyles({
              fontSize: (this.getSelectionStyles().fontSize || this.getFontSize()) + 5
            });
          }
          else {
            this.setFontSize(this.getFontSize() + 5);
          }
        }
      });
      fcanvas.renderAll();
      return false;
    },
    smallerFont: function(e) {
      e.preventDefault();
      var image_box = $('#redactor-image-box'),
          img = image_box.find('img')[0],
          fcanvas = $(img).data('canvas');
      $.each(fcanvas.getObjects(), function() {
        if (this.get('active') && this instanceof fabric.IText) {
          if (this.getSelectedText()) {
            this.setSelectionStyles({
              fontSize: (this.getSelectionStyles().fontSize || this.getFontSize()) - 5
            });
          }
          else {
            this.setFontSize(this.getFontSize() - 5);
          }
        }
      });
      fcanvas.renderAll();
      return false;
    },

    paintStroke: function(e) {
      $('#annotate-set-stroke').css({'background-color': 'rgba(255,255,255,0.3)'});
      $('#annotate-set-fill').css({'background-color': 'transparent'});
      this.imageannotate.paintMode = 'stroke';
      return false;
    },
    paintFill: function(e) {
      $('#annotate-set-fill').css({'background-color': 'rgba(255,255,255,0.3)'});
      $('#annotate-set-stroke').css({'background-color': 'transparent'});
      this.imageannotate.paintMode = 'fill';
      return false;
    },

    setOpacity: function(e) {
      e.preventDefault();
      var image_box = $('#redactor-image-box'),
          img = image_box.find('img')[0],
          fcanvas = $(img).data('canvas');
      $.each(fcanvas.getObjects(), function() {
        if (this.get('active')) {
          if (this.getOpacity() != 1)
            this.setOpacity(1);
          else
            this.setOpacity(0.6);
        }
      });
      fcanvas.renderAll();
      return false;
    },

    bringForward: function(e) {
      e.preventDefault();
      var image_box = $('#redactor-image-box'),
          img = image_box.find('img')[0],
          fcanvas = $(img).data('canvas');
      $.each(fcanvas.getObjects(), function() {
        if (this.get('active')) {
          this.bringForward();
        }
      });
    },

    keydown: function(e) {
      var image_box = $('#redactor-image-box'),
          img = image_box.find('img')[0],
          fcanvas = $(img).data('canvas');

      if (!fcanvas)
          return;

      var active = fcanvas.getActiveObject();

      // Check if editing a text element
      if (active instanceof fabric.IText && active.get('isEditing')) {
        // This keystroke is not for redactor
        var ss = active.get('selectionStart'),
            se = active.get('selectionEnd');
        active.exitEditing();
        active.enterEditing();
        active.set({
          'selectionStart': ss,
          'selectionEnd': se
        });
        if (e.type == 'keydown')
            active.onKeyDown(e);
        else
            active.onKeyPress(e);
        return false;
      }

      // Check if [delete] was pressed with selected objects
      if (e.keyCode == 8 || e.keyCode == 46)
        return this.imageannotate.discard(e);
      else if (e.keyCode == 90 && (e.metaKey || e.ctrlKey)) {
        fcanvas.loadFromJSON(atob($(img).attr('data-annotations')));
        return false;
      }
    },

    discard: function(e) {
      var image_box = $('#redactor-image-box', this.$editor),
          img = image_box && image_box.find('img')[0],
          fcanvas = img && $(img).data('canvas');

      if (!fcanvas)
        // Not annotating
        return;

      e.preventDefault();
      this.imageannotate.setBuffer();
      $.each(fcanvas.getObjects(), function() {
        if (this.get('active'))
          this.remove();
      });
      fcanvas.renderAll();
      return false;
    },

    commit: function(e) {
      e.preventDefault();
      var redactor = this,
          image_box = $('#redactor-image-box'),
          img = image_box.find('img')[0],
          $img = $(img),
          fcanvas = $(img).data('canvas');
      fcanvas.deactivateAll();

      // Upload to server
      redactor.buffer.set();
      var annotated = fcanvas.toDataURL({
            format: 'jpg', quality: 4,
            multiplier: 1/fcanvas.getZoom()
          }),
          file = new Blob([annotated], {type: 'image/jpeg'});

      // Fallback to the data URL — show while the image is being uploaded
      var origSrc = $img.attr('src');
      $img.attr('src', annotated);

      var origCallback = redactor.opts.imageUploadCallback,
          origErrorCbk = redactor.opts.imageUploadErrorCallback;

      // After successful upload, replace the old image with the new one.
      // Transfer the annotation state to the new image for replay.
      redactor.opts.imageUploadCallback = function(image, json) {
        redactor.opts.imageUploadCallback = origCallback;
        redactor.opts.imageUploadErrorCallback = origErrorCbk;
        // Transfer the annotation JSON data and drop the original image.
        image.attr('data-annotations', $img.attr('data-annotations'));
        // Record the image that was originally annotated. If the committed
        // image is annotated again, it should be the original image with
        // the annotations placed live on the original image. The image
        // being committed here will be discarded.
        image.attr('data-orig-annotated-image-src',
          $img.attr('data-orig-annotated-image-src') || origSrc
        );
        $img.remove();
        // Redactor will add <br> before and after the image in linebreaks
        // mode
        var N = image.next();
        if (N.is('br')) N.remove();
        var P = image.prev();
        if (N.is('br')) P.remove();
      };

      // Handle upload issues
      redactor.opts.imageUploadErrorCallback = function(json) {
        redactor.opts.imageUploadCallback = origCallback;
        redactor.opts.imageUploadErrorCallback = origErrorCbk;
        $img.show();
      };
      redactor.imageannotate.teardownAnnotate(image_box);
      $img.css({opacity: 0.5});
      redactor.upload.directUpload(file, e);
      return false;
    },

    // Utils
    resizeShape: function(o) {
      var shape = o.target;
      if (shape instanceof fabric.Ellipse) {
        shape.set({
          rx: shape.get('rx') * shape.get('scaleX'),
          ry: shape.get('ry') * shape.get('scaleY'),
          scaleX: 1,
          scaleY: 1
        });
      }
      else if (shape instanceof fabric.Rect) {
        shape.set({
          width: shape.get('width') * shape.get('scaleX'),
          height: shape.get('height') * shape.get('scaleY'),
          scaleX: 1,
          scaleY: 1
        });
      }
    },
    setBuffer: function() {
      var image_box = $('#redactor-image-box'),
          img = image_box.find('img')[0],
          $img = $(img),
          fcanvas = $img.data('canvas'),
          state = fcanvas.toObject();
      // Capture current annotations
      delete state.backgroundImage;
      $img.attr('data-annotations', btoa(JSON.stringify(state)));
    },

    // Startup

    initCanvas: function(img) {
      var self = this,
          plugin = this.imageannotate,
          $img = $(img);
      if ($img.data('canvas'))
        return;
      var box = $img.parent(),
          canvas = $('<canvas>').css({
            position: 'absolute',
            top: 0, bottom: 0, left: 0, right: 0,
            width: '100%', height: '100%'
          }).appendTo(box),
          fcanvas = new fabric.Canvas(canvas[0], {
            backgroundColor: 'rgba(0,0,0,0,0)',
            containerClass: 'no-margin',
            includeDefaultValues: false,
          }),
          previous = $(img).attr('data-annotations');

      // Catch [delete] key and map to delete object
      self.opts.keydownCallback = plugin.keydown.bind(self);
      self.opts.keyupCallback = plugin.keydown.bind(self);

      var I = new Image(), scale;
      I.src = $img.attr('src');
      // Use a maximum zoom-out of 0.7, so that very large pictures do not
      // result in unusually small annotations (esp. stroke widths which are
      // not adjustable).
      scale = Math.max(0.7, $img.width() / I.width);
      var scaleWidth = $img.width() / scale,
          scaleHeight = $img.height() / scale;
      console.log(I.width, scaleWidth, $img.width(), scale);
      fcanvas
        .setDimensions({width: $img.width(), height: $img.height()})
        .setZoom(scale)
        .setBackgroundImage(
            $img.attr('data-orig-annotated-image-src') || $img.attr('src'),
            fcanvas.renderAll.bind(fcanvas), {
          width: scaleWidth,
          height: scaleHeight,
          // Needed to position overlayImage at 0/0
          originX: 'left',
          originY: 'top'
        })
        .on('object:scaling', plugin.resizeShape.bind(self));
      if (previous) {
        fcanvas.loadFromJSON(atob(previous));
        fcanvas.forEachObject(function(o) {
          o.set({
            transparentCorners: false,
            borderColor: 'rgba(102,153,255,0.9)',
            cornerColor: 'rgba(102,153,255,0.5)',
            cornerSize: 10
          });
        });
      }
      $img.data('canvas', fcanvas).addClass('hidden');
      return fcanvas;
    }
  };
};