/* Copyright 2014 Mozilla Foundation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
/* globals PDFJS */

'use strict';

(function (root, factory) {
  if (typeof define === 'function' && define.amd) {
    define('pdfjs/display/annotation_layer', ['exports', 'pdfjs/shared/util',
      'pdfjs/display/dom_utils'], factory);
  } else if (typeof exports !== 'undefined') {
    factory(exports, require('../shared/util.js'), require('./dom_utils.js'));
  } else {
    factory((root.pdfjsDisplayAnnotationLayer = {}), root.pdfjsSharedUtil,
      root.pdfjsDisplayDOMUtils);
  }
}(this, function (exports, sharedUtil, displayDOMUtils) {

var AnnotationBorderStyleType = sharedUtil.AnnotationBorderStyleType;
var AnnotationType = sharedUtil.AnnotationType;
var Util = sharedUtil.Util;
var isExternalLinkTargetSet = sharedUtil.isExternalLinkTargetSet;
var LinkTargetStringMap = sharedUtil.LinkTargetStringMap;
var warn = sharedUtil.warn;
var CustomStyle = displayDOMUtils.CustomStyle;

var ANNOT_MIN_SIZE = 10; // px

/**
 * @typedef {Object} AnnotationElementParameters
 * @property {Object} data
 * @property {PDFPage} page
 * @property {PageViewport} viewport
 * @property {IPDFLinkService} linkService
 */

/**
 * @class
 * @alias AnnotationElementFactory
 */
function AnnotationElementFactory() {}
AnnotationElementFactory.prototype =
    /** @lends AnnotationElementFactory.prototype */ {
  /**
   * @param {AnnotationElementParameters} parameters
   * @returns {AnnotationElement}
   */
  create: function AnnotationElementFactory_create(parameters) {
    var subtype = parameters.data.annotationType;

    switch (subtype) {
      case AnnotationType.LINK:
        return new LinkAnnotationElement(parameters);

      case AnnotationType.TEXT:
        return new TextAnnotationElement(parameters);

      case AnnotationType.WIDGET:
        return new WidgetAnnotationElement(parameters);

      default:
        throw new Error('Unimplemented annotation type "' + subtype + '"');
    }
  }
};

/**
 * @class
 * @alias AnnotationElement
 */
var AnnotationElement = (function AnnotationElementClosure() {
  function AnnotationElement(parameters) {
    this.data = parameters.data;
    this.page = parameters.page;
    this.viewport = parameters.viewport;
    this.linkService = parameters.linkService;

    this.container = this._createContainer();
  }

  AnnotationElement.prototype = /** @lends AnnotationElement.prototype */ {
    /**
     * Create an empty container for the annotation's HTML element.
     *
     * @private
     * @memberof AnnotationElement
     * @returns {HTMLSectionElement}
     */
    _createContainer: function AnnotationElement_createContainer() {
      var data = this.data, page = this.page, viewport = this.viewport;
      var container = document.createElement('section');
      var width = data.rect[2] - data.rect[0];
      var height = data.rect[3] - data.rect[1];

      container.setAttribute('data-annotation-id', data.id);

      data.rect = Util.normalizeRect([
        data.rect[0],
        page.view[3] - data.rect[1] + page.view[1],
        data.rect[2],
        page.view[3] - data.rect[3] + page.view[1]
      ]);

      CustomStyle.setProp('transform', container,
                          'matrix(' + viewport.transform.join(',') + ')');
      CustomStyle.setProp('transformOrigin', container,
                          -data.rect[0] + 'px ' + -data.rect[1] + 'px');

      if (data.borderStyle.width > 0) {
        container.style.borderWidth = data.borderStyle.width + 'px';
        if (data.borderStyle.style !== AnnotationBorderStyleType.UNDERLINE) {
          // Underline styles only have a bottom border, so we do not need
          // to adjust for all borders. This yields a similar result as
          // Adobe Acrobat/Reader.
          width = width - 2 * data.borderStyle.width;
          height = height - 2 * data.borderStyle.width;
        }

        var horizontalRadius = data.borderStyle.horizontalCornerRadius;
        var verticalRadius = data.borderStyle.verticalCornerRadius;
        if (horizontalRadius > 0 || verticalRadius > 0) {
          var radius = horizontalRadius + 'px / ' + verticalRadius + 'px';
          CustomStyle.setProp('borderRadius', container, radius);
        }

        switch (data.borderStyle.style) {
          case AnnotationBorderStyleType.SOLID:
            container.style.borderStyle = 'solid';
            break;

          case AnnotationBorderStyleType.DASHED:
            container.style.borderStyle = 'dashed';
            break;

          case AnnotationBorderStyleType.BEVELED:
            warn('Unimplemented border style: beveled');
            break;

          case AnnotationBorderStyleType.INSET:
            warn('Unimplemented border style: inset');
            break;

          case AnnotationBorderStyleType.UNDERLINE:
            container.style.borderBottomStyle = 'solid';
            break;

          default:
            break;
        }

        if (data.color) {
          container.style.borderColor =
            Util.makeCssRgb(data.color[0] | 0,
                            data.color[1] | 0,
                            data.color[2] | 0);
        } else {
          // Transparent (invisible) border, so do not draw it at all.
          container.style.borderWidth = 0;
        }
      }

      container.style.left = data.rect[0] + 'px';
      container.style.top = data.rect[1] + 'px';

      container.style.width = width + 'px';
      container.style.height = height + 'px';

      return container;
    },

    /**
     * Render the annotation's HTML element in the empty container.
     *
     * @public
     * @memberof AnnotationElement
     */
    render: function AnnotationElement_render() {
      throw new Error('Abstract method AnnotationElement.render called');
    }
  };

  return AnnotationElement;
})();

/**
 * @class
 * @alias LinkAnnotationElement
 */
var LinkAnnotationElement = (function LinkAnnotationElementClosure() {
  function LinkAnnotationElement(parameters) {
    AnnotationElement.call(this, parameters);
  }

  Util.inherit(LinkAnnotationElement, AnnotationElement, {
    /**
     * Render the link annotation's HTML element in the empty container.
     *
     * @public
     * @memberof LinkAnnotationElement
     * @returns {HTMLSectionElement}
     */
    render: function LinkAnnotationElement_render() {
      this.container.className = 'annotLink';

      var link = document.createElement('a');
      link.href = link.title = this.data.url || '';

      if (this.data.url && isExternalLinkTargetSet()) {
        link.target = LinkTargetStringMap[PDFJS.externalLinkTarget];
      }

      // Strip referrer from the URL.
      if (this.data.url) {
        link.rel = PDFJS.externalLinkRel;
      }

      if (!this.data.url) {
        if (this.data.action) {
          this._bindNamedAction(link, this.data.action);
        } else {
          this._bindLink(link, ('dest' in this.data) ? this.data.dest : null);
        }
      }

      this.container.appendChild(link);
      return this.container;
    },

    /**
     * Bind internal links to the link element.
     *
     * @private
     * @param {Object} link
     * @param {Object} destination
     * @memberof LinkAnnotationElement
     */
    _bindLink: function LinkAnnotationElement_bindLink(link, destination) {
      var self = this;

      link.href = this.linkService.getDestinationHash(destination);
      link.onclick = function() {
        if (destination) {
          self.linkService.navigateTo(destination);
        }
        return false;
      };
      if (destination) {
        link.className = 'internalLink';
      }
    },

    /**
     * Bind named actions to the link element.
     *
     * @private
     * @param {Object} link
     * @param {Object} action
     * @memberof LinkAnnotationElement
     */
    _bindNamedAction:
        function LinkAnnotationElement_bindNamedAction(link, action) {
      var self = this;

      link.href = this.linkService.getAnchorUrl('');
      link.onclick = function() {
        self.linkService.executeNamedAction(action);
        return false;
      };
      link.className = 'internalLink';
    }
  });

  return LinkAnnotationElement;
})();

/**
 * @class
 * @alias TextAnnotationElement
 */
var TextAnnotationElement = (function TextAnnotationElementClosure() {
  function TextAnnotationElement(parameters) {
    AnnotationElement.call(this, parameters);

    this.pinned = false;
  }

  Util.inherit(TextAnnotationElement, AnnotationElement, {
    /**
     * Render the text annotation's HTML element in the empty container.
     *
     * @public
     * @memberof TextAnnotationElement
     * @returns {HTMLSectionElement}
     */
    render: function TextAnnotationElement_render() {
      var rect = this.data.rect, container = this.container;

      // Sanity check because of OOo-generated PDFs.
      if ((rect[3] - rect[1]) < ANNOT_MIN_SIZE) {
        rect[3] = rect[1] + ANNOT_MIN_SIZE;
      }
      if ((rect[2] - rect[0]) < ANNOT_MIN_SIZE) {
        rect[2] = rect[0] + (rect[3] - rect[1]); // make it square
      }

      container.className = 'annotText';

      var image  = document.createElement('img');
      image.style.height = container.style.height;
      image.style.width = container.style.width;
      var iconName = this.data.name;
      image.src = PDFJS.imageResourcesPath + 'annotation-' +
        iconName.toLowerCase() + '.svg';
      image.alt = '[{{type}} Annotation]';
      image.dataset.l10nId = 'text_annotation_type';
      image.dataset.l10nArgs = JSON.stringify({type: iconName});

      var contentWrapper = document.createElement('div');
      contentWrapper.className = 'annotTextContentWrapper';
      contentWrapper.style.left = Math.floor(rect[2] - rect[0] + 5) + 'px';
      contentWrapper.style.top = '-10px';

      var content = this.content = document.createElement('div');
      content.className = 'annotTextContent';
      content.setAttribute('hidden', true);

      var i, ii;
      if (this.data.hasBgColor && this.data.color) {
        var color = this.data.color;

        // Enlighten the color (70%).
        var BACKGROUND_ENLIGHT = 0.7;
        var r = BACKGROUND_ENLIGHT * (255 - color[0]) + color[0];
        var g = BACKGROUND_ENLIGHT * (255 - color[1]) + color[1];
        var b = BACKGROUND_ENLIGHT * (255 - color[2]) + color[2];
        content.style.backgroundColor = Util.makeCssRgb(r | 0, g | 0, b | 0);
      }

      var title = document.createElement('h1');
      var text = document.createElement('p');
      title.textContent = this.data.title;

      if (!this.data.content && !this.data.title) {
        content.setAttribute('hidden', true);
      } else {
        var e = document.createElement('span');
        var lines = this.data.content.split(/(?:\r\n?|\n)/);
        for (i = 0, ii = lines.length; i < ii; ++i) {
          var line = lines[i];
          e.appendChild(document.createTextNode(line));
          if (i < (ii - 1)) {
            e.appendChild(document.createElement('br'));
          }
        }
        text.appendChild(e);

        image.addEventListener('click', this._toggle.bind(this));
        image.addEventListener('mouseover', this._show.bind(this, false));
        image.addEventListener('mouseout', this._hide.bind(this, false));
        content.addEventListener('click', this._hide.bind(this, true));
      }

      content.appendChild(title);
      content.appendChild(text);
      contentWrapper.appendChild(content);
      container.appendChild(image);
      container.appendChild(contentWrapper);
      return container;
    },

    /**
     * Toggle the visibility of the content box.
     *
     * @private
     * @memberof TextAnnotationElement
     */
    _toggle: function TextAnnotationElement_toggle() {
      if (this.pinned) {
        this._hide(true);
      } else {
        this._show(true);
      }
    },

    /**
     * Show the content box.
     *
     * @private
     * @param {boolean} pin
     * @memberof TextAnnotationElement
     */
    _show: function TextAnnotationElement_show(pin) {
      if (pin) {
        this.pinned = true;
      }
      if (this.content.hasAttribute('hidden')) {
        this.container.style.zIndex += 1;
        this.content.removeAttribute('hidden');
      }
    },

    /**
     * Hide the content box.
     *
     * @private
     * @param {boolean} unpin
     * @memberof TextAnnotationElement
     */
    _hide: function TextAnnotationElement_hide(unpin) {
      if (unpin) {
        this.pinned = false;
      }
      if (!this.content.hasAttribute('hidden') && !this.pinned) {
        this.container.style.zIndex -= 1;
        this.content.setAttribute('hidden', true);
      }
    }
  });

  return TextAnnotationElement;
})();

/**
 * @class
 * @alias WidgetAnnotationElement
 */
var WidgetAnnotationElement = (function WidgetAnnotationElementClosure() {
  function WidgetAnnotationElement(parameters) {
    AnnotationElement.call(this, parameters);
  }

  Util.inherit(WidgetAnnotationElement, AnnotationElement, {
    /**
     * Render the widget annotation's HTML element in the empty container.
     *
     * @public
     * @memberof WidgetAnnotationElement
     * @returns {HTMLSectionElement}
     */
    render: function WidgetAnnotationElement_render() {
      var content = document.createElement('div');
      content.textContent = this.data.fieldValue;
      var textAlignment = this.data.textAlignment;
      content.style.textAlign = ['left', 'center', 'right'][textAlignment];
      content.style.verticalAlign = 'middle';
      content.style.display = 'table-cell';

      var font = (this.data.fontRefName ?
        this.page.commonObjs.getData(this.data.fontRefName) : null);
      this._setTextStyle(content, font);

      this.container.appendChild(content);
      return this.container;
    },

    /**
     * Apply text styles to the text in the element.
     *
     * @private
     * @param {HTMLDivElement} element
     * @param {Object} font
     * @memberof WidgetAnnotationElement
     */
    _setTextStyle:
        function WidgetAnnotationElement_setTextStyle(element, font) {
      // TODO: This duplicates some of the logic in CanvasGraphics.setFont().
      var style = element.style;
      style.fontSize = this.data.fontSize + 'px';
      style.direction = (this.data.fontDirection < 0 ? 'rtl': 'ltr');

      if (!font) {
        return;
      }

      style.fontWeight = (font.black ?
        (font.bold ? '900' : 'bold') :
        (font.bold ? 'bold' : 'normal'));
      style.fontStyle = (font.italic ? 'italic' : 'normal');

      // Use a reasonable default font if the font doesn't specify a fallback.
      var fontFamily = font.loadedName ? '"' + font.loadedName + '", ' : '';
      var fallbackName = font.fallbackName || 'Helvetica, sans-serif';
      style.fontFamily = fontFamily + fallbackName;
    }
  });

  return WidgetAnnotationElement;
})();

/**
 * @typedef {Object} AnnotationLayerParameters
 * @property {PageViewport} viewport
 * @property {HTMLDivElement} div
 * @property {Array} annotations
 * @property {PDFPage} page
 * @property {IPDFLinkService} linkService
 */

/**
 * @class
 * @alias AnnotationLayer
 */
var AnnotationLayer = (function AnnotationLayerClosure() {
  return {
    /**
     * Render a new annotation layer with all annotation elements.
     *
     * @public
     * @param {AnnotationLayerParameters} parameters
     * @memberof AnnotationLayer
     */
    render: function AnnotationLayer_render(parameters) {
      var annotationElementFactory = new AnnotationElementFactory();

      for (var i = 0, ii = parameters.annotations.length; i < ii; i++) {
        var data = parameters.annotations[i];
        if (!data || !data.hasHtml) {
          continue;
        }

        var properties = {
          data: data,
          page: parameters.page,
          viewport: parameters.viewport,
          linkService: parameters.linkService
        };
        var element = annotationElementFactory.create(properties);
        parameters.div.appendChild(element.render());
      }
    },

    /**
     * Update the annotation elements on existing annotation layer.
     *
     * @public
     * @param {AnnotationLayerParameters} parameters
     * @memberof AnnotationLayer
     */
    update: function AnnotationLayer_update(parameters) {
      for (var i = 0, ii = parameters.annotations.length; i < ii; i++) {
        var data = parameters.annotations[i];
        var element = parameters.div.querySelector(
          '[data-annotation-id="' + data.id + '"]');
        if (element) {
          CustomStyle.setProp('transform', element,
            'matrix(' + parameters.viewport.transform.join(',') + ')');
        }
      }
      parameters.div.removeAttribute('hidden');
    }
  };
})();

PDFJS.AnnotationLayer = AnnotationLayer;

exports.AnnotationLayer = AnnotationLayer;
}));