/* eslint indent: ["error", 2, { "outerIIFEBody": 0, "SwitchCase": 1 }] */
/**
 * @copyright Copyright (C) 2018, struktur AG
 *
 * @author Joachim Bauch <mail@joachim-bauch.de>
 *
 * @license AGPL-3.0
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

import $ from 'jquery';
import 'jquery-ui/ui/widgets/draggable';
import { each, throttle } from 'lodash';
import io from 'socket.io-client';
import iro from '@jaames/iro';
import paper from 'paper';
import { pdfjsLib, PDFViewerApplication, PDFViewerApplicationOptions } from 'pdfjs-dist-viewer-min';

(function() {
"use strict";

// TODO(jojo): Should get this from a viewer.js property.
var CSS_UNITS = 96.0 / 72.0;
var INITAL_COLORS  = [
  "#ff0000",
  "#008080",
  "#00ffff",
  "#00ff00",
  "#008000",
  "#c0c0c0",
  "#f7347a",
  "#990000",
  "#ccff00",
  "#3399ff",
  "#f6546a",
  "#ffff00",
  "#ffa500",
  "#0000ff",
  "#800080",
];

var DEFAULT_STROKE_WIDTH = 5;

var PERMISSION_CREATE = 4;
var PERMISSION_READ = 1;
var PERMISSION_UPDATE = 2;
var PERMISSION_DELETE = 8;
var PERMISSION_SHARE = 16;
var PERMISSION_ALL = 31;

// Taken from https://gist.github.com/jed/982883
function uuidv4() {
  return ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g, c =>
    (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)
  );
}

function getRandomColor() {
  return INITAL_COLORS[Math.floor(Math.random() * INITAL_COLORS.length)];
}

// Required for running in older versions of Qt (see #31).
// https://github.com/uxitten/polyfill/blob/master/string.polyfill.js
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/padStart
if (!String.prototype.padStart) {
  String.prototype.padStart = function padStart(targetLength, padString) {
    targetLength = targetLength >> 0; //truncate if number, or convert non-number to 0;
    padString = String(padString !== undefined ? padString : ' ');
    if (this.length >= targetLength) {
      return String(this);
    } else {
      targetLength = targetLength - this.length;
      if (targetLength > padString.length) {
        padString += padString.repeat(targetLength / padString.length); //append to original to ensure we are longer than needed
      }
      return padString.slice(0, targetLength) + String(this);
    }
  };
}

var uid = uuidv4();
var object_count = 0;

function getObjectId() {
  return uid + ":" + (++object_count);
}

var Storage = function(prefix) {
  this.prefix = prefix;
  this.storage = window.localStorage;
  if (!this.storage) {
    console.warn('LocalStorage not available, saving of settings will be disabled.');
  }
};

Storage.prototype._getKey = function(key) {
  if (!this.prefix) {
    return key;
  }

  return this.prefix + '.' + key;
};

Storage.prototype.get = function(key) {
  if (!this.storage) {
    return null;
  }

  return this.storage.getItem(this._getKey(key));
};

Storage.prototype.set = function(key, value) {
  if (!this.storage) {
    return;
  } else if (!value) {
    this.storage.removeItem(this._getKey(key));
    return;
  }

  this.storage.setItem(this._getKey(key), value);
};

var BaseDrawer = function(annotator) {
  this.annotator = annotator;
};

BaseDrawer.prototype.destroy = function() {};
BaseDrawer.prototype.activate = function(page_annotator) {};
BaseDrawer.prototype.updateSettings = function(page_annotator) {};
BaseDrawer.prototype.onMouseDown = function(page_annotator, event) {};
BaseDrawer.prototype.onMouseUp = function(page_annotator, event) {};
BaseDrawer.prototype.onMouseDrag = function(page_annotator, event) {};
BaseDrawer.prototype.onMouseEnter = function(page_annotator, event) {};
BaseDrawer.prototype.onMouseLeave = function(page_annotator, event) {};
BaseDrawer.prototype.onMouseMove = function(page_annotator, event) {};
BaseDrawer.prototype.onClick = function(page_annotator, event) {};
BaseDrawer.prototype.onKeyUp = function(page_annotator, event) {};
BaseDrawer.prototype.onItemMoved = function(page_annotator, item, event) {};

var NullDrawer = function() {
  BaseDrawer.apply(this, arguments);
};
NullDrawer.prototype = Object.create(BaseDrawer.prototype);

var FreehandDrawer = function(annotator) {
  BaseDrawer.apply(this, arguments);
  this.path = null;
  this.intervalId = null;
};
FreehandDrawer.prototype = Object.create(BaseDrawer.prototype);

FreehandDrawer.prototype.sendPath = function() {
  if (this.path && this.path.segments.length) {
    this.annotator.sendItem(this.page_annotator, this.path);
  }
};

FreehandDrawer.prototype.onMouseDown = function(page_annotator, event) {
  page_annotator.activate();
  this.page_annotator = page_annotator;
  this.intervalId = setInterval(this.sendPath.bind(this), 250);
  this.path = page_annotator.createPath({
    name: getObjectId(),
    strokeColor: this.annotator.color,
    strokeWidth: this.annotator.strokeWidth,
  });
};

FreehandDrawer.prototype.onMouseUp = function(page_annotator, event) {
  if (!this.path) {
    return;
  }
  if (this.path.segments.length) {
    this.path.smooth();
    this.annotator.sendItem(page_annotator, this.path);
  }
  this.path = null;
  clearInterval(this.intervalId);
};

FreehandDrawer.prototype.onMouseDrag = function(page_annotator, event) {
  if (!this.path) {
    return;
  }
  this.path.add(event.point);
};

var LineDrawer = function(annotator) {
  FreehandDrawer.apply(this, arguments);
};

LineDrawer.prototype = Object.create(FreehandDrawer.prototype);

LineDrawer.prototype.onMouseDrag = function(page_annotator, event) {
  if (!this.path) {
    return;
  }
  this.path.removeSegment(1);
  this.path.add(event.point);
};

var RectangleDrawer = function(annotator) {
  BaseDrawer.apply(this, arguments);
  this.options = {};
  this.rect = null;
};

RectangleDrawer.prototype = Object.create(BaseDrawer.prototype);

RectangleDrawer.prototype.onMouseDown = function(page_annotator, event) {
  page_annotator.activate();
  this.options = {
    name: getObjectId(),
    strokeColor: this.annotator.color,
    strokeWidth: this.annotator.strokeWidth,
    from: [event.point.x, event.point.y],
    to: [event.point.x, event.point.y],
  };
  this.update(page_annotator);
};

RectangleDrawer.prototype.update = function(page_annotator) {
  var create = page_annotator.createRectangle.bind(page_annotator);
  if (!this.rect) {
    this.rect = create(this.options);
  } else {
    this.rect = this.rect.replaceWith(create(this.options));
  }
};

RectangleDrawer.prototype.onMouseUp = function(page_annotator, event) {
  if (!this.rect) {
    return;
  }
  this.options.to = [event.point.x, event.point.y];
  this.update(page_annotator);
  this.rect.ready = true;
  this.annotator.sendItem(page_annotator, this.rect);
  this.options = {};
  this.rect = null;
};

RectangleDrawer.prototype.onMouseDrag = function(page_annotator, event) {
  if (!this.rect) {
    return;
  }
  this.options.to = [event.point.x, event.point.y];
  this.update(page_annotator);
};

var EllipseDrawer = function(annotator) {
  BaseDrawer.apply(this, arguments);
  this.options = {};
  this.ellipse = null;
};

EllipseDrawer.prototype = Object.create(BaseDrawer.prototype);

EllipseDrawer.prototype.onMouseDown = function(page_annotator, event) {
  page_annotator.activate();
  this.options = {
    name: getObjectId(),
    strokeColor: this.annotator.color,
    strokeWidth: this.annotator.strokeWidth,
    from: [event.point.x, event.point.y],
    to: [event.point.x, event.point.y],
  };
  this.update(page_annotator);
};

EllipseDrawer.prototype.update = function(page_annotator) {
  var create = page_annotator.createEllipse.bind(page_annotator);
  if (!this.ellipse) {
    this.ellipse = create(this.options);
  } else {
    var temp_ellipse = create(this.options);
    this.ellipse = this.ellipse.replaceWith(temp_ellipse);
  }
};

EllipseDrawer.prototype.onMouseUp = function(page_annotator, event) {
  if (!this.ellipse) {
    return;
  }
  this.options.to = [event.point.x, event.point.y];
  this.update(page_annotator);
  this.ellipse.ready = true;
  this.annotator.sendItem(page_annotator, this.ellipse);
  this.options = {};
  this.ellipse = null;
};

EllipseDrawer.prototype.onMouseDrag = function(page_annotator, event) {
  if (!this.ellipse) {
    return;
  }
  this.options.to = [event.point.x, event.point.y];
  this.update(page_annotator);
};

var PointerDrawer = function(annotator) {
  BaseDrawer.apply(this, arguments);
};
PointerDrawer.prototype = Object.create(BaseDrawer.prototype);

PointerDrawer.prototype.destroy = function() {
  this.annotator.destroyCursors();
};

PointerDrawer.prototype.onMouseMove = function(page_annotator, event) {
  this.annotator.renderCursor(page_annotator, event.point.x, event.point.y);
};

PointerDrawer.prototype.onMouseLeave = function(page_annotator, event) {
  this.annotator.hideCursor();
};

var SelectDrawer = function(annotator) {
  BaseDrawer.apply(this, arguments);
  this.selected_item = null;
  this.prev_settings = null;
  this.pending_items = {};
  this.send_interval = setInterval(this._sendPending.bind(this), 100);
};
SelectDrawer.prototype = Object.create(BaseDrawer.prototype);

SelectDrawer.prototype._select = function(item) {
  if (this.selected_item === item) {
    return;
  }

  this._unselect();
  this.selected_item = item;
  if (this.selected_item.onFocus) {
    var result = this.selected_item.onFocus();
    if (result) {
      this.selected_item = result;
    }
  } else {
    this.selected_item.shadowColor = "black";
    this.selected_item.shadowBlur = 10;
  }

  var color = this.selected_item.strokeColor;
  if (typeof(color) !== 'string') {
    color = color.toCSS(true);
    if (this.selected_item.strokeColor.hasAlpha() && color.length <= 7) {
      color += Math.round(this.selected_item.strokeColor.alpha * 255).toString(16);
    }
  }
  var settings = {
    'color': color,
    'strokeWidth': this.selected_item.strokeWidth,
  };
  this.prev_settings = this.annotator.updateSettings(settings);
};

SelectDrawer.prototype._unselect = function() {
  var reset;
  if (this.prev_settings) {
    if (this.selected_item) {
      reset = {
        'strokeColor': this.selected_item.strokeColor,
        'strokeWidth': this.selected_item.strokeWidth,
      };
    }
    this.annotator.updateSettings(this.prev_settings);
    this.prev_settings = null;
  }

  if (!this.selected_item) {
    return;
  }

  if (this.selected_item.onBlur) {
    this.selected_item.onBlur();
  } else {
    this.selected_item.shadowColor = null;
    this.selected_item.shadowBlur = 0;
  }
  if (reset) {
    this.selected_item.strokeColor = reset.strokeColor;
    this.selected_item.strokeWidth = reset.strokeWidth;
  }
  this.selected_item = null;
};

SelectDrawer.prototype._sendItem = function(page_annotator, item) {
  // Need to remove "selected" layout while generating item JSON.
  var is_selected = (item === this.selected_item);
  if (is_selected) {
    this._unselect();
  }
  this.annotator.sendItem(page_annotator, item);
  if (is_selected) {
    this._select(item);
  }
};

SelectDrawer.prototype._sendPending = function() {
  var now = Date.now();
  for (var name in this.pending_items) {
    if (!this.pending_items.hasOwnProperty(name)) {
      continue;
    }

    var info = this.pending_items[name];
    var page_annotator = info[0];
    var item = info[1];
    var last_change = info[2];
    if (now - last_change < 250) {
      continue;
    }

    this._sendItem(page_annotator, item);
    delete this.pending_items[name];
  }
};

SelectDrawer.prototype.destroy = function() {
  clearInterval(this.send_interval);
  this._sendPending();
  this._unselect();
};

SelectDrawer.prototype._hitItem = function(page_annotator, point) {
  var hit = page_annotator.scope.project.hitTest(point);
  if (!hit) {
    // Check if user clicked on a text annotation.
    each(page_annotator.textAnnotations, function(ta) {
      var elem = ta.textareaContainer;
      if (!elem.is(':visible')) {
        return;
      }

      var rect = elem[0].getBoundingClientRect();
      if (rect.left <= point.x && rect.right >= point.x &&
          rect.top <= point.y && rect.bottom >= point.y) {
        hit = {
          "item": ta,
        };
      }
    });
    if (!hit) {
      this._unselect();
      return;
    }
  }

  this._select(hit.item);
};

SelectDrawer.prototype.onMouseDown = function(page_annotator, event) {
  this._hitItem(page_annotator, event.point);
};

SelectDrawer.prototype.onClick = function(page_annotator, event) {
  this._hitItem(page_annotator, event.point);
};

SelectDrawer.prototype.onKeyUp = function(page_annotator, event) {
  if (!this.selected_item) {
    return;
  }

  if (this.selected_item.ignoreKeyUp && this.selected_item.ignoreKeyUp()) {
    return;
  }

  switch (event.keyCode) {
    case 8: // Backspace
      // Fallthrough
    case 46: // Delete key
      var item = this.selected_item;
      this.annotator.deleteItem(page_annotator, item);
      this._unselect();
      item.remove();
      break;
  }
};

SelectDrawer.prototype.onItemMoved = function(page_annotator, item, event) {
  event.stopPropagation();
  item.position.x += event.delta.x;
  item.position.y += event.delta.y;
  this.addPendingItem(page_annotator, item);
};

SelectDrawer.prototype.addPendingItem = function(page_annotator, item) {
  if (this.pending_items.hasOwnProperty(item.name)) {
    this.pending_items[item.name][0] = page_annotator;
    this.pending_items[item.name][1] = item;
  } else {
    this.pending_items[item.name] = [page_annotator, item, Date.now()];
  }
};

SelectDrawer.prototype.updateSettings = function(page_annotator) {
  if (!this.selected_item) {
    return;
  }

  if (this.selected_item.strokeColor === this.annotator.color &&
      this.selected_item.strokeWidth === this.annotator.strokeWidth) {
    return;
  }

  this.selected_item.strokeColor = this.annotator.color;
  this.selected_item.strokeWidth = this.annotator.strokeWidth;
  this.addPendingItem(page_annotator, this.selected_item);
};

var TextDrawer = function(annotator)  {
  BaseDrawer.apply(this, arguments);
};
TextDrawer.prototype = Object.create(BaseDrawer.prototype);

TextDrawer.prototype.onClick = function(page_annotator, event) {
  var author = this.annotator.displayname;
  var textarea = page_annotator.createTextArea({
    'x': event.point.x,
    'y': event.point.y,
    'author': author,
  });
  textarea.sendData();
  textarea.onFocus();
  textarea.textarea.trigger('focus');
  this.annotator.setDrawMode("select");
};

var Cursor = function(annotator, userid, radius) {
  this.annotator = annotator;
  this.page_annotator = null;
  this.name = getObjectId();
  this.userid = userid;
  this.radius = radius;
  this.circle = null;
  this.label = $("<div class='cursor'></div>");
  this.label.css('position', 'absolute');
};

Cursor.prototype.destroy = function() {
  if (this.page_annotator) {
    this.page_annotator.unregisterPendingActivate(this);
  }
  if (this.circle) {
    this.circle.remove();
    this.circle = null;
  }
  this.label.remove();
};

Cursor.prototype.draw = function(page_annotator, x, y, color, text) {
  this.x = x;
  this.y = y;
  var center = new paper.Point(x, y);
  if (this.page_annotator && page_annotator !== this.page_annotator) {
    this.page_annotator.unregisterPendingActivate(this);
    if (this.page_annotator.container) {
      this.page_annotator.container.remove(this.label);
    }
    this.page_annotator = null;
    this.destroy();
  }
  if (!this.circle) {
    page_annotator.activate();
    this.circle = new paper.Path.Circle(center, this.radius);
    this.circle.name = getObjectId();
  }
  if (!this.page_annotator) {
    this.page_annotator = page_annotator;
    if (this.page_annotator.container) {
      this.page_annotator.container.append(this.label);
    } else {
      this.page_annotator.registerPendingActivate(this);
    }
  }
  this.circle.set({
    fillColor: color,
    shadowColor: color,
    shadowBlur: 10,
    position: center
  });
  this.label.html(text);
  this.update();
};

Cursor.prototype.activate = function(page_annotator) {
  if (page_annotator !== this.page_annotator) {
    return;
  }

  this.label.remove();
  if (page_annotator.container) {
    page_annotator.container.append(this.label);
  }
};

Cursor.prototype.update = function() {
  var radius = this.circle.bounds.width / 2;
  var newRadius = this.radius / this.page_annotator.scale;
  if (radius !== newRadius) {
    this.circle.scale(newRadius / radius);
  }
  var x = this.x * this.page_annotator.scale;
  var y = this.y * this.page_annotator.scale;
  this.label.css('left', x + (this.radius * 2));
  this.label.css('top', y - (this.radius));
};

var TextArea = function(page_annotator, text, color, author) {
  this.page_annotator = page_annotator;
  this.content = text;
  this.color = color;
  this.author = author;
  this.modified = Math.round((new Date()).getTime() / 1000);
  this.name = getObjectId();
  this.textareaContainer = $('<div class="textareaContainer form-group shadow-textarea"></div>');
  this.textareaContainer.draggable({
    start: function(event, ui) {
      if ($('textarea', this.textareaContainer).prop("disabled")) {
        event.preventDefault();
      }
    }.bind(this),
    drag: function(event, ui) {
      this.textareaContainerPos = [
        ui.position.left / this.page_annotator.scale,
        ui.position.top / this.page_annotator.scale
      ];
      this.update();
      this.sendData();
    }.bind(this),
    stop: function(event, ui) {
      this.textareaContainerPos = [
        ui.position.left / this.page_annotator.scale,
        ui.position.top / this.page_annotator.scale
      ];
      this.update();
      this.sendData();
    }.bind(this),
  });
  this.textareaContainer.on('click', function(event) {
    this.page_annotator.annotator.drawer.onClick(this.page_annotator, {
      'point': {
        'x': event.originalEvent.clientX,
        'y': event.originalEvent.clientY,
      },
    });
  }.bind(this));
  this.textareaContainer.on('mouseup', function(event) {
    this.page_annotator.annotator.drawer.onMouseUp(this.page_annotator, {
      'point': {
        'x': event.originalEvent.clientX,
        'y': event.originalEvent.clientY,
      },
    });
  }.bind(this));
  this.page_annotator.container.append(this.textareaContainer);
  this.circle = null;
  this.line = null;
  this.textarea = $('<textarea class="form-control" disabled="disabled"></textarea>');
  this.authorLabel = $('<span class="author"</span>');
  this.closeButton = $('<button type="button" disabled="disabled" class="deleteButton btn btn-danger form-control close" aria-label="Close"> <span aria-hidden="true">&times;</span></button>');
  this.closeButton.on('click', function() {
    this.textareaContainer.hide();
    this.update();
  }.bind(this));
  this.textarea.on('input', function(e) {
    if (this.content === this.textarea.val()) {
      return;
    }
    this.content = this.textarea.val();
    this.author = this.page_annotator.annotator.displayname;
    this.modified = Math.round((new Date()).getTime() / 1000);
    this.update();
    this.sendData();
  }.bind(this));
  this.textarea.on('focus', function(e) {
    this.onFocus();
  }.bind(this));
  this.textarea.on('dragstart', function(e) {
    e.preventDefault();
  }.bind(this));
  this.textareaContainer.append(this.authorLabel, this.closeButton, this.textarea);
  this.textareaContainer.css("background-color", this.color);
  this.textarea.css("font-size", this.page_annotator.annotator.fontSize+"px");

  this._sendData = throttle(function() {
    this.page_annotator.annotator.sendItem(this.page_annotator, this);
  }.bind(this), 250);
};

TextArea.prototype.exportJSON = function() {
  var data = {
    "type": "text-annotation",
    "anchor": [
      this.circle.position.x,
      this.circle.position.y,
    ],
    "author": this.author,
    "color": this.color,
    "content": this.content,
    "modified": this.modified,
    "pos": [
      this.textareaContainerPos[0] / this.page_annotator.scale,
      this.textareaContainerPos[1] / this.page_annotator.scale,
    ],
  };

  return JSON.stringify(data);
};

Object.defineProperty(TextArea.prototype, 'strokeColor', {
  "get": function() {
    return this.color;
  },
  "set": function(value) {
    if (this.color === value) {
      return;
    }

    this.color = value;
    this.textareaContainer.css("background-color", this.color);
    if (!$('textarea', this.textareaContainer).prop("disabled")) {
      this.textareaContainer.css("box-shadow", "0px 0px 15px 5px"+this.color);
    }
    this.authorLabel.css('background-color', this.color);
    if (this.circle) {
      this.circle.fillColor = value;
    }
    if (this.line) {
      this.line.strokeColor = value;
    }
  },
});

Object.defineProperty(TextArea.prototype, 'strokeWidth', {
  "get": function() {
    return this.page_annotator.annotator.strokeWidth;
  },
  "set": function(value) {
    // Not supported by text annotations.
  },
});

TextArea.prototype.sendData = function() {
  this._sendData();
};

TextArea.prototype.draw = function(x, y) {
  this.x = x;
  this.y = y;
  if (!this.circle) {
    this.drawCircle();
  }
  if (!this.textareaContainerPos) {
    this.textareaContainerPos = [x, y];
  }
  this.update();
};

TextArea.prototype.drawCircle = function() {
  var center = new paper.Point(this.x, this.y);
  var circle = new paper.Path.Circle(center, 10);
  circle.name = this.name+"pointer";
  circle.set({
    fillColor: this.color,
    shadowColor: this.color,
    shadowBlur: 2,
    position: center
  });

  circle.onClick = function(e) {
    this.textareaContainer.show();
    this.textarea.trigger('blur');
    this.update();
  }.bind(this);
  circle.onFocus = function() {
    this.onFocus();
    return this;
  }.bind(this);
  circle.onBlur = function() {
    this.onBlur();
    return this;
  }.bind(this);
  circle.onMouseDrag = function(event) {
    event.stopPropagation();
    this.textarea.trigger('blur');
    this.circle.position.x += event.delta.x;
    this.circle.position.y += event.delta.y;
    this.update();
    this.sendData();
  }.bind(this);
  circle.onMouseUp = function(event) {
    this.update();
    this.sendData();
  }.bind(this);
  this.circle = circle;
};

TextArea.prototype.update = function(page_annotator) {
  this.authorLabel.text(this.author || 'Anonymous');
  this.authorLabel.attr('title', this.author || 'Anonymous');
  this.authorLabel.css('background-color', this.color);
  this.textareaContainer.css('left', this.textareaContainerPos[0]*this.page_annotator.scale);
  this.textareaContainer.css('top', this.textareaContainerPos[1]*this.page_annotator.scale);
  this.textareaContainer.css("background-color", this.color);
  this.textarea.css("font-size", this.page_annotator.annotator.fontSize+"px");
  this.textarea.val(this.content);
  if (this.line) {
    this.line.remove();
    this.line = null;
  }
  if (this.textareaContainer.is(':visible')) {
    var from = new paper.Point(
      this.circle.position.x,
      this.circle.position.y);
    var to = new paper.Point(
      this.textareaContainerPos[0],
      this.textareaContainerPos[1]);
    this.line = new paper.Path.Line(from, to);
    this.line.strokeColor = this.color;
    this.line.strokeWidth = 2;
    if (!$('textarea', this.textareaContainer).prop("disabled")) {
      this.line.shadowColor = "black";
      this.line.shadowBlur = 5;
    }
    this.line.onFocus = function() {
      this.onFocus();
      return this;
    }.bind(this);
    this.line.onBlur = function() {
      this.onBlur();
      return this;
    }.bind(this);
  }
};

TextArea.prototype.remove = function() {
  this.circle.remove();
  if (this.line) {
    this.line.remove();
  }
  this.textareaContainer.remove();
  delete this.page_annotator.textAnnotations[this.name];
};

TextArea.prototype.ignoreKeyUp = function() {
  if (this.textarea.is(':focus')) {
    // The textarea currently is focused, ignore keypresses in drawer,
    // otherwise the element gets deleted when the user presses "delete".
    return true;
  }

  return false;
};

TextArea.prototype.onFocus = function() {
  if (this._selecting) {
    return;
  }

  this._selecting = true;
  $('textarea', this.textareaContainer).prop("disabled", false);
  this.closeButton.prop("disabled", false);
  this.textareaContainer.css("box-shadow", "0px 0px 15px 5px"+this.color);
  this.circle.shadowColor = "black";
  this.circle.shadowBlur = 10;
  if (this.line) {
    this.line.shadowColor = "black";
    this.line.shadowBlur = 5;
  }
  this._selecting = false;
};

TextArea.prototype.onBlur = function() {
  if (this._unselecting) {
    return;
  }

  this._unselecting = true;
  $('textarea', this.textareaContainer).prop("disabled", true);
  this.closeButton.prop("disabled", true);
  this.textareaContainer.draggable("disable");
  this.textareaContainer.css("box-shadow", "none");
  this.circle.shadowColor = null;
  this.circle.shadowBlur = 0;
  if (this.line) {
    this.line.shadowColor = null;
    this.line.shadowBlur = 0;
  }
  this.textarea.trigger('blur');
  this._unselecting = false;
};

TextArea.prototype.onResized = function(scale) {
  this.page_annotator.container.append(this.textareaContainer);
  this.draw(this.x, this.y);
};

var PageAnnotator = function(annotator, pagenum, container, page) {
  this.annotator = annotator;
  this.pagenum = pagenum;
  this.container = container;
  this.page = page;
  this.pending_activation = {};
  this.textAnnotations = {};
  this.canvas = document.createElement("canvas");
  this.canvas.style.position = "absolute";
  this.canvas.style.left = 0;
  this.canvas.style.top = 0;
  this.canvas.style.right = 0;
  this.canvas.style.bottom = 0;
  this.scope = new paper.PaperScope();
  this.scope.setup(this.canvas);
  this.scope.project.options.hitTolerance = 5;
  if (page) {
    this.setPage(page, container);
  }
  this.view = this.scope.getView();
  this.view.on('mousedown', function() {
    var args = Array.prototype.slice.call(arguments);
    args.unshift(this);
    this.activate();
    this.annotator.e.trigger('mousedown', [args]);
  }.bind(this));
  this.view.on('mouseup', function() {
    var args = Array.prototype.slice.call(arguments);
    args.unshift(this);
    this.activate();
    this.annotator.e.trigger('mouseup', [args]);
  }.bind(this));
  this.view.on('mousedrag', function() {
    var args = Array.prototype.slice.call(arguments);
    args.unshift(this);
    this.activate();
    this.annotator.e.trigger('mousedrag', [args]);
  }.bind(this));
  this.view.on('mouseenter', function() {
    var args = Array.prototype.slice.call(arguments);
    args.unshift(this);
    this.activate();
    this.annotator.e.trigger('mouseenter', [args]);
  }.bind(this));
  this.view.on('mouseleave', function() {
    var args = Array.prototype.slice.call(arguments);
    args.unshift(this);
    this.activate();
    this.annotator.e.trigger('mouseleave', [args]);
  }.bind(this));
  // Sometimes the "mouseleave" event of paper.js doesn't fire when
  // switching views, also handle "mouseleave" on the canvas itself
  // as a workaround.
  $(this.canvas).on('mouseleave', function() {
    var args = Array.prototype.slice.call(arguments);
    args.unshift(this);
    this.activate();
    this.annotator.e.trigger('mouseleave', [args]);
  }.bind(this));
  this.view.on('mousemove', function() {
    var args = Array.prototype.slice.call(arguments);
    args.unshift(this);
    this.activate();
    this.annotator.e.trigger('mousemove', [args]);
  }.bind(this));
  this.view.on('click', function() {
    var args = Array.prototype.slice.call(arguments);
    args.unshift(this);
    this.activate();
    this.annotator.e.trigger('click', [args]);
  }.bind(this));
  $(window).on('keyup', function(event) {
    if (PageAnnotator.prototype.__active !== this) {
      return;
    }

    var args = Array.prototype.slice.call(arguments);
    args.unshift(this);
    this.activate();
    this.annotator.e.trigger('keyup', [args]);
  }.bind(this));
};

PageAnnotator.prototype.exportSVG = function() {
  // Hide circle and line for text annotations so they don't show up in exported PDFs.
  each(this.textAnnotations, function(ta) {
    ta.circle.remove();
    if (ta.line) {
      ta.line.remove();
    }
  }.bind(this));

  var svg = this.scope.project.exportSVG({
    asString: true
  });

  each(this.textAnnotations, function(ta) {
    this.scope.project.activeLayer.addChild(ta.circle);
    if (ta.line) {
      this.scope.project.activeLayer.addChild(ta.line);
    }
  }.bind(this));
  return svg;
};

PageAnnotator.prototype.activate = function() {
  PageAnnotator.prototype.__active = this;
  for (var drawerName in this.pending_activation) {
    if (!this.pending_activation.hasOwnProperty(drawerName)) {
      continue;
    }

    this.pending_activation[drawerName].activate(this);
  }
  this.pending_activation = {};
  this.scope.activate();
};

PageAnnotator.prototype.registerPendingActivate = function(drawer) {
  this.pending_activation[drawer.name] = drawer;
};

PageAnnotator.prototype.unregisterPendingActivate = function(drawer) {
  delete this.pending_activation[drawer.name];
};

PageAnnotator.prototype.setPage = function(page, container) {
  this.container = container;
  this.page = page;
  var pagesize = page.view;
  this.pagewidth = (pagesize[2] - pagesize[0]) * CSS_UNITS;
  this.pageheight = (pagesize[3] - pagesize[1]) * CSS_UNITS;
  if (this.container) {
    this.container.append(this.canvas);
  }
};

PageAnnotator.prototype.update = function(scale) {
  if (this.container && !this.container.has(this.canvas).length) {
    this.container.append(this.canvas);
  }
  this.scale = scale;
  var width = this.pagewidth * scale;
  var height = this.pageheight * scale;
  this.view.viewSize = new paper.Size(width, height);
  this.view.center = new paper.Point(
    (width + (this.pagewidth * (1 - scale))) / 2,
    (height + (this.pageheight * (1 - scale))) / 2);
  this.view.zoom = scale;
  this.activate();
  each(this.textAnnotations, function(ta) {
    ta.onResized();
  });
  this.view._needsUpdate = true;
  this.view.requestUpdate();
};

PageAnnotator.prototype.createPath = function(options) {
  var path = new paper.Path(options || {});
  path.onMouseDrag = function(event) {
    this.annotator.drawer.onItemMoved(this, path, event);
  }.bind(this);
  return path;
};

PageAnnotator.prototype.createRectangle = function(options) {
  var rect = new paper.Path.Rectangle(options || {});
  rect.onMouseDrag = function(event) {
    if (rect.ready) {
      this.annotator.drawer.onItemMoved(this, rect, event);
    }
  }.bind(this);
  return rect;
};

PageAnnotator.prototype.createEllipse = function(options) {
  var from = new paper.Point(options.from);
  var to = new paper.Point(options.to);
  var rectangle = new paper.Rectangle(
    from, to
  );
  var ellipse = new paper.Path.Ellipse(rectangle);
  ellipse.strokeColor = options.strokeColor;
  ellipse.strokeWidth = options.strokeWidth;
  ellipse.name = options.name;
  ellipse.onMouseDrag = function(event) {
    if (ellipse.ready) {
      this.annotator.drawer.onItemMoved(this, ellipse, event);
    }
  }.bind(this);
  return ellipse;
};

PageAnnotator.prototype.createTextArea = function(options) {
  var text = options.text || "";
  var color = options.color || this.annotator.color;
  var author = options.author || "";
  var textarea = new TextArea(this, text, color, author);
  if (options.name) {
    textarea.name = options.name;
  }
  if (options.modified) {
    textarea.modified = options.modified;
  }
  this.textAnnotations[textarea.name] = textarea;
  textarea.draw(options.x, options.y);
  if (options.anchor && textarea.circle) {
    textarea.circle.position = {
      x: options.anchor[0],
      y: options.anchor[1],
    };
  }
  return textarea;
};

PageAnnotator.prototype.drawItem = function(name, data) {
  this.activate();
  var path = this.scope.project.getItem({"name": name});
  if (!path) {
    path = this.createPath();
  }
  try {
    path.importJSON(data);
  } catch (e) {
    path.remove();
    path.name = name;
    this.annotator.deleteItem(this, path);
    console.log("Could not import item", data, e);
    return;
  }
  path.name = name;
};

PageAnnotator.prototype.drawTextAnnotation = function(userid, name, data) {
  this.activate();
  var textarea;
  if (this.textAnnotations.hasOwnProperty(name)) {
    textarea = this.textAnnotations[name];
    textarea.author = data.author;
    textarea.modified = data.modified;
    textarea.color = data.color;
    textarea.content = data.content;
    textarea.circle.position = {
      x: Math.max(0, data.anchor[0]),
      y: Math.max(0, data.anchor[1]),
    };
    textarea.draw(data.pos[0], data.pos[1]);
  } else {
    textarea = this.createTextArea({
      'x': data.pos[0],
      'y': data.pos[1],
      'text': data.content,
      'color': data.color,
      'name': name,
      'anchor': data.anchor,
      'author': data.author,
      'modified': data.modified,
    });
  }
  textarea.update();
  return textarea;
};

PageAnnotator.prototype.deleteItem = function(name) {
  this.activate();
  var path = this.scope.project.getItem({"name": name});
  if (!path) {
    return;
  }

  path.remove();
};

function parseStrokeWidth(value) {
  if (typeof(value) !== "number") {
    value = parseInt(value, 10);
  }
  if (value <= 0 || isNaN(value)) {
    return DEFAULT_STROKE_WIDTH;
  }

  return value;
}

function Annotator(socketurl, id, userid, displayname, token) {
  this.e = $({});
  this.annotators = {};
  this.cursors = {};
  this.draw_mode = null;
  this.drawer = this.nulldrawer = new NullDrawer();
  this.id = id;
  this.userid = userid;
  this.displayname = displayname;
  this.token = token;
  this.storage = new Storage('pdfdraw');
  var color = this.storage.get('color');
  if (!color) {
    color = getRandomColor();
    this.storage.set('color', color);
  }
  this.color = color;
  var strokeWidth = this.storage.get('strokeWidth');
  if (!strokeWidth) {
    strokeWidth = DEFAULT_STROKE_WIDTH;
    this.storage.set('strokeWidth', strokeWidth);
  } else {
    strokeWidth = parseStrokeWidth(strokeWidth);
  }
  this.strokeWidth = strokeWidth;
  this.fontSize = 12;
  this.pageCount = -1;
  this.currentPage = null;
  this.users = {};
  this.has_document = false;
  this.setDrawMode('pointer');
  this.pending_messages = [];
  this.socketurl = socketurl;
  this.socket = io(socketurl, {
    'transports': ['websocket'],
    'query': {
      'token': token
    }
  });
  // Also allow polling connections when the connection was interrupted once
  // (could be caused by proxy / firewall).
  this.socket.io.on('reconnect_attempt', function() {
    this.socket.io.opts.transports = ['polling', 'websocket'];
  }.bind(this));
  this.socket.io.on('reconnect', this.onReconnected.bind(this));
  this.socket.on('message', this.onMessage.bind(this));
  this.socket.on('user.joined', this.onUserJoined.bind(this));
  this.socket.on('user.left', this.onUserLeft.bind(this));
  this.socket.on('connect', this.onConnected.bind(this));
  this.socket.on('disconnect', this.onDisconnect.bind(this));

  this.colorPicker = new iro.ColorPicker("#colorPicker", {
    width: 320,
    height: 320,
    color: this.color,
    transparency: true,
  });
  var setColor = function(color) {
    this.color = color.hex8String;
    this.storage.set('color', this.color);
    $(".modeButton.colorMode, .modeButton.colorMode:focus")
      .css("background-color", this.color);
    if (!this._updating_settings) {
      this.drawer.updateSettings(PageAnnotator.prototype.__active);
    }
  }.bind(this);
  this.colorPicker.on("color:init", setColor);
  this.colorPicker.on("color:change", setColor);

  $('#inputStrokeWidth').val(this.strokeWidth);
  $('#strokeWidthValue').text(this.strokeWidth);
  $('#inputStrokeWidth').on('input', function(e) {
    var strokeWidth = parseStrokeWidth(e.target.value);
    this.storage.set('strokeWidth', strokeWidth);
    this.strokeWidth = strokeWidth;
    if (!this._updating_settings) {
      this.drawer.updateSettings(PageAnnotator.prototype.__active);
    }
  }.bind(this));

  $('#inputStrokeWidth').on('change', function(e) {
    var strokeWidth = parseStrokeWidth(e.target.value);
    $('#strokeWidthValue').text(strokeWidth);
  }.bind(this));

  this.userlist = $("<div class='userlist'></div>");
  $("#mainContainer").append(this.userlist);
  this.connectionError = $("#connectionError");
  this.connectingMessage = $("#connectingMessage");

  this.e.on('mousedown', function(event, args) {
    if (this.settings_open) {
      event.preventDefault();
      return;
    }

    this.drawer.onMouseDown.apply(this.drawer, args);
  }.bind(this));
  this.e.on('mouseup', function(event, args) {
    if (this.settings_open) {
      event.preventDefault();
      return;
    }

    this.drawer.onMouseUp.apply(this.drawer, args);
  }.bind(this));
  this.e.on('mousedrag', function(event, args) {
    if (this.settings_open) {
      event.preventDefault();
      return;
    }

    this.drawer.onMouseDrag.apply(this.drawer, args);
  }.bind(this));
  this.e.on('mouseenter', function(event, args) {
    if (this.settings_open) {
      event.preventDefault();
      return;
    }

    this.drawer.onMouseEnter.apply(this.drawer, args);
  }.bind(this));
  this.e.on('mouseleave', function(event, args) {
    if (this.settings_open) {
      event.preventDefault();
      return;
    }

    this.drawer.onMouseLeave.apply(this.drawer, args);
  }.bind(this));
  this.e.on('mousemove', function(event, args) {
    if (this.settings_open) {
      event.preventDefault();
      return;
    }

    this.drawer.onMouseMove.apply(this.drawer, args);
  }.bind(this));
  this.e.on('keyup', function(event, args) {
    if (this.settings_open) {
      event.preventDefault();
      return;
    }

    this.drawer.onKeyUp.apply(this.drawer, args);
  }.bind(this));
  this.e.on('click', function(event, args) {
    if (this.settings_open) {
      this.drawer.updateSettings(args[0]);
      this.toggleSettings();
      return;
    }

    this.drawer.onClick.apply(this.drawer, args);
  }.bind(this));
}

Annotator.prototype.updateSettings = function(settings) {
  this._updating_settings = true;
  var prev = {
    'color': this.colorPicker.color.hex8String,
    'strokeWidth': this.strokeWidth,
  };
  this.colorPicker.color.hex8String = settings.color;
  $('#inputStrokeWidth').val(settings.strokeWidth);
  $('#strokeWidthValue').text($('#inputStrokeWidth').val());
  this._updating_settings = false;
  return prev;
};

Annotator.prototype.toggleSettings = function() {
  $("#settingsDialog").toggle();
  this.settings_open = !this.settings_open;
};

Annotator.prototype.onDisconnect = function() {
  this.users = {};
  this.updateUsersList();
  this.connectionError.show();
};

Annotator.prototype.onConnected = function() {
  if (!this.connectingMessage) {
    return;
  }

  this.connectingMessage.hide();
  this.connectingMessage = null;
};

Annotator.prototype.onReconnected = function() {
  this.connectionError.hide();
};

function compareEntries(a, b) {
  // Compare by display name.
  a = a[1].toLocaleLowerCase();
  b = b[1].toLocaleLowerCase();
  return a.localeCompare(b);
}

Annotator.prototype.updateUsersList = function() {
  var entries = [];
  for (var userid in this.users) {
    if (!this.users.hasOwnProperty(userid)) {
      continue;
    }

    entries.push([userid, this.users[userid].displayname || "Anonymous"]);
  }
  this.userlist.empty();
  if (!entries.length) {
    this.userlist.hide();
    return;
  }

  entries.sort(compareEntries);
  for (var i = 0; i < entries.length; ++i) {
    userid = entries[i][0];
    var displayname = entries[i][1];
    var elem = $("<div></div>").text(displayname);
    if (userid === this.socket.id) {
      elem.addClass("own");
    }
    this.userlist.append(elem);
  }
  this.userlist.show();
};

Annotator.prototype.onUserJoined = function(message) {
  for (var i = 0; i < message.length; ++i) {
    var userid = message[i].userid;
    if (!userid) {
      continue;
    }

    this.users[userid] = message[i];
  }
  this.updateUsersList();
};

Annotator.prototype.onUserLeft = function(message) {
  var userid = message.userid;
  if (!userid || !this.users.hasOwnProperty(userid)) {
    return;
  }

  delete this.users[userid];
  this.updateUsersList();
};

Annotator.prototype.documentLoaded = function(pdfDocument) {
  this.pageCount = pdfDocument.numPages;

  this.has_document= true;
  while (this.pending_messages.length) {
    var message = this.pending_messages.shift();
    this.onMessage(message);
  }

  if (this.connectingMessage) {
    this.connectingMessage.show();
  }
};

Annotator.prototype.sendMessage = function(message) {
  // console.log("Send", message);
  this.socket.emit('message', message);
};

Annotator.prototype.sendItem = function(page_annotator, item) {
  var data = item.exportJSON();
  if (!data) {
    console.log("Can't export to JSON", item);
    return;
  }

  this.sendMessage({
    'type': 'item',
    'item': {
      'page': page_annotator.pagenum,
      'name': item.name,
      'data': data
    }
  });
};

Annotator.prototype.deleteItem = function(page_annotator, item) {
  this.sendMessage({
    'type': 'delete',
    'delete': {
      'page': page_annotator.pagenum,
      'name': item.name
    }
  });
};

Annotator.prototype.onMessage = function(message) {
  // console.log("Received", message);
  if (!this.has_document) {
    this.pending_messages.push(message);
    return;
  }

  this.processMessage(message);
};

Annotator.prototype.renderCursor = function(page_annotator, x, y) {
  var cursor = this.getCursor(this.userid, 5);
  cursor.draw(page_annotator, x, y, this.color, this.displayname);
  var data = {
    'type': 'cursor',
    'cursor': {
      'action': 'show',
      'page': page_annotator.pagenum,
      'x': x,
      'y': y,
      'color': this.color
    }
  };
  this.sendMessage(data);
};

Annotator.prototype.getCursor = function(userid, size) {
  var cursor;
  if (!this.cursors.hasOwnProperty(userid)) {
    cursor = this.cursors[userid] = new Cursor(this, userid, size);
  } else {
    cursor = this.cursors[userid];
  }
  return cursor;
};

Annotator.prototype.showCursor = function(userid, data) {
  if (!this.users.hasOwnProperty(userid)) {
    return;
  }

  var displayname = this.users[userid].displayname || "Anonymous";
  var cursor = this.getCursor(userid, 5);
  this.getPage(data.page).then(function(page_annotator) {
    cursor.draw(page_annotator, data.x, data.y, data.color, displayname);
  });
};

Annotator.prototype.hideCursor = function(userid) {
  if (!userid) {
    var data = {
      'type': 'cursor',
      'cursor': {
        'action': 'hide'
      }
    };
    this.sendMessage(data);
    userid = this.userid;
  }
  if (!this.cursors.hasOwnProperty(userid)) {
    return;
  }

  var cursor = this.cursors[userid];
  delete this.cursors[userid];
  cursor.destroy();
};

Annotator.prototype.destroyCursors = function() {
  for (var i in this.cursors) {
    if (!this.cursors.hasOwnProperty(i)) {
      continue;
    }

    this.cursors[i].destroy();
  }
  this.cursors = {};
};

Annotator.prototype.getExistingPage = function(pagenum) {
  if (typeof(pagenum) === "string") {
    pagenum = parseInt(pagenum, 10);
  }
  if (!this.annotators.hasOwnProperty(pagenum)) {
    return null;
  }

  return this.annotators[pagenum];
};

Annotator.prototype.getPage = function(pagenum, page, container) {
  if (typeof(pagenum) === "string") {
    pagenum = parseInt(pagenum, 10);
  }
  return new Promise(function(resolve, reject) {
    var page_annotator;
    if (!this.annotators.hasOwnProperty(pagenum)) {
      page_annotator = this.annotators[pagenum] = new PageAnnotator(this, pagenum, container, page);
    } else {
      page_annotator = this.annotators[pagenum];
    }

    if (page_annotator.page && page_annotator.container) {
      return resolve(page_annotator);
    } else if (page && container) {
      page_annotator.setPage(page, container);
      return resolve(page_annotator);
    }
    if (page_annotator.page || !PDFViewerApplication.pdfViewer.pdfDocument) {
      return resolve(page_annotator);
    }

    PDFViewerApplication.pdfViewer.pdfDocument.getPage(pagenum).then(function(page) {
      page_annotator.setPage(page, page_annotator.container);
      page_annotator.update(PDFViewerApplication.pdfViewer.currentScale);
      return resolve(page_annotator);
    }, function(error) {
      return reject(error);
    });
  }.bind(this));
};

Annotator.prototype.destroyDrawer = function() {
  if (this.drawer && this.drawer !== this.nulldrawer) {
    var drawer = this.drawer;
    this.drawer = this.nulldrawer;
    drawer.destroy();
  }
};

Annotator.prototype.setDrawMode = function(mode) {
  $(".toolbarButton.selected").removeClass('selected');
  this.draw_mode = mode;
  $("#" + (mode || "none") + "Mode").addClass('selected');
  switch (mode) {
    case "select":
    case "color":
    case "pointer":
    case null:
      break;
    default:
      $("#drawModeToolbar").addClass('selected');
      break;
  }

  this.destroyDrawer();
  switch (this.draw_mode) {
    case "freehand":
      this.drawer = new FreehandDrawer(this);
      break;
    case "rectangle":
      this.drawer = new RectangleDrawer(this);
      break;
    case "ellipse":
      this.drawer = new EllipseDrawer(this);
      break;
    case "pointer":
      this.drawer = new PointerDrawer(this);
      break;
    case "select":
      this.drawer = new SelectDrawer(this);
      break;
    case "line":
      this.drawer = new LineDrawer(this);
      break;
    case "text":
      this.drawer = new TextDrawer(this);
      break;
    case null:
      break;
    default:
      console.log("Unknown draw mode", this.draw_mode);
      return;
  }
};

Annotator.prototype.switchPage = function(pagenum) {
  if (pagenum === this.currentPage) {
    return;
  }

  this.currentPage = pagenum;
  this.sendMessage({
    'type': 'control',
    'control': {
      'type': 'page',
      'page': pagenum
    }
  });
};

Annotator.prototype.processMessage = function(message) {
  switch (message.type) {
    case 'cursor':
      this.processCursorMessage(message.userid, message.cursor);
      break;
    case 'item':
      this.processItemMessage(message.userid, message.item);
      break;
    case 'delete':
      this.processDeleteMessage(message.userid, message.delete);
      break;
    case 'control':
      this.processControlMessage(message.userid, message.control);
      break;
    default:
      console.log('Unknown message', message);
      break;
  }
};

Annotator.prototype.processCursorMessage = function(userid, message) {
  switch (message.action) {
    case 'hide':
      this.hideCursor(userid);
      break;
    case 'show':
    default:
      this.showCursor(userid, message);
      break;
  }
};

Annotator.prototype.processItemMessage = function(userid, message) {
  if (typeof message.data === "string") {
    message.data = JSON.parse(message.data);
  }

  this.getPage(message.page).then(function(page_annotator) {
    switch (message.data.type) {
      case "text-annotation":
        page_annotator.drawTextAnnotation(userid, message.name, message.data);
        return;
    }

    page_annotator.drawItem(message.name, message.data);
  });
};

Annotator.prototype.processDeleteMessage = function(userid, message) {
  this.getPage(message.page).then(function(page_annotator) {
    page_annotator.deleteItem(message.name);
  });
};

Annotator.prototype.processControlMessage = function(userid, message) {
  switch (message.type) {
    case 'page':
      var page = message.page;
      if (page >= 1 && page <= PDFViewerApplication.pagesCount && page !== PDFViewerApplication.page) {
        this.currentPage = page;
        PDFViewerApplication.page = page;
      }
      break;
    default:
      console.log('Unsupported control message', message);
      return;
  }
};

Annotator.prototype.exportSVG = function() {
  return new Promise(function(resolve, reject) {
    var result = [];
    var pages = [];

    var continueExport = function() {
      // Remove trailing empty pages.
      while (result.length && !result[result.length-1]) {
        result.pop();
      }

      // No annotations yet?
      if (!result.length) {
        return resolve(result);
      }

      // Need to keep a last empty page to avoid duplicating the last overlay.
      if (result.length < this.pageCount) {
        var page = pages[result.length + 1];
        if (page) {
          var svg = page.exportSVG();
          result.push(svg);
        }
      }

      return resolve(result);
    }.bind(this);

    var remaining = this.pageCount;
    for (var i = 1; i <= this.pageCount; i++) {
      this.getPage(i).then(function(pagenum, page_annotator) {
        pages[pagenum] = page_annotator;
        var svg = null;
        if (page_annotator) {
          svg = page_annotator.exportSVG();
        }
        result[pagenum - 1] = svg;
        remaining -= 1;
        if (remaining === 0) {
          continueExport();
        }
      }.bind(this, i));
    }
  }.bind(this));
};

Annotator.prototype.exportTextAnnotations = function() {
  var list = [];
  each(this.annotators, function(page_annotator) {
    each(page_annotator.textAnnotations, function(annotation) {
      var scaleX = page_annotator.pagewidth / (annotation.circle.project.view.bounds.width * CSS_UNITS);
      var scaleY = page_annotator.pageheight / (annotation.circle.project.view.bounds.height * CSS_UNITS);
      var color = annotation.color;
      if (color.length === 9 && color[0] === '#') {
        // PDF annotations don't support transparency.
        color = color.substr(0, 7);
      }
      var taObj = {
        "page": page_annotator.pagenum - 1,
        "x": annotation.circle.position.x * scaleX,
        "y": annotation.circle.position.y * scaleY,
        "author": annotation.author,
        "modified": annotation.modified,
        "text": annotation.content,
        "color": color,
      };
      list.push(taObj);
    });
  });
  return list;
};

Annotator.prototype.downloadPdf = function() {
  this.exportSVG().then(function(svg) {
    if (!svg.length) {
      // No items drawn yet, download source PDF.
      console.log("Download source PDF");
      return;
    }

    var data = {
      "svg": svg,
      "token": this.token,
      "text": this.exportTextAnnotations(),
    };

    // Download code from https://stackoverflow.com/a/23797348
    var xhr = new XMLHttpRequest();
    var base_url = this.socketurl;
    if (base_url[base_url.length-1] !== '/') {
      base_url += '/';
    }
    xhr.open('POST', base_url + "download/" + this.id, true);
    xhr.responseType = 'arraybuffer';
    xhr.onload = function() {
      $("#downloadInProgress").hide();
      if (this.status !== 200) {
        $("#downloadFailed").show();
        setTimeout(function() {
          $("#downloadFailed").hide();
        }, 4000);
        return;
      }

      var filename = "";
      var disposition = xhr.getResponseHeader('Content-Disposition');
      if (disposition && disposition.indexOf('attachment') !== -1) {
        var filenameRegex = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/;
        var matches = filenameRegex.exec(disposition);
        if (matches != null && matches[1]) {
          filename = matches[1].replace(/['"]/g, '');
        }
      }
      var type = xhr.getResponseHeader('Content-Type');

      var blob = typeof File === 'function'
        ? new File([this.response], filename, { type: type })
        : new Blob([this.response], { type: type });
      if (typeof window.navigator.msSaveBlob !== 'undefined') {
        // IE workaround for "HTML7007: One or more blob URLs were revoked by
        // closing the blob for which they were created. These URLs will no
        // longer resolve as the data backing the URL has been freed."
        window.navigator.msSaveBlob(blob, filename);
      } else {
        var URL = window.URL || window.webkitURL;
        var downloadUrl = URL.createObjectURL(blob);

        if (filename) {
          // use HTML5 a[download] attribute to specify filename
          var a = document.createElement("a");
          // safari doesn't support this yet
          if (typeof a.download === 'undefined') {
            window.location = downloadUrl;
          } else {
            a.href = downloadUrl;
            a.download = filename;
            document.body.appendChild(a);
            a.click();
          }
        } else {
          window.location = downloadUrl;
        }

        setTimeout(function() {
          URL.revokeObjectURL(downloadUrl);
        }, 100); // cleanup
      }
    };
    xhr.setRequestHeader("Content-Type", "application/json");
    xhr.send(JSON.stringify(data));
    $("#downloadInProgress").show();
  }.bind(this));
};

$(document).ready(function() {
  var fileid = document.getElementsByTagName('head')[0].getAttribute('data-fileid');
  var userid = document.getElementsByTagName('head')[0].getAttribute('data-userid');
  var displayname = document.getElementsByTagName('head')[0].getAttribute('data-displayname');
  var socketurl = document.getElementsByTagName('head')[0].getAttribute('data-socketurl');
  var token = document.getElementsByTagName('head')[0].getAttribute('data-token');
  var permissions = parseInt(document.getElementsByTagName('head')[0].getAttribute('data-permissions'), 10);
  if (!fileid || !socketurl || !token) {
    window.location.href = '/';
    return;
  }

  var can_update = ((permissions & PERMISSION_UPDATE) === PERMISSION_UPDATE);

  var annotator = new Annotator(socketurl, fileid, userid, displayname, token);

  PDFViewerApplication.initializedPromise.then(function() {
    var eventBus = PDFViewerApplication.eventBus;
    eventBus.on('pagesloaded', function(event) {
      annotator.documentLoaded(PDFViewerApplication.pdfViewer.pdfDocument);
      if (!can_update) {
        $('#viewer .page[data-page-number!=1]').addClass('hiddenPage');
      }
    });

    eventBus.on('pagerendered', function(event) {
      var pagenum = event.pageNumber;
      if (!pagenum) {
        console.log("Rendered event without page number", event);
        return;
      }

      var container = $("#viewer .page[data-page-number='" + pagenum + "']");
      if (!container.length) {
        console.log("Could not get page container", event);
        return;
      }

      PDFViewerApplication.pdfViewer.pdfDocument.getPage(pagenum).then(function(page) {
        annotator.getPage(pagenum, page, container).then(function(page_annotator) {
          page_annotator.update(PDFViewerApplication.pdfViewer.currentScale);
        });
      });
    });

    eventBus.on('pagechanging', function(event) {
      var pagenum = event.pageNumber;
      if (can_update) {
        annotator.switchPage(pagenum);
      } else {
        $('#viewer .page').addClass('hiddenPage');
        var page = $('#viewer .page[data-page-number=' + pagenum +']');
        page.removeClass('hiddenPage');
        // Need to redraw currently visible page to fix any layout issues.
        setTimeout(function() {
          PDFViewerApplication.pdfViewer.update();
        }, 0);
      }
    });
  });

  $(".modeButton").click(function(event) {
    var button = $(event.target);
    var mode = button.data("mode") || null;
    if (mode === "color") {
      annotator.toggleSettings();
    } else {
      annotator.setDrawMode(mode);
    }
  });

  var $btnDrawMode = $(".toolbarButton.drawMode");
  $btnDrawMode.click(function(event) {
    $('#drawMenuToolbar').toggleClass('hidden');
  });
  $('#drawMenuToolbar .toolbarButton').each(function(_, elem) {
    var $elem = $(elem);
    $elem.click(function() {
      var mode = $(this).data('mode');
      // TODO(leon): This is an ugly hack to determine the previous mode
      $btnDrawMode.get(0).classList.forEach(function(c) {
        if (c !== 'drawMode' && c.indexOf('Mode') !== -1) {
          $btnDrawMode.removeClass(c);
        }
      });
      $btnDrawMode.addClass(mode + 'Mode');
      $('#drawMenuToolbar').addClass('hidden');
    });
  });

  $("#downloadPdf").click(function(event) {
    annotator.downloadPdf();
  });

  $("#secondaryToolbarClose").click(function() {
    history.back();
  });

  if (!can_update) {
    // User may not modify the file.
    $('#outerContainer').addClass('readonly');
    annotator.setDrawMode(null);
  }

  if (history.length <= 1) {
    // Annotation was opened in a new window. Closing windows through JS is not
    // possible - that's why we simply hide the button to not confuse users.
    $("#secondaryToolbarClose").hide();
  }
});

function setupPdfJs() {
  console.log("Loaded pdf.js", pdfjsLib.version, pdfjsLib.build);
  PDFViewerApplicationOptions.set("sidebarViewOnLoad", 0);
  PDFViewerApplicationOptions.set("showPreviousViewOnLoad", false);
  PDFViewerApplicationOptions.set("disablePageMode", true);
  PDFViewerApplicationOptions.set("isEvalSupported", false);
  PDFViewerApplicationOptions.set("cMapUrl", document.getElementsByTagName('head')[0].getAttribute('data-cmapurl'));
  PDFViewerApplicationOptions.set("workerSrc", document.getElementsByTagName('head')[0].getAttribute('data-workersrc'));
}

if (document.readyState === 'interactive' || document.readyState === 'complete') {
  setupPdfJs();
} else {
  document.addEventListener('DOMContentLoaded', setupPdfJs, true);
}
})();
