Source: DroneCommandArgument.js

const Enum = require('./util/Enum');

/**
 * Drone Command Argument class
 *
 * Used for storing command arguments
 *
 * @property {Enum|undefined} enum - Enum store containing possible enum values if `this.type === 'enum'`. If set then `this.hasEnumProperty === true`.
 * @todo allow boolean values for u8 and i8 params
 */
class DroneCommandArgument {
  /**
   * Command argument constructor
   * @param {object} raw - Raw command argument data from the xml specification
   */
  constructor(raw) {
    this._name = raw.$.name;
    this._description = String(raw._).trim();
    this._type = raw.$.type;
    this._value = this.type === 'string' ? '' : 0;

    // Parse enum if needed
    if (this.type === 'enum') {
      const enumData = {};
      let enumValue = 0;

      for (const option of raw.enum) {
        const enumName = option.$.name;

        enumData[enumName] = enumValue++;
      }

      this._enum = new Enum(enumData);

      Object.defineProperty(this, 'enum', {
        enumerable: false,
        get: () => this._enum,
      });
    }
  }

  /**
   * Parameter name
   * @returns {string} - name
   */
  get name() {
    return this._name;
  }

  /**
   * Parameter description
   * @returns {string} - description
   */
  get description() {
    return this._description;
  }

  /**
   * Parameter type
   * @returns {string} - type
   */
  get type() {
    return this._type;
  }

  /**
   * Get the parameter value
   * @returns {number|string} - value
   * @see DroneCommandArgument#type
   */
  get value() {
    if (this.type === 'string' && !this._value.endsWith('\0')) {
      return this._value + '\0';
    } else if (this.type === 'float') {
      return Math.fround(this._value);

      /**
       * Javascript uses doubles by default not fixed
       * precision or decimals. This means that we can
       * just return the value without rounding it.
       */
    }

    return this._value;
  }

  /**
   * Set the parameter value
   * @param {number|string} value - Parameter value
   * @throws TypeError
   */
  set value(value) {
    if (Object.is(value, -0)) {
      value = 0;
    }

    this._value = this._parseValue(value);
  }

  /**
   * If it has the enum property set
   * @returns {boolean} - has enum
   */
  get hasEnumProperty() {
    return typeof this.enum !== 'undefined';
  }

  /**
   * Parses the value before setting it
   * @param {number|string} value - Target value
   * @returns {number|string} - parsed value
   * @private
   * @throws TypeError
   */
  _parseValue(value) {
    switch (this.type) {
      case 'enum':
        if (this.enum.hasKey(value)) {
          return this.enum[value];
        } else if (this.enum.hasValue(value)) {
          return value;
          // } else if (value === 256) {
          //   // This is some BS value I sometimes get from the drone
          //   // Pretty much just means "unavailable"
          //   return value;
        }

        throw new TypeError(`Value ${value} could not be interpreted as an enum value for ${this.name}. Available options are ${this.enum.toString()}`);
      case 'string':
        return String(value);
      default:
        return Number(value);
    }
  }

  /**
   * Gets the byte size of the value.
   * @returns {number} - value size in bytes
   */
  getValueSize() {
    switch (this.type) {
      case 'string':
        return this.value.length;
      case 'u8':
      case 'i8':
        return 1;
      case 'u16':
      case 'i16':
        return 2;
      case 'u32':
      case 'i32':
        return 4;
      case 'u64':
      case 'i64':
        return 8;
      case 'float':
        return 4;
      case 'double':
        return 8;
      case 'enum':
        return 4;
      default:
        return 0;
    }
  }

  /**
   * Returns a string representation of the DroneCommandArgument instance
   * @param {boolean} debug - If extra debug info should be shown.
   * @param {number} precision - Amount of precision for numerical values
   * @returns {string} - string representation
   */
  toString(debug = false, precision = 3) {
    let value;

    switch (this.type) {
      case 'string':
        value = this.value;

        while (value.endsWith('\0')) {
          value = value.substring(0, value.length - 1);
        }

        value = `"${value}"`;
        break;
      case 'u8':
      case 'i8':
      case 'u16':
      case 'i16':
      case 'u32':
      case 'i32':
      case 'u64':
      case 'i64':
        value = this.value;
        break;
      case 'float':
      case 'double':
        // This will provide a reasonable estimate of the
        // floating point value for debugging purposes.
        value = this.value.toFixed(precision).replace(/0+$/, '');
        break;
      case 'enum':
        value = `"${this.enum.findForValue(this.value)}"[${this.value}]`;
        break;
      default:
        value = this.value;
    }

    if (!debug) {
      return `${this.name}=${value}`;
    }

    return `(${this.type})${this.name}=${value}`;
  }
}

module.exports = DroneCommandArgument;