'use strict';

Object.defineProperty(exports, "__esModule", {
  value: true
});

var _index = require('../../index.js');

var _index2 = _interopRequireDefault(_index);

var _colorlike = require('../../colorlike.js');

var _colorlike2 = _interopRequireDefault(_colorlike);

var _dom = require('../../dom.js');

var _dom2 = _interopRequireDefault(_dom);

var _geometrytype = require('../../geom/geometrytype.js');

var _geometrytype2 = _interopRequireDefault(_geometrytype);

var _has = require('../../has.js');

var _has2 = _interopRequireDefault(_has);

var _replay = require('../replay.js');

var _replay2 = _interopRequireDefault(_replay);

var _webgl = require('../webgl.js');

var _webgl2 = _interopRequireDefault(_webgl);

var _texturereplay = require('../webgl/texturereplay.js');

var _texturereplay2 = _interopRequireDefault(_texturereplay);

var _atlasmanager = require('../../style/atlasmanager.js');

var _atlasmanager2 = _interopRequireDefault(_atlasmanager);

var _buffer = require('../../webgl/buffer.js');

var _buffer2 = _interopRequireDefault(_buffer);

function _interopRequireDefault(obj) {
  return obj && obj.__esModule ? obj : { default: obj };
}

/**
 * @constructor
 * @extends {ol.render.webgl.TextureReplay}
 * @param {number} tolerance Tolerance.
 * @param {ol.Extent} maxExtent Max extent.
 * @struct
 */
var _ol_render_webgl_TextReplay_ = function (tolerance, maxExtent) {
  _texturereplay2.default.call(this, tolerance, maxExtent);

  /**
   * @private
   * @type {Array.<HTMLCanvasElement>}
   */
  this.images_ = [];

  /**
   * @private
   * @type {Array.<WebGLTexture>}
   */
  this.textures_ = [];

  /**
   * @private
   * @type {HTMLCanvasElement}
   */
  this.measureCanvas_ = _dom2.default.createCanvasContext2D(0, 0).canvas;

  /**
   * @private
   * @type {{strokeColor: (ol.ColorLike|null),
   *         lineCap: (string|undefined),
   *         lineDash: Array.<number>,
   *         lineDashOffset: (number|undefined),
   *         lineJoin: (string|undefined),
   *         lineWidth: number,
   *         miterLimit: (number|undefined),
   *         fillColor: (ol.ColorLike|null),
   *         font: (string|undefined),
   *         scale: (number|undefined)}}
   */
  this.state_ = {
    strokeColor: null,
    lineCap: undefined,
    lineDash: null,
    lineDashOffset: undefined,
    lineJoin: undefined,
    lineWidth: 0,
    miterLimit: undefined,
    fillColor: null,
    font: undefined,
    scale: undefined
  };

  /**
   * @private
   * @type {string}
   */
  this.text_ = '';

  /**
   * @private
   * @type {number|undefined}
   */
  this.textAlign_ = undefined;

  /**
   * @private
   * @type {number|undefined}
   */
  this.textBaseline_ = undefined;

  /**
   * @private
   * @type {number|undefined}
   */
  this.offsetX_ = undefined;

  /**
   * @private
   * @type {number|undefined}
   */
  this.offsetY_ = undefined;

  /**
   * @private
   * @type {Object.<string, ol.WebglGlyphAtlas>}
   */
  this.atlases_ = {};

  /**
   * @private
   * @type {ol.WebglGlyphAtlas|undefined}
   */
  this.currAtlas_ = undefined;

  this.scale = 1;

  this.opacity = 1;
};

_index2.default.inherits(_ol_render_webgl_TextReplay_, _texturereplay2.default);

/**
 * @inheritDoc
 */
_ol_render_webgl_TextReplay_.prototype.drawText = function (geometry, feature) {
  if (this.text_) {
    var flatCoordinates = null;
    var offset = 0;
    var end = 2;
    var stride = 2;
    switch (geometry.getType()) {
      case _geometrytype2.default.POINT:
      case _geometrytype2.default.MULTI_POINT:
        flatCoordinates = geometry.getFlatCoordinates();
        end = flatCoordinates.length;
        stride = geometry.getStride();
        break;
      case _geometrytype2.default.CIRCLE:
        flatCoordinates = /** @type {ol.geom.Circle} */geometry.getCenter();
        break;
      case _geometrytype2.default.LINE_STRING:
        flatCoordinates = /** @type {ol.geom.LineString} */geometry.getFlatMidpoint();
        break;
      case _geometrytype2.default.MULTI_LINE_STRING:
        flatCoordinates = /** @type {ol.geom.MultiLineString} */geometry.getFlatMidpoints();
        end = flatCoordinates.length;
        break;
      case _geometrytype2.default.POLYGON:
        flatCoordinates = /** @type {ol.geom.Polygon} */geometry.getFlatInteriorPoint();
        break;
      case _geometrytype2.default.MULTI_POLYGON:
        flatCoordinates = /** @type {ol.geom.MultiPolygon} */geometry.getFlatInteriorPoints();
        end = flatCoordinates.length;
        break;
      default:
    }
    this.startIndices.push(this.indices.length);
    this.startIndicesFeature.push(feature);

    var glyphAtlas = this.currAtlas_;
    var lines = this.text_.split('\n');
    var textSize = this.getTextSize_(lines);
    var i, ii, j, jj, currX, currY, charArr, charInfo;
    var anchorX = Math.round(textSize[0] * this.textAlign_ - this.offsetX_);
    var anchorY = Math.round(textSize[1] * this.textBaseline_ - this.offsetY_);
    var lineWidth = this.state_.lineWidth / 2 * this.state_.scale;

    for (i = 0, ii = lines.length; i < ii; ++i) {
      currX = 0;
      currY = glyphAtlas.height * i;
      charArr = lines[i].split('');

      for (j = 0, jj = charArr.length; j < jj; ++j) {
        charInfo = glyphAtlas.atlas.getInfo(charArr[j]);

        if (charInfo) {
          var image = charInfo.image;

          this.anchorX = anchorX - currX;
          this.anchorY = anchorY - currY;
          this.originX = j === 0 ? charInfo.offsetX - lineWidth : charInfo.offsetX;
          this.originY = charInfo.offsetY;
          this.height = glyphAtlas.height;
          this.width = j === 0 || j === charArr.length - 1 ? glyphAtlas.width[charArr[j]] + lineWidth : glyphAtlas.width[charArr[j]];
          this.imageHeight = image.height;
          this.imageWidth = image.width;

          var currentImage;
          if (this.images_.length === 0) {
            this.images_.push(image);
          } else {
            currentImage = this.images_[this.images_.length - 1];
            if (_index2.default.getUid(currentImage) != _index2.default.getUid(image)) {
              this.groupIndices.push(this.indices.length);
              this.images_.push(image);
            }
          }

          this.drawText_(flatCoordinates, offset, end, stride);
        }
        currX += this.width;
      }
    }
  }
};

/**
 * @private
 * @param {Array.<string>} lines Label to draw split to lines.
 * @return {Array.<number>} Size of the label in pixels.
 */
_ol_render_webgl_TextReplay_.prototype.getTextSize_ = function (lines) {
  var self = this;
  var glyphAtlas = this.currAtlas_;
  var textHeight = lines.length * glyphAtlas.height;
  //Split every line to an array of chars, sum up their width, and select the longest.
  var textWidth = lines.map(function (str) {
    var sum = 0;
    var i, ii;
    for (i = 0, ii = str.length; i < ii; ++i) {
      var curr = str[i];
      if (!glyphAtlas.width[curr]) {
        self.addCharToAtlas_(curr);
      }
      sum += glyphAtlas.width[curr] ? glyphAtlas.width[curr] : 0;
    }
    return sum;
  }).reduce(function (max, curr) {
    return Math.max(max, curr);
  });

  return [textWidth, textHeight];
};

/**
 * @private
 * @param {Array.<number>} flatCoordinates Flat coordinates.
 * @param {number} offset Offset.
 * @param {number} end End.
 * @param {number} stride Stride.
 */
_ol_render_webgl_TextReplay_.prototype.drawText_ = function (flatCoordinates, offset, end, stride) {
  var i, ii;
  for (i = offset, ii = end; i < ii; i += stride) {
    this.drawCoordinates(flatCoordinates, offset, end, stride);
  }
};

/**
 * @private
 * @param {string} char Character.
 */
_ol_render_webgl_TextReplay_.prototype.addCharToAtlas_ = function (char) {
  if (char.length === 1) {
    var glyphAtlas = this.currAtlas_;
    var state = this.state_;
    var mCtx = this.measureCanvas_.getContext('2d');
    mCtx.font = state.font;
    var width = Math.ceil(mCtx.measureText(char).width * state.scale);

    var info = glyphAtlas.atlas.add(char, width, glyphAtlas.height, function (ctx, x, y) {
      //Parameterize the canvas
      ctx.font = /** @type {string} */state.font;
      ctx.fillStyle = state.fillColor;
      ctx.strokeStyle = state.strokeColor;
      ctx.lineWidth = state.lineWidth;
      ctx.lineCap = /*** @type {string} */state.lineCap;
      ctx.lineJoin = /** @type {string} */state.lineJoin;
      ctx.miterLimit = /** @type {number} */state.miterLimit;
      ctx.textAlign = 'left';
      ctx.textBaseline = 'top';
      if (_has2.default.CANVAS_LINE_DASH && state.lineDash) {
        //FIXME: use pixelRatio
        ctx.setLineDash(state.lineDash);
        ctx.lineDashOffset = /** @type {number} */state.lineDashOffset;
      }
      if (state.scale !== 1) {
        //FIXME: use pixelRatio
        ctx.setTransform( /** @type {number} */state.scale, 0, 0,
        /** @type {number} */state.scale, 0, 0);
      }

      //Draw the character on the canvas
      if (state.strokeColor) {
        ctx.strokeText(char, x, y);
      }
      if (state.fillColor) {
        ctx.fillText(char, x, y);
      }
    });

    if (info) {
      glyphAtlas.width[char] = width;
    }
  }
};

/**
 * @inheritDoc
 */
_ol_render_webgl_TextReplay_.prototype.finish = function (context) {
  var gl = context.getGL();

  this.groupIndices.push(this.indices.length);
  this.hitDetectionGroupIndices = this.groupIndices;

  // create, bind, and populate the vertices buffer
  this.verticesBuffer = new _buffer2.default(this.vertices);

  // create, bind, and populate the indices buffer
  this.indicesBuffer = new _buffer2.default(this.indices);

  // create textures
  /** @type {Object.<string, WebGLTexture>} */
  var texturePerImage = {};

  this.createTextures(this.textures_, this.images_, texturePerImage, gl);

  this.state_ = {
    strokeColor: null,
    lineCap: undefined,
    lineDash: null,
    lineDashOffset: undefined,
    lineJoin: undefined,
    lineWidth: 0,
    miterLimit: undefined,
    fillColor: null,
    font: undefined,
    scale: undefined
  };
  this.text_ = '';
  this.textAlign_ = undefined;
  this.textBaseline_ = undefined;
  this.offsetX_ = undefined;
  this.offsetY_ = undefined;
  this.images_ = null;
  this.atlases_ = {};
  this.currAtlas_ = undefined;
  _texturereplay2.default.prototype.finish.call(this, context);
};

/**
 * @inheritDoc
 */
_ol_render_webgl_TextReplay_.prototype.setTextStyle = function (textStyle) {
  var state = this.state_;
  var textFillStyle = textStyle.getFill();
  var textStrokeStyle = textStyle.getStroke();
  if (!textStyle || !textStyle.getText() || !textFillStyle && !textStrokeStyle) {
    this.text_ = '';
  } else {
    if (!textFillStyle) {
      state.fillColor = null;
    } else {
      var textFillStyleColor = textFillStyle.getColor();
      state.fillColor = _colorlike2.default.asColorLike(textFillStyleColor ? textFillStyleColor : _webgl2.default.defaultFillStyle);
    }
    if (!textStrokeStyle) {
      state.strokeColor = null;
      state.lineWidth = 0;
    } else {
      var textStrokeStyleColor = textStrokeStyle.getColor();
      state.strokeColor = _colorlike2.default.asColorLike(textStrokeStyleColor ? textStrokeStyleColor : _webgl2.default.defaultStrokeStyle);
      state.lineWidth = textStrokeStyle.getWidth() || _webgl2.default.defaultLineWidth;
      state.lineCap = textStrokeStyle.getLineCap() || _webgl2.default.defaultLineCap;
      state.lineDashOffset = textStrokeStyle.getLineDashOffset() || _webgl2.default.defaultLineDashOffset;
      state.lineJoin = textStrokeStyle.getLineJoin() || _webgl2.default.defaultLineJoin;
      state.miterLimit = textStrokeStyle.getMiterLimit() || _webgl2.default.defaultMiterLimit;
      var lineDash = textStrokeStyle.getLineDash();
      state.lineDash = lineDash ? lineDash.slice() : _webgl2.default.defaultLineDash;
    }
    state.font = textStyle.getFont() || _webgl2.default.defaultFont;
    state.scale = textStyle.getScale() || 1;
    this.text_ = /** @type {string} */textStyle.getText();
    var textAlign = _replay2.default.TEXT_ALIGN[textStyle.getTextAlign()];
    var textBaseline = _replay2.default.TEXT_ALIGN[textStyle.getTextBaseline()];
    this.textAlign_ = textAlign === undefined ? _webgl2.default.defaultTextAlign : textAlign;
    this.textBaseline_ = textBaseline === undefined ? _webgl2.default.defaultTextBaseline : textBaseline;
    this.offsetX_ = textStyle.getOffsetX() || 0;
    this.offsetY_ = textStyle.getOffsetY() || 0;
    this.rotateWithView = !!textStyle.getRotateWithView();
    this.rotation = textStyle.getRotation() || 0;

    this.currAtlas_ = this.getAtlas_(state);
  }
};

/**
 * @private
 * @param {Object} state Font attributes.
 * @return {ol.WebglGlyphAtlas} Glyph atlas.
 */
_ol_render_webgl_TextReplay_.prototype.getAtlas_ = function (state) {
  var params = [];
  var i;
  for (i in state) {
    if (state[i] || state[i] === 0) {
      if (Array.isArray(state[i])) {
        params = params.concat(state[i]);
      } else {
        params.push(state[i]);
      }
    }
  }
  var hash = this.calculateHash_(params);
  if (!this.atlases_[hash]) {
    var mCtx = this.measureCanvas_.getContext('2d');
    mCtx.font = state.font;
    var height = Math.ceil((mCtx.measureText('M').width * 1.5 + state.lineWidth / 2) * state.scale);

    this.atlases_[hash] = {
      atlas: new _atlasmanager2.default({
        space: state.lineWidth + 1
      }),
      width: {},
      height: height
    };
  }
  return this.atlases_[hash];
};

/**
 * @private
 * @param {Array.<string|number>} params Array of parameters.
 * @return {string} Hash string.
 */
_ol_render_webgl_TextReplay_.prototype.calculateHash_ = function (params) {
  //TODO: Create a more performant, reliable, general hash function.
  var i, ii;
  var hash = '';
  for (i = 0, ii = params.length; i < ii; ++i) {
    hash += params[i];
  }
  return hash;
};

/**
 * @inheritDoc
 */
_ol_render_webgl_TextReplay_.prototype.getTextures = function (opt_all) {
  return this.textures_;
};

/**
 * @inheritDoc
 */
_ol_render_webgl_TextReplay_.prototype.getHitDetectionTextures = function () {
  return this.textures_;
};
exports.default = _ol_render_webgl_TextReplay_;
module.exports = exports['default'];