Source: parameter.js

import { getElements, isNullOrUndefined, toWKT } from './utils';


/**
 * @module opensearch/parameter
 */

const typeRE = /{([a-zA-Z:]+)([?]?)}/;

function parseType(value) {
  const match = typeRE.exec(value);
  if (match) {
    return match[1];
  }
  return null;
}

function isMandatory(value) {
  return typeRE.exec(value)[2] !== '?';
}

function eoValueToString(value, isDate = false) {
  const convertDate = (dateValue) => {
    if (dateValue instanceof Date) {
      return dateValue.toISOString();
    }
    return value;
  };

  if (typeof value === 'string') {
    return value;
  } else if (typeof value === 'number') {
    return value.toString();
  } else if (isDate && value instanceof Date) {
    return convertDate(value);
  } else if (Array.isArray(value)) {
    if (isDate) {
      return `{${value.map(convertDate).join(',')}}`;
    }
    return `{${value.join(',')}}`;
  }

  let left = null;
  let right = null;
  if (Object.prototype.hasOwnProperty.call(value, 'min')) {
    left = `[${isDate ? convertDate(value.min) : value.min}`;
  } else if (Object.prototype.hasOwnProperty.call(value, 'minExclusive')) {
    left = `]${isDate ? convertDate(value.minExclusive) : value.minExclusive}`;
  }

  if (Object.prototype.hasOwnProperty.call(value, 'max')) {
    right = `${isDate ? convertDate(value.max) : value.max}]`;
  } else if (Object.prototype.hasOwnProperty.call(value, 'maxExclusive')) {
    right = `${isDate ? convertDate(value.maxExclusive) : value.maxExclusive}[`;
  }

  if (left !== null && right !== null) {
    return `${left},${right}`;
  } else if (left !== null) {
    return left;
  }
  return right;
}

/**
 * Class to describe a single OpenSearch URL parameter.
 */
export class OpenSearchParameter {
  /**
   * Class to describe a single OpenSearch URL parameter.
   * @param {string} type The type of the parameter
   * @param {string} name The name of the parameter
   * @param {boolean} mandatory Whether the parameter is mandatory
   * @param {object[]} [options=null] The possible values for this parameter
   * @param {string} options[].label The label of the option
   * @param {string} options[].value The value of the option
   * @param {Number} [minExclusive=undefined] The minimum value allowed for this
                                              parameter (exclusive)
   * @param {Number} [maxExclusive=undefined] The maximum value allowed for this
                                              parameter (exclusive)
   * @param {Number} [minInclusive=undefined] The minimum value allowed for this
                                              parameter (inclusive)
   * @param {Number} [maxInclusive=undefined] The maximum value allowed for this
                                              parameter (inclusive)
   */
  constructor(type, name, mandatory, options = null,
    minExclusive = undefined, maxExclusive = undefined,
    minInclusive = undefined, maxInclusive = undefined) {
    this._type = type;
    this._name = name;
    this._mandatory = mandatory;
    this._options = options;
    this._minExclusive = minExclusive;
    this._maxExclusive = maxExclusive;
    this._minInclusive = minInclusive;
    this._maxInclusive = maxInclusive;
  }

  /**
   * The type of the parameter
   * @readonly
   */
  get type() {
    return this._type;
  }

  /**
   * The name of the parameter
   * @readonly
   */
  get name() {
    return this._name;
  }

  /**
   * Whether the parameter is mandatory
   * @readonly
   */
  get mandatory() {
    return this._mandatory;
  }

  /**
   * The possible values for this parameter
   * @readonly
   */
  get options() {
    return this._options;
  }

  /**
   * The minimum value allowed for this parameter (exclusive)
   * @readonly
   */
  get minExclusive() {
    return this._minExclusive;
  }

  /**
   * The maximum value allowed for this parameter (exclusive)
   * @readonly
   */
  get maxExclusive() {
    return this._maxExclusive;
  }

  /**
   * The minimum value allowed for this parameter (inclusive)
   * @readonly
   */
  get minInclusive() {
    return this._minInclusive;
  }

  /**
   * The maximum value allowed for this parameter (inclusive)
   * @readonly
   */
  get maxInclusive() {
    return this._maxInclusive;
  }

  /**
   * Combines this parameter with the values of another parameter.
   * @param {OpenSearchParameter} other the other parameter
   * @returns {OpenSearchParameter} the combined parameter
   */
  combined(other) {
    return new OpenSearchParameter(
      this.type, this.name,
      isNullOrUndefined(this.mandatory) ? other.mandatory : this.mandatory,
      isNullOrUndefined(this.options) ? other.options : this.options,
      isNullOrUndefined(this.minExclusive) ? other.minExclusive : this.minExclusive,
      isNullOrUndefined(this.maxExclusive) ? other.maxExclusive : this.maxExclusive,
      isNullOrUndefined(this.minInclusive) ? other.minInclusive : this.minInclusive,
      isNullOrUndefined(this.maxInclusive) ? other.maxInclusive : this.maxInclusive
    );
  }

  /**
   * Serialize the given value according to the internal type to be sent in a
   * request.
   * @param {Number|string|Date|array|object} value The value to serialize. The
   *                                                allowed types depend on the
   *                                                internal type.
   * @returns {string} the serialized value.
   */
  serializeValue(value) {
    switch (this.type) {
      case 'time:start':
      case 'time:end':
        if (value instanceof Date) {
          return value.toISOString();
        }
        break;
      case 'geo:box':
        if (Array.isArray(value)) {
          return value.join(',');
        }
        break;
      case 'geo:geometry':
        return toWKT(value);
      case 'eo:orbitNumber':
      case 'eo:track':
      case 'eo:frame':
      case 'eo:cloudCover':
      case 'eo:snowCover':
      case 'eo:startTimeFromAscendingNode':
      case 'eo:completionTimeFromAscendingNode':
      case 'eo:illuminationAzimuthAngle':
      case 'eo:illuminationZenithAngle':
      case 'eo:illuminationElevationAngle':
      case 'eo:minimumIncidenceAngle':
      case 'eo:maximumIncidenceAngle':
      case 'eo:dopplerFrequency':
      case 'eo:incidenceAngleVariation':
        return eoValueToString(value);
      case 'eo:availabilityTime':
      case 'eo:creationDate':
      case 'eo:modificationDate':
      case 'eo:processingDate':
        return eoValueToString(value, true);
      default:
        break;
    }
    return value;
  }

  /**
   * Constructs a new OpenSearchParameter from a DOM-Node.
   * @param {DOMNode} node the node to create the parameter from.
   * @returns {OpenSearchParameter} the constructed parameters object.
   */
  static fromNode(node) {
    const type = parseType(node.getAttribute('value'));
    const name = node.getAttribute('name');
    const mandatory = node.hasAttribute('minimum')
                        ? node.getAttribute('minimum') !== '0' : undefined;
    const minExclusive = node.hasAttribute('minExclusive')
                          ? parseInt(node.getAttribute('minExclusive'), 10)
                          : undefined;
    const maxExclusive = node.hasAttribute('maxExclusive')
                          ? parseInt(node.getAttribute('maxExclusive'), 10)
                          : undefined;
    const minInclusive = node.hasAttribute('minInclusive')
                          ? parseInt(node.getAttribute('minInclusive'), 10)
                          : undefined;
    const maxInclusive = node.hasAttribute('maxInclusive')
                          ? parseInt(node.getAttribute('maxInclusive'), 10)
                          : undefined;
    const optionNodes = getElements(node, 'parameters', 'Option');
    let options;
    if (optionNodes.length) {
      options = optionNodes.map(optionNode => ({
        label: optionNode.getAttribute('label'),
        value: optionNode.getAttribute('value'),
      }));
    }
    return new OpenSearchParameter(
      type, name, mandatory, options, minExclusive, maxExclusive, minInclusive, maxInclusive
    );
  }

  /**
   * Constructs a new OpenSearchParameter from a key value pair (e.g: from the
   * query part of a KVP-URL). Returns null, when the value could not be parsed.
   * @param {DOMNode} key the key of the key-value-pair.
   * @param {DOMNode} value the value of the key-value-pair.
   * @returns {OpenSearchParameter|null} the constructed parameters object.
   */
  static fromKeyValuePair(key, value) {
    const type = parseType(value);
    if (type) {
      return new OpenSearchParameter(
        type, key, isMandatory(value)
      );
    }
    return null;
  }

  /**
   * Serialize the parameter to a simple object.
   * @returns {object} The serialized parameter
   */
  serialize() {
    const values = {
      type: this._type,
      name: this._name,
      mandatory: this._mandatory,
      options: this._options,
    };

    if (typeof this._minExclusive !== 'undefined') {
      values.minExclusive = this._minExclusive;
    }
    if (typeof this._maxExclusive !== 'undefined') {
      values.maxExclusive = this._maxExclusive;
    }
    if (typeof this._minInclusive !== 'undefined') {
      values.minInclusive = this._minInclusive;
    }
    if (typeof this._maxInclusive !== 'undefined') {
      values.maxInclusive = this._maxInclusive;
    }
    return values;
  }

  /**
   * Deserialize a parameter from a simple object.
   * @param {object} values The serialized parameter
   * @returns {OpenSearchParameter} The deserialized parameter
   */
  static deserialize(values) {
    return new OpenSearchParameter(
      values.type, values.name, values.mandatory, values.options,
      values.minExclusive, values.maxExclusive, values.minInclusive, values.maxInclusive
    );
  }
}