/**
 * Relay
 * @module j5e/relay
 * @requires module:j5e/fn
 */

import { normalizeDevice, normalizeIO, getProvider } from "j5e/fn";

/**
 * Class representing a relay
 * @classdesc The Relay class allows for control of a relay
 * @async
 */
class Relay {

  #state = {
    isInverted: false,
    isClosed: false,
    value: null
  }

  /**
   * Instantiate Relay
   * @param {number|string|object} io - Pin identifier or IO Options (See {@tutorial C-INSTANTIATING})
   * @param {object} options - Device configuration options
   * @param {string} options.type - "NO" (Normally Open) or "NC" (Normally Closed)
   * @example
   * <caption>Use a Relay</caption>
   * import Relay from "j5e/relay";
   *
   * const relay = await new Relay(12);
   *
   */
  constructor(io) {
    return (async() => {
      io = normalizeIO(io);

      const Provider = await getProvider(io, "Digital");
      this.io = new Provider({
        pin: io.pin,
        mode: Provider.Output
      });

      this.configure();
      return this;
    })();

  }

  /**
   * Configure a Relay
   * @returns {Relay} The instance on which the method was called
   * @param {object} options - Device configuration options
   * @param {number} [options.type="NO"] - "NC" if a relay is normally closed, "NO" if it is normally open
   * @example
   * import Relay from "j5e/relay";
   *
   * const relay = await new Relay(14);
   * relay.configure({
   *   type: "NC"
   * });
   *
   * // With type: "NC", relay.open() sets pin 14 high
   * relay.open();
   */
  configure(options = {}) {
    options = normalizeDevice(options);

    if (typeof options.type !== "undefined") {
      this.#state.isInverted = options.type === "NC" ? true : false;
    }

    return this;
  }

  /**
   * A numerical value representing the relay state
   * @type {number}
   * @readonly
   */
  get value() {
    return Number(this.isClosed);
  }

  /**
   * True if the relay is closed
   * @type {boolean}
   * @readonly
   */
  get isClosed() {
    return this.#state.isClosed;
  }

  /**
   * "NC" if the relay is normally closed, "NO" if the relay is normally open
   * @type {string}
   * @readonly
   */
  get type() {
    return this.#state.isInverted ? "NC" : "NO";
  }

  /**
   * Close the relay circuit
   * @return {Relay}
   * @example
   * import Relay from "j5e/relay"
   *
   * const relay = await new Relay(12);
   *
   * // Turn it on
   * relay.close();
   *
   * // Wait 5 seeconds and turn it off
   * system.setTimeout(function() {
   *   relay.open();
   * }, 5000);
   */
  close() {
    this.io.write(
      this.#state.isInverted ? 0 : 2 ** (this.io.resolution || 1) - 1
    );
    this.#state.isClosed = true;

    return this;
  }

  /**
   * Open the relay circuit
   * @return {Relay}
   * @example
   * import Relay from "j5e/relay"
   *
   * const relay = await new Relay(12);
   *
   * // Turn it on
   * relay.close();
   *
   * // Wait 5 seeconds and turn it off
   * system.setTimeout(function() {
   *   relay.open();
   * }, 5000);
   */
  open() {

    this.io.write(
      this.#state.isInverted ? 2 ** (this.io.resolution || 1) - 1 : 0
    );
    this.#state.isClosed = false;

    return this;
  }

  /**
   * Toggle the relay circuit
   * @return {Relay}
   * @example
   * import Relay from "j5e/relay"
   *
   * const relay = await new Relay(12);
   *
   * // Turn it on
   * relay.toggle();
   *
   * // Wait 5 seeconds and turn it off
   * system.setTimeout(function() {
   *   relay.toggle();
   * }, 5000);
   */
  toggle() {

    if (this.#state.isClosed) {
      this.open();
    } else {
      this.close();
    }

    return this;
  }

}

export default Relay;