/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const ABSOLUTE = ["celsius", "kelvin", "fahrenheit"];
const ALIAS = ["c", "k", "f"];
const UNITS = [...ABSOLUTE, ...ALIAS];

const NUMBER_REGEX = "-?\\d+(?:\\.\\d+)?\\s*";
const UNIT_REGEX = "\\w+";

// NOTE: This regex need to be localized upon supporting multi locales
//       since it supports en-US input format only.
const QUERY_REGEX = new RegExp(
  `^(${NUMBER_REGEX})(${UNIT_REGEX})(?:\\s+in\\s+|\\s+to\\s+|\\s*=\\s*)(${UNIT_REGEX})`,
  "i"
);

const DECIMAL_PRECISION = 10;

/**
 * This module converts temperature unit.
 */
export class UnitConverterTemperature {
  /**
   * Convert the given search string.
   *
   * @param {string} searchString
   *   The string to be converted
   * @returns {string} conversion result.
   */
  convert(searchString) {
    const regexResult = QUERY_REGEX.exec(searchString);
    if (!regexResult) {
      return null;
    }

    const target = findUnits(regexResult[2], regexResult[3]);

    if (!target) {
      return null;
    }

    const { inputUnit, outputUnit } = target;
    const inputNumber = Number(regexResult[1]);
    const inputChar = inputUnit.charAt(0);
    const outputChar = outputUnit.charAt(0);

    let outputNumber;
    if (inputChar === outputChar) {
      outputNumber = inputNumber;
    } else {
      outputNumber = this[`${inputChar}2${outputChar}`](inputNumber);
    }

    outputNumber = parseFloat(outputNumber.toPrecision(DECIMAL_PRECISION));

    try {
      return new Intl.NumberFormat("en-US", {
        style: "unit",
        unit: outputUnit,
        maximumFractionDigits: DECIMAL_PRECISION,
      }).format(outputNumber);
    } catch (e) {}

    return `${outputNumber} ${outputUnit}`;
  }

  c2k(t) {
    return t + 273.15;
  }

  c2f(t) {
    return t * 1.8 + 32;
  }

  k2c(t) {
    return t - 273.15;
  }

  k2f(t) {
    return this.c2f(this.k2c(t));
  }

  f2c(t) {
    return (t - 32) / 1.8;
  }

  f2k(t) {
    return this.c2k(this.f2c(t));
  }
}

/**
 * Returns the suitable units for the given two values.
 * If could not found suitable unit, returns null.
 *
 * @param {string} inputUnit
 *    A set of units to convert, mapped to the `inputUnit` value on the return
 * @param {string} outputUnit
 *    A set of units to convert, mapped to the `outputUnit` value on the return
 * @returns {{ inputUnit: string, outputUnit: string }} The suitable units.
 */
function findUnits(inputUnit, outputUnit) {
  inputUnit = inputUnit.toLowerCase();
  outputUnit = outputUnit.toLowerCase();

  if (!UNITS.includes(inputUnit) || !UNITS.includes(outputUnit)) {
    return null;
  }

  return {
    inputUnit: toAbsoluteUnit(inputUnit),
    outputUnit: toAbsoluteUnit(outputUnit),
  };
}

function toAbsoluteUnit(unit) {
  if (unit.length !== 1) {
    return unit;
  }

  return ABSOLUTE.find(a => a.startsWith(unit));
}
