/**
 * @module meteoJS/thermodynamicDiagram/windspeedProfile
 */
import {
  windspeedMSToKN,
  windspeedKNToMS,
  windspeedMSToKMH
} from '../calc.js';
import {
  getNormalizedFontOptions,
  getNormalizedLineOptions,
  drawTextInto
} from './Functions.js';
import PlotAltitudeDataArea from './PlotAltitudeDataArea.js';

/**
 * Triggered, when the windspeedMax changes.
 * 
 * @event module:meteoJS/thermodynamicDiagram/windspeedProfile#change:windspeedMax
 */

/**
 * Options for labels on hovering the windspeed profile.
 * 
 * @typedef {module:meteoJS/thermodynamicDiagram/plotAltitudeDataArea~hoverLabelsOptions}
 *   module:meteoJS/thermodynamicDiagram/windspeedProfile~hoverLabelsOptions
 * @property {Object} [windspeed]
 *   Options for the output of the windspeed value.
 * @property {boolean} [windspeed.visible=true] - Visibility.
 * @property {string} [windspeed.unit='kn']
 *   Unit of the value text. Allowed values: 'm/s', 'kn', 'km/h'
 * @property {integer} [windspeed.decimalPlaces=0]
 *   Number of digits to appear after the decimal point.
 * @property {string} [windspeed.prefix=' kn'] - Prefix of the value text.
 * @property {number} [windspeedMax=77.17]
 *   The maximum visible windspeed. Unit: m/s.
 */

/**
 * Isobar grid lines.
 * 
 * @typedef {module:meteoJS/thermodynamicDiagram~lineOptions}
 *   module:meteoJS/thermodynamicDiagram/windspeedProfile~isobarsOptions
 * @property {number} [max]
 *   Maximum isobar value for the grid lines. By default, this is the
 *   maximum pressure of the coordinate system for x=0.
 * @property {number} [min]
 *   Minimum isobar value for the grid lines. By default, this is the
 *   minimum pressure of the coordinate system for x=0.
 * @property {number} [interval=100]
 *   Interval between the grid lines.
 */

/**
 * Isotach grid lines.
 * 
 * @typedef {module:meteoJS/thermodynamicDiagram~lineOptions}
 *   module:meteoJS/thermodynamicDiagram/windspeedProfile~isotachsOptions
 * @property {number} [max=undefined]
 *   Maximum windspeed value for the grid lines. By default, this is the
 *   maximum visible windspeed.
 * @property {number} [min=0]
 *   Value for the first grid line.
 * @property {number} [interval=25.72]
 *   Interval between the grid lines.
 */

/**
 * Options for the constructor.
 * 
 * @typedef {module:meteoJS/thermodynamicDiagram/plotAltitudeDataArea~options}
 *   module:meteoJS/thermodynamicDiagram/windspeedProfile~options
 * @property {number} [windspeedMax=77.17] - Maximum visible windspeed, in m/s.
 * @property {Object} [grid] - Options for grid.
 * @property {module:meteoJS/thermodynamicDiagram/windspeedProfile~isotachsOptions}
 *   [grid.isotachs] - Options for isotach grid. By default, the lines are grey and dashed.
 * @property {module:meteoJS/thermodynamicDiagram/windspeedProfile~isobarsOptions}
 *   [grid.isobars] - Options for isobar grid. By default, the lines are grey and dashed.
 * @property {module:meteoJS/thermodynamicDiagram/windspeedProfile~hoverLabelsOptions}
 *   [hoverLabels] - Hover labels options.
 */

/**
 * Class to draw windspeed profiles.
 * 
 * <pre><code>import WindspeedProfile from 'meteojs/thermodynamicDiagram/WindspeedProfile';</code></pre>
 * 
 * @extends module:meteoJS/thermodynamicDiagram/plotAltitudeDataArea.PlotAltitudeDataArea
 * @fires module:meteoJS/thermodynamicDiagram/windspeedProfile#change:windspeedMax
 */
export class WindspeedProfile extends PlotAltitudeDataArea {
  
  /**
   * @param {module:meteoJS/thermodynamicDiagram/windspeedProfile~options} options
   *   Options.
   */
  constructor({
    svgNode = undefined,
    coordinateSystem = undefined,
    x = undefined,
    y = undefined,
    width = undefined,
    height = undefined,
    style = {},
    visible = true,
    events = {},
    hoverLabels = {},
    dataGroupIds = ['windspeed'],
    getCoordinatesByLevelData = (dataGroupId, sounding, levelData, plotArea) => {
      if (levelData.pres === undefined ||
          levelData.wspd === undefined)
        return {};
      
      return {
        x: plotArea.width * levelData.wspd / plotArea.windspeedMax,
        y: plotArea.coordinateSystem.height -
          plotArea.coordinateSystem.getYByXP(0, levelData.pres)
      };
    },
    insertDataGroupInto = (svgNode, dataGroupId, sounding, data) => {
      svgNode
        .polyline(data.map(level => [ level.x, level.y ]))
        .fill('none')
        .stroke(sounding.options.windprofile.windspeed.style);
    },
    windspeedMax = windspeedKNToMS(150),
    grid = {},
    filterDataPoint = undefined,
    minDataPointsDistance = 0
  } = {}) {
    super({
      svgNode,
      coordinateSystem,
      x,
      y,
      width,
      height,
      style,
      visible,
      events,
      hoverLabels,
      getSoundingVisibility:
        sounding => sounding.visible && sounding.options.windprofile.windspeed.visible,
      dataGroupIds,
      getCoordinatesByLevelData,
      insertDataGroupInto,
      filterDataPoint,
      minDataPointsDistance
    });

    /**
     * @type number
     * @private
     */
    this._windspeedMax = windspeedMax;

    this._gridOptions = this.getNormalizedGridOptions(grid);
    
    this.init();
  }

  /**
   * The maximum visible windspeed. Unit: m/s.
   * 
   * @type number
   */
  get windspeedMax() {
    return this._windspeedMax;
  }
  set windspeedMax(windspeedMax) {
    const oldWindspeedMax = this._windspeedMax;
    this._windspeedMax = windspeedMax;
    if (this._windspeedMax != oldWindspeedMax)
      this.trigger('change:windspeedMax');
  }
  
  /**
   * Draw background into SVG group.
   * 
   * @override
   */
  _drawBackground(svgNode) {
    super._drawBackground(svgNode);

    // isobars
    if (this._gridOptions.isobars.visible) {
      const isobarsNode = svgNode.group();
      for (let i=this._gridOptions.isobars.min; i<=this._gridOptions.isobars.max; i+=this._gridOptions.isobars.interval) {
        const y = this.coordinateSystem.height - this.coordinateSystem.getYByXP(0, i);
        isobarsNode
          .line(0, y, this.width, y)
          .stroke(this._gridOptions.isobars.style);
      }
    }

    // isotach grid
    if (this._gridOptions.isotachs.visible) {
      const isotachsNode = svgNode.group();
      for (let i=this._gridOptions.isotachs.min; i<=this._gridOptions.isotachs.max; i+=this._gridOptions.isotachs.interval) {
        const x = this.width * i / this.windspeedMax;
        isotachsNode
          .line(x, 0, x, this.height)
          .stroke(this._gridOptions.isotachs.style);
      }
    }
  }
  
  /**
   * Initialize hover labels options.
   * 
   * @param {module:meteoJS/thermodynamicDiagram/windspeedProfile~hoverLabelsOptions}
   *   options - Hover labels options.
   * @override
   */
  _initHoverLabels({
    visible = true,
    type = 'mousemove',
    maxDistance = undefined,
    remote = true,
    insertLabelsFunc = undefined,
    getHoverSounding = undefined,
    windspeed = {}
  }) {
    if (!('visible' in windspeed))
      windspeed.visible = true;
    if (!('style' in windspeed))
      windspeed.style = {};
    windspeed.font = getNormalizedFontOptions(windspeed.font, {
      anchor: 'end',
      'alignment-baseline': 'bottom'
    });
    if (!('fill' in windspeed))
      windspeed.fill = {};
    if (windspeed.fill.opacity === undefined)
      windspeed.fill.opacity = 0.7;
    windspeed.radius = ('radius' in windspeed) ? windspeed.radius : undefined;
    windspeed.radiusPlus =
      ('radiusPlus' in windspeed) ? windspeed.radiusPlus : 2;
    if (windspeed.horizontalMargin === undefined)
      windspeed.horizontalMargin = 10;
    
    if (insertLabelsFunc === undefined)
      insertLabelsFunc = this._makeInsertLabelsFunc(windspeed);
    
    super._initHoverLabels({
      visible,
      type,
      maxDistance,
      remote,
      insertLabelsFunc,
      getHoverSounding
    });
  }
  
  /**
   * Makes a default insertLabelsFunc.
   * 
   * @param {module:meteoJS/thermodynamicDiagram/windspeedProfile~hoverLabelsOptions}
   *   options - Style options for the hover labels.
   * @private
   */
  _makeInsertLabelsFunc({
    visible = true,
    style = {},
    font = {},
    fill = {},
    horizontalMargin = 10,
    verticalMargin = 0,
    radius = undefined,
    radiusPlus = 2,
    windspeed = {},
  }) {
    windspeed =  (({
      visible = true,
      unit = 'kn',
      decimalPlaces = 0,
      prefix = ' kn'
    }) => { return { visible, unit, decimalPlaces, prefix }; })(windspeed);
    return (sounding, levelData, group) => {
      group.clear();
      
      if (levelData === undefined
        || levelData.pres === undefined
        || !windspeed.visible)
        return;
      
      if (!visible ||
          levelData.wspd === undefined)
        return;
      
      const { x, y } =
        this._getCoordinatesByLevelData('windspeed',
          sounding, levelData, this);
      if (x === undefined ||
          y === undefined)
        return;
      
      const dotRadius = (radius === undefined)
        ? sounding.options.windprofile.windspeed.style.width / 2 +
          radiusPlus
        : radius;
      const fillOptions = {...style}; // Deep copy
      if (!('color' in fillOptions))
        fillOptions.color = sounding.options.windprofile.windspeed.style.color;
      group
        .circle(2 * dotRadius)
        .attr({ cx: x, cy: y })
        .fill(fillOptions);
      const labelFont = {...font}; // Deep copy
      if (labelFont.anchor == 'start' &&
          this.width - x < 45)
        labelFont.anchor = 'end';
      if (labelFont.anchor == 'end' &&
          x < 45)
        labelFont.anchor = 'start';
      if (labelFont['alignment-baseline'] == 'bottom' &&
          y < labelFont.size * 5/4)
        labelFont['alignment-baseline'] = 'top';
      if (labelFont['alignment-baseline'] == 'top' &&
          this.height - y < labelFont.size * 5/4)
        labelFont['alignment-baseline'] = 'bottom';
      let text = '';
      switch (windspeed.unit) {
      case 'm/s':
        text = Number.parseFloat(levelData.wspd)
          .toFixed(windspeed.decimalPlaces);
        break;
      case 'kn':
        text = windspeedMSToKN(levelData.wspd)
          .toFixed(windspeed.decimalPlaces);
        break;
      default:
        text = windspeedMSToKMH(levelData.wspd)
          .toFixed(windspeed.decimalPlaces);
        break;
      }
      text = `${text}${windspeed.prefix}`;
      drawTextInto({
        node: group,
        text,
        x,
        y,
        horizontalMargin,
        verticalMargin,
        font: labelFont,
        fill
      });
    };
  }
  
  /**
   * Normalizes options for grid.
   * 
   * @private
   */
  getNormalizedGridOptions({
    isotachs = {},
    isobars = {}
  }) {
    isotachs = getNormalizedIsolineOptions(isotachs, {
      min: 0,
      max: this._windspeedMax,
      interval: windspeedKNToMS(50),
      style: {
        color: 'grey',
        dasharray: '2 2'
      }
    });
    const isobarsInterval = 100;
    const min = (this.coordinateSystem === undefined)
      ? 100
      : Math.ceil(this.coordinateSystem.getPByXY(0, this.height)/isobarsInterval)*isobarsInterval;
    const max = (this.coordinateSystem === undefined)
      ? 1050
      : Math.floor(this.coordinateSystem.getPByXY(0, 0)/isobarsInterval)*isobarsInterval;
    isobars = getNormalizedIsolineOptions(isobars, {
      min,
      max,
      interval: isobarsInterval,
      style: {
        color: 'grey',
        dasharray: '1 3'
      }
    });
    
    return {
      isotachs,
      isobars
    };
  }
}
export default WindspeedProfile;

/**
 * Normalize grid options.
 * 
 * @param {module:meteoJS/thermodynamicDiagram/windspeedProfile~isobarsOptions|module:meteoJS/thermodynamicDiagram/windspeedProfile~windspeedOptions}
 *   options - Options.
 * @returns {module:meteoJS/thermodynamicDiagram/windspeedProfile~isobarsOptions|module:meteoJS/thermodynamicDiagram/windspeedProfile~windspeedOptions}
 *   Normalized options.
 */
function getNormalizedIsolineOptions({
  min = undefined,
  max = undefined,
  interval = undefined,
  ...rest
}, defaults = {}) {
  const options = getNormalizedLineOptions({ ...rest }, defaults);
  options.min = (min === undefined) ? defaults.min : min;
  options.max = (max === undefined) ? defaults.max : max;
  options.interval = (interval === undefined) ? defaults.interval : interval;
  return options;
}