var fixShadow = require("./utils/fix_shadow");

var _constants = require("./constants");

var ContextCachedBy = _constants.ContextCachedBy;

var _constants2 = require("../graphic/constants");

var mathMin = _constants2.mathMin;

/**
 * @class qrenderer.graphic.Style
 * 
 * @docauthor 大漠穷秋 <damoqiongqiu@126.com>
 */
var STYLE_COMMON_PROPS = [['shadowBlur', 0], ['shadowOffsetX', 0], ['shadowOffsetY', 0], ['shadowColor', '#000'], ['lineCap', 'butt'], ['lineJoin', 'miter'], ['miterLimit', 10]];

function createLinearGradient(ctx, obj, rect) {
  var x = obj.x == null ? 0 : obj.x;
  var x2 = obj.x2 == null ? 1 : obj.x2;
  var y = obj.y == null ? 0 : obj.y;
  var y2 = obj.y2 == null ? 0 : obj.y2;

  if (!obj.global) {
    x = x * rect.width + rect.x;
    x2 = x2 * rect.width + rect.x;
    y = y * rect.height + rect.y;
    y2 = y2 * rect.height + rect.y;
  } // Fix NaN when rect is Infinity


  x = isNaN(x) ? 0 : x;
  x2 = isNaN(x2) ? 1 : x2;
  y = isNaN(y) ? 0 : y;
  y2 = isNaN(y2) ? 0 : y2;
  var canvasGradient = ctx.createLinearGradient(x, y, x2, y2);
  return canvasGradient;
}

function createRadialGradient(ctx, obj, rect) {
  var width = rect.width;
  var height = rect.height;
  var min = mathMin(width, height);
  var x = obj.x == null ? 0.5 : obj.x;
  var y = obj.y == null ? 0.5 : obj.y;
  var r = obj.r == null ? 0.5 : obj.r;

  if (!obj.global) {
    x = x * width + rect.x;
    y = y * height + rect.y;
    r = r * min;
  }

  var canvasGradient = ctx.createRadialGradient(x, y, 0, x, y, r);
  return canvasGradient;
}

var Style = function Style(opts) {
  this.extendStyle(opts, false);
};

Style.prototype = {
  constructor: Style,

  /**
   * @property {String} fill
   */
  fill: '#000',

  /**
   * @property {String} stroke
   */
  stroke: null,

  /**
   * @property {Number} opacity
   */
  opacity: 1,

  /**
   * @property {Number} fillOpacity
   */
  fillOpacity: null,

  /**
   * @property {Number} strokeOpacity
   */
  strokeOpacity: null,

  /**
   * @property {Array<Number>|Boolean} lineDash
   * `true` is not supported.
   * `false`/`null`/`undefined` are the same.
   * `false` is used to remove lineDash in some
   * case that `null`/`undefined` can not be set.
   * (e.g., emphasis.lineStyle in echarts)
   */
  lineDash: null,

  /**
   * @property {Number} lineDashOffset
   */
  lineDashOffset: 0,

  /**
   * @property {Number} shadowBlur
   */
  shadowBlur: 0,

  /**
   * @property {Number} shadowOffsetX
   */
  shadowOffsetX: 0,

  /**
   * @property {Number} shadowOffsetY
   */
  shadowOffsetY: 0,

  /**
   * @property {Number} lineWidth
   */
  lineWidth: 1,

  /**
   * @property {Boolean} strokeNoScale
   * If stroke ignore scale
   */
  strokeNoScale: false,
  // Bounding rect text configuration
  // Not affected by element transform

  /**
   * @property {String} text
   */
  text: null,

  /**
   * @property {String} font
   * If `fontSize` or `fontFamily` exists, `font` will be reset by
   * `fontSize`, `fontStyle`, `fontWeight`, `fontFamily`.
   * So do not visit it directly in upper application (like echarts),
   * but use `contain/text#makeFont` instead.
   */
  font: null,

  /**
   * @deprecated
   * @property {String} textFont
   * The same as font. Use font please.
   */
  textFont: null,

  /**
   * @property {String} fontStyle
   * It helps merging respectively, rather than parsing an entire font string.
   */
  fontStyle: null,

  /**
   * @property {String} fontWeight
   * It helps merging respectively, rather than parsing an entire font string.
   */
  fontWeight: null,

  /**
   * @property {Number} fontSize
   * It helps merging respectively, rather than parsing an entire font string.
   * Should be 12 but not '12px'.
   */
  fontSize: null,

  /**
   * @property {String} fontFamily
   * It helps merging respectively, rather than parsing an entire font string.
   */
  fontFamily: null,

  /**
   * @property {String} textTag
   * Reserved for special functinality, like 'hr'.
   */
  textTag: null,

  /**
   * @property {String} textFill
   */
  textFill: '#000',

  /**
   * @property {String} textStroke
   */
  textStroke: null,

  /**
   * @property {Number} textWidth
   */
  textWidth: null,

  /**
   * @property {Number} textHeight
   * Only for textBackground.
   */
  textHeight: null,

  /**
   * @property {Number} textStrokeWidth
   * textStroke may be set as some color as a default
   * value in upper applicaion, where the default value
   * of textStrokeWidth should be 0 to make sure that
   * user can choose to do not use text stroke.
   */
  textStrokeWidth: 0,

  /**
   * @property {Number} textLineHeight
   */
  textLineHeight: null,

  /**
   * @property {string|Array<Number>} textPosition
   * 'inside', 'left', 'right', 'top', 'bottom'
   * [x, y]
   * Based on x, y of rect.
   */
  textPosition: 'inside',

  /**
   * @property {Object} textRect
   * If not specified, use the boundingRect of a `displayable`.
   */
  textRect: null,

  /**
   * @property {Array<Number>} textOffset
   * [x, y]
   */
  textOffset: null,

  /**
   * @property {String} textAlign
   */
  textAlign: null,

  /**
   * @property {String} textVerticalAlign
   */
  textVerticalAlign: null,

  /**
   * @property {Number} textDistance
   */
  textDistance: 5,

  /**
   * @property {String} textShadowColor
   */
  textShadowColor: 'transparent',

  /**
   * @property {Number} textShadowBlur
   */
  textShadowBlur: 0,

  /**
   * @property {Number} textShadowOffsetX
   */
  textShadowOffsetX: 0,

  /**
   * @property {Number} textShadowOffsetY
   */
  textShadowOffsetY: 0,

  /**
   * @property {String} textBoxShadowColor
   */
  textBoxShadowColor: 'transparent',

  /**
   * @property {Number} textBoxShadowBlur
   */
  textBoxShadowBlur: 0,

  /**
   * @property {Number} textBoxShadowOffsetX
   */
  textBoxShadowOffsetX: 0,

  /**
   * @property {Number} textBoxShadowOffsetY
   */
  textBoxShadowOffsetY: 0,

  /**
   * @property {Boolean} transformText
   * Whether transform text.
   * Only available in Path and Image element,
   * where the text is called as `RectText`.
   */
  transformText: false,

  /**
   * @property {Number} textRotation
   * Text rotate around position of Path or Image.
   * The origin of the rotation can be specified by `textOrigin`.
   * Only available in Path and Image element,
   * where the text is called as `RectText`.
   */
  textRotation: 0,

  /**
   * @property {String|Array<Number>} textOrigin
   * Text origin of text rotation.
   * Useful in the case like label rotation of circular symbol.
   * Only available in Path and Image element, where the text is called
   * as `RectText` and the element is called as "host element".
   * The value can be:
   * + If specified as a coordinate like `[10, 40]`, it is the `[x, y]`
   * base on the left-top corner of the rect of its host element.
   * + If specified as a string `center`, it is the center of the rect of
   * its host element.
   * + By default, this origin is the `textPosition`.
   */
  textOrigin: null,

  /**
   * @property {String} textBackgroundColor
   */
  textBackgroundColor: null,

  /**
   * @property {String} textBorderColor
   */
  textBorderColor: null,

  /**
   * @property {Number} textBorderWidth
   */
  textBorderWidth: 0,

  /**
   * @property {Number} textBorderRadius
   */
  textBorderRadius: 0,

  /**
   * @property {number|Array<Number>} textPadding
   * Can be `2` or `[2, 4]` or `[2, 3, 4, 5]`
   */
  textPadding: null,

  /**
   * @property {Object} rich
   * Text styles for rich text.
   */
  rich: null,

  /**
   * @property {Object} truncate
   * {outerWidth, outerHeight, ellipsis, placeholder}
   */
  truncate: null,

  /**
   * @property {String} blend
   * https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/globalCompositeOperation
   */
  blend: null,

  /**
   * @method bind
   * @param {CanvasRenderingContext2D} ctx
   * @param {Element} el
   * @param {Element} prevEl
   */
  bind: function bind(ctx, el, prevEl) {
    var style = this;
    var prevStyle = prevEl && prevEl.style; // If no prevStyle, it means first draw.
    // Only apply cache if the last time cachced by this function.

    var notCheckCache = !prevStyle || ctx.__attrCachedBy !== ContextCachedBy.STYLE_BIND;
    ctx.__attrCachedBy = ContextCachedBy.STYLE_BIND;

    for (var i = 0; i < STYLE_COMMON_PROPS.length; i++) {
      var prop = STYLE_COMMON_PROPS[i];
      var styleName = prop[0];

      if (notCheckCache || style[styleName] !== prevStyle[styleName]) {
        // FIXME Invalid property value will cause style leak from previous element.
        ctx[styleName] = fixShadow(ctx, styleName, style[styleName] || prop[1]);
      }
    }

    if (notCheckCache || style.fill !== prevStyle.fill) {
      ctx.fillStyle = style.fill;
    }

    if (notCheckCache || style.stroke !== prevStyle.stroke) {
      ctx.strokeStyle = style.stroke;
    }

    if (notCheckCache || style.opacity !== prevStyle.opacity) {
      ctx.globalAlpha = style.opacity == null ? 1 : style.opacity;
    }

    if (notCheckCache || style.blend !== prevStyle.blend) {
      ctx.globalCompositeOperation = style.blend || 'source-over';
    }

    if (this.hasStroke()) {
      var lineWidth = style.lineWidth;
      ctx.lineWidth = lineWidth / (this.strokeNoScale && el && el.getLineScale ? el.getLineScale() : 1);
    }
  },

  /**
   * @method hasFill
   */
  hasFill: function hasFill() {
    var fill = this.fill;
    return fill != null && fill !== 'none';
  },

  /**
   * @method hasStroke
   */
  hasStroke: function hasStroke() {
    var stroke = this.stroke;
    return stroke != null && stroke !== 'none' && this.lineWidth > 0;
  },

  /**
   * @method extendStyle
   * Extend from other style
   * @param {Style} otherStyle
   * @param {Boolean} overwrite true: overwrirte any way.
   *                            false: overwrite only when !target.hasOwnProperty
   *                            others: overwrite when property is not null/undefined.
   */
  extendStyle: function extendStyle(otherStyle, overwrite) {
    if (otherStyle) {
      for (var name in otherStyle) {
        if (otherStyle.hasOwnProperty(name) && (overwrite === true || (overwrite === false ? !this.hasOwnProperty(name) : otherStyle[name] != null))) {
          this[name] = otherStyle[name];
        }
      }
    }
  },

  /**
   * @method set
   * Batch setting style with a given object
   * @param {Object|String} obj
   * @param {*} [obj]
   */
  set: function set(obj, value) {
    if (typeof obj === 'string') {
      this[obj] = value;
    } else {
      this.extendStyle(obj, true);
    }
  },

  /**
   * @method clone
   * @return {Style}
   */
  clone: function clone() {
    var newStyle = new this.constructor();
    newStyle.extendStyle(this, true);
    return newStyle;
  },

  /**
   * @method getGradient
   * @param {*} ctx 
   * @param {*} obj 
   * @param {*} rect 
   */
  getGradient: function getGradient(ctx, obj, rect) {
    var method = obj.type === 'radial' ? createRadialGradient : createLinearGradient;
    var canvasGradient = method(ctx, obj, rect);
    var colorStops = obj.colorStops;

    for (var i = 0; i < colorStops.length; i++) {
      canvasGradient.addColorStop(colorStops[i].offset, colorStops[i].color);
    }

    return canvasGradient;
  }
};
var styleProto = Style.prototype;

for (var i = 0; i < STYLE_COMMON_PROPS.length; i++) {
  var prop = STYLE_COMMON_PROPS[i];

  if (!(prop[0] in styleProto)) {
    styleProto[prop[0]] = prop[1];
  }
} // Provide for others


Style.getGradient = styleProto.getGradient;
var _default = Style;
module.exports = _default;