/**
 * @module meteoJS/thermodynamicDiagram/functions
 */
import { windspeedMSToKN } from '../calc.js';

/**
 * Definition of a line style. Some properties misses.
 * 
 * @typedef {Object} module:meteoJS/thermodynamicDiagram~lineStyleOptions
 * @property {string} [color='black'] - Color.
 * @property {number} [width=1] - Width.
 * @property {undefined|number} [opacity=undefined] - Opacity.
 * @property {'butt'|'round'|'square'} [linecap=undefined] - Linecap.
 * @property {'arcs'|'bevel'|'miter'|'miter-clip'|'round'} [linejoin=undefined]
 *   Linejoin.
 * @property {string} [dasharray=undefined] - Dasharray.
 * @property {number|string} [dashoffset=undefined] - Dashoffset.
 */

/**
 * Returns normalized lineStyle-Options.
 * 
 * @param {module:meteoJS/thermodynamicDiagram~lineStyleOptions}
 *   [options] - Options.
 * @param {module:meteoJS/thermodynamicDiagram~lineStyleOptions}
 *   [defaults] - Optional defaults.
 * @returns {module:meteoJS/thermodynamicDiagram~lineStyleOptions}
 *   Normalized options.
 * @private
 */
export function getNormalizedLineStyleOptions({
  color = undefined,
  width = undefined,
  ...result
} = {}, defaults = {}) {
  result.color = getFirstDefinedValue(color, defaults.color, 'black');
  result.width = getFirstDefinedValue(width, defaults.width, 1);
  Object.keys(defaults).forEach(key => {
    if (key != 'color' && key != 'width' && result[key] === undefined)
      result[key] = defaults[key];
  });
  return result;
}

/**
 * Definition of font options. Some properties misses.
 * 
 * @typedef {Object} module:meteoJS/thermodynamicDiagram~fontOptions
 * @param {mixed} [size=12] - Size.
 * @param {mixed} [color='black'] - Color.
 * @param {'start'|'middle'|'end'} [anchor=undefined] - Anchor.
 */

/**
 * Returns normalized font-Options.
 * 
 * @param {module:meteoJS/thermodynamicDiagram~fontOptions}
 *   [options] - Options.
 * @param {module:meteoJS/thermodynamicDiagram~fontOptions}
 *   [defaults] - Optional defaults.
 * @returns {module:meteoJS/thermodynamicDiagram~fontOptions}
 *   Normalized options.
 * @private
 */
export function getNormalizedFontOptions({
  size = undefined,
  color = undefined,
  anchor = undefined,
  ...result
} = {}, defaults = {}) {
  result.size = getFirstDefinedValue(size, defaults.size, 12);
  result.color = getFirstDefinedValue(color, defaults.color, 'black');
  anchor = getFirstDefinedValue(anchor, defaults.anchor);
  if (anchor !== undefined)
    result.anchor = anchor;
  Object.keys(defaults).forEach(key => {
    if (key != 'color' && key != 'size' && key != 'anchor'
      && result[key] === undefined)
      result[key] = defaults[key];
  });
  return result;
}

/**
 * A line with its visibility and style.
 * 
 * @typedef {Object} module:meteoJS/thermodynamicDiagram~lineOptions
 * @param {boolean} [visible=true] - Visibility of the line.
 * @param {module:meteoJS/thermodynamicDiagram~lineStyleOptions}
 *   [style] - Line style.
 */

/**
 * Returns normalized line options with visibility and line style.
 * 
 * @param {module:meteoJS/thermodynamicDiagram~lineOptions}
 *   [options] - Options.
 * @param {module:meteoJS/thermodynamicDiagram~lineOptions}
 *   [defaults] - Optional defaults.
 * @returns {module:meteoJS/thermodynamicDiagram~lineOptions}
 *   Normalized options.
 * @internal
 */
export function getNormalizedLineOptions({
  visible = undefined,
  style = {},
  ...result
} = {}, defaults = {}) {
  result.visible = getFirstDefinedValue(visible, defaults.visible, true);
  result.style = getNormalizedLineStyleOptions(style, defaults.style);
  Object.keys(defaults).forEach(key => {
    if (key != 'visible' && key != 'style' && result[key] === undefined)
      result[key] = defaults[key];
  });
  return result;
}

/**
 * A text with its visibility, style and font style.
 * 
 * @typedef {Object} module:meteoJS/thermodynamicDiagram~textOptions
 * @param {boolean} [visible=true] - Visibility of the line.
 * @param {module:meteoJS/thermodynamicDiagram~fontOptions}
 *   [font] - Font defintions.
 */

/**
 * Returns normalized text options with visibility and line style.
 * 
 * @param {module:meteoJS/thermodynamicDiagram~textOptions}
 *   [options] - Options.
 * @param {module:meteoJS/thermodynamicDiagram~textOptions}
 *   [defaults] - Optional defaults.
 * @returns {module:meteoJS/thermodynamicDiagram~textOptions}
 *   Normalized options.
 * @internal
 */
export function getNormalizedTextOptions({
  visible = true,
  font = {},
  ...result
} = {}, defaults = {}) {
  result.visible = getFirstDefinedValue(visible, defaults.visible, true);
  result.font = getNormalizedFontOptions(font, defaults.font);
  Object.keys(defaults).forEach(key => {
    if (key != 'visible' && key != 'font'
      && result[key] === undefined)
      result[key] = defaults[key];
  });
  return result;
}

/**
 * An object with its visibility, style and font style.
 * 
 * @typedef {module:meteoJS/thermodynamicDiagram~lineOptions}
 *   module:meteoJS/thermodynamicDiagram~lineTextOptions
 * @property {module:meteoJS/thermodynamicDiagram~fontOptions}
 *   [font] - Font defintions.
 */

/**
 * Returns normalized text options with visibility, line and font style.
 * 
 * @param {module:meteoJS/thermodynamicDiagram~lineTextOptions}
 *   [options] - Options.
 * @param {module:meteoJS/thermodynamicDiagram~lineTextOptions}
 *   [defaults] - Optional defaults.
 * @returns {module:meteoJS/thermodynamicDiagram~lineTextOptions}
 *   Normalized options.
 * @internal
 */
export function getNormalizedLineTextOptions({
  visible = true,
  style = {},
  font = {}
} = {}, defaults = {}) {
  return {
    visible: getFirstDefinedValue(visible, defaults.visible, true),
    style: getNormalizedLineStyleOptions(style, defaults.style),
    font: getNormalizedFontOptions(font, defaults.font)
  };
}

/**
 * Updates current options with some new options.
 * 
 * @param {module:meteoJS/thermodynamicDiagram~lineOptions}
 *   [options] - Current options.
 * @param {module:meteoJS/thermodynamicDiagram~lineOptions}
 *   [updateOptions] - Some new options.
 * @returns {module:meteoJS/thermodynamicDiagram~lineOptions}
 *   New options object.
 * @private
 */
export function updateLineOptions(options = {}, updateOptions = {}) {
  if ('visible' in updateOptions)
    options.visible = updateOptions.visible;
  if ('style' in updateOptions) {
    ['color', 'width', 'opacity',  'linecap',  'linejoin',  'dasharray']
      .forEach(styleKey => {
        if (styleKey in updateOptions.style)
          options.style[styleKey] = updateOptions.style[styleKey];
      });
  }
  return options;
}

/**
 * Returns the first defined argument.
 * 
 * @param {mixed|undefined} [...params] - Some values.
 * @returns {mixed|undefined} - First defined value, if available.
 */
export function getFirstDefinedValue(...params) {
  return params
    .reduce((acc, cur) => { return (acc !== undefined) ? acc : cur; });
}

/**
 * Draws text in a SVG node.
 * 
 * @param {Object} options - Options.
 * @param {external:SVG} options.node - SVG node.
 * @param {string} options.text - Text.
 * @param {number} options.x - X coordinate.
 * @param {number} options.y - Base Y coordinate.
 * @param {number} [options.horizontalMargin=0] - Padding in x direction.
 * @param {number} [options.verticalMargin=0] - Padding in y direction.
 * @param {module:meteoJS/thermodynamicDiagram~fontOptions} [options.font] - Font style.
 * @param {string|Object|undefined} [options.fill]
 *   Fill for background. If undefined, no background is drawn.
 * @returns {external:SVG} - SVG group containing the inserted elements.
 */
export function drawTextInto({
  node,
  text,
  x,
  y,
  horizontalMargin = 0,
  verticalMargin = 0,
  font = {},
  fill = undefined
}) {
  const group = node.group();
  let background = undefined;
  if (fill !== undefined) {
    if (!('color' in fill))
      fill.color = 'white';
    background = group.rect().fill(fill);
  }
  const f = {...font};
  let fontColor = undefined;
  if ('color' in f) {
    fontColor = f.color;
    delete f.color;
  }
  const textNode = group
    .text(text)
    .attr({ x, y })
    .font(font);
  if (fontColor !== undefined)
    textNode.fill(fontColor);
  if (font['alignment-baseline'] == 'bottom')
    textNode.dy(-textNode.bbox().height - 5);
  textNode
    .dx(horizontalMargin * ((textNode.attr('text-anchor') == 'end') ? -1 : 1))
    .dy(verticalMargin * ((font['alignment-baseline'] == 'bottom') ? -1 : 1));
  if (background !== undefined)
    background.attr({
      x: textNode.bbox().x,
      y: textNode.bbox().y,
      width: textNode.bbox().width,
      height: textNode.bbox().height
    });
  return group;
}

/**
 * Draws a windbarb into an SVG node.
 * 
 * @param {Object} options - Options.
 * @param {external:SVG} options.node - SVG node.
 * @param {number} [options.x=0] - X coordinate for windbarb tip.
 * @param {number} [options.y=0] - Y coordinate for windbarb tip.
 * @param {number} [options.wspd=0] - Wind speed [m/s].
 * @param {number} [options.wdir=0] - Wind direction [°].
 * @param {number} [options.length=50] - Windbarb length.
 * @param {module:meteoJS/thermodynamicDiagram~lineStyleOptions}
 *   [options.strokeStyle] - Line style.
 * @param {boolean} [options.fillTriangle=true] - Fill the 50 knots triangles.
 * @param {boolean} [options.triangleRatio=0.2]
 *   Width of the 50 knots triangles according to length.
 * @param {boolean} [options.barbDistanceRatio=0.1]
 *   Distance between triangles and/or 10 knot lines according to length.
 * @param {boolean} [options.barbHeightRatio=0.375]
 *   Height of the triangles and lines according to length.
 */
export function drawWindbarbInto({
  node,
  x = 0,
  y = 0,
  wspd = 0,
  wdir = 270,
  length = 50,
  strokeStyle = undefined,
  fillTriangle = true,
  triangleRatio = 1 / 5,
  barbDistanceRatio = 1 / 10,
  barbHeightRatio = 3 / 8,
  circleOnLowWindspeed = true,
  circleRadiusRatio = 1 / 10
} = {}) {
  strokeStyle = getNormalizedLineStyleOptions(strokeStyle);
  
  const windspeed = windspeedMSToKN(wspd);
  const windbarbGroup = node.group();
  const barbGroup = (windspeed >= 5) ? windbarbGroup.group() : undefined;
  const triangleWidth = length * triangleRatio;
  const barbDistance = length * barbDistanceRatio;
  const windbarbHeight = length * barbHeightRatio;
  let yPosition = y - length;
  let windspeedResidual = windspeed;
  
  if (windspeed < 5 && circleOnLowWindspeed) {
    windbarbGroup
      .circle(length * circleRadiusRatio)
      .cx(x)
      .cy(y)
      .stroke(strokeStyle)
      .fill('none');
    return;
  }
  
  // base line
  windbarbGroup
    .line(x, yPosition, x, y)
    .stroke(strokeStyle);
  
  // 50 knots triangles
  while (windspeedResidual >= 50) {
    barbGroup
      .polyline([
        [x, yPosition],
        [x + windbarbHeight, yPosition + triangleWidth/2],
        [x, yPosition + triangleWidth]
      ])
      .fill(fillTriangle ? strokeStyle : 'none')
      .stroke(strokeStyle);
    windspeedResidual -= 50;
    yPosition += triangleWidth + ((windspeedResidual >= 50) ? barbDistance/2 : barbDistance);
  }
  
  // 10 knots lines
  while (windspeedResidual >= 10) {
    barbGroup
      .line(
        x, yPosition,
        x + windbarbHeight, yPosition - triangleWidth/2
      )
      .stroke(strokeStyle);
    yPosition += barbDistance;
    windspeedResidual -= 10;
  }
  
  if (windspeed < 10)
    yPosition += barbDistance;
  
  // 5 knot line
  if (windspeedResidual >= 5)
    barbGroup
      .line(
        x, yPosition,
        x + windbarbHeight/2, yPosition - triangleWidth/4
      )
      .stroke(strokeStyle);
  
  // compress barbs on high windspeed
  const barbsWidth = yPosition - (y - length);
  if (barbsWidth > length * 0.9)
    barbGroup.scale(1, (length * 0.9) / barbsWidth, x, y - length);
  
  if (wdir != 0)
    windbarbGroup.rotate(wdir, x, y);
}