Source: decorators.js

import Types from './Types'

/**
 * Class member decorator generator for Binary class property
 * @param {object} type - Any element from the {@link Types}
 * @return {function} - The {@link decorator} which generates the {@link propertyDescriptor} to be used to transform a class member into binary backed member
 * @decorator
 * @method
 */
const binary = ({ bytes, padding = false, get, set }) => {
  /**
   * The class member generated decorator
   * @param {object} target - object which will be `assign`ed to the Class.prototype (extending {@link Binary} or decorated with {@link withBinary})
   * @param {string} name - property name
   * @param {object} descriptor - as in `Object.defineProperty(target, name, descriptor)`, like {@link propertyDescriptor}
   * @return {object} - The new or modified {@link propertyDescriptor} for this class member
   * @method
   * @name decorator
   */
  return function decorator (target, name, descriptor) {
    // const deleted = (() => {
    //  const {value, initializer, writable} = descriptor;
    //  return {value, initializer, writable};
    // })();

    // Add size as static property to Class
    target.constructor._size = target.constructor._size ?? 0
    target.constructor._binaryProps = target.constructor._binaryProps ?? []

    // Create an initial padding offset if needed
    const size = target.constructor._size
    const paddingOffset = padding ? padding - (size % padding) : 0

    // Get this property offset
    const offset = target.constructor._size + paddingOffset

    // Add property size to Class size (at static Class property)
    target.constructor._size += bytes + paddingOffset
    target.constructor._binaryProps.push(name)

    /**
     * Property definition returned from {@link binary}'s returned {@link decorator}
     * @name propertyDescriptor
     */
    return {
      // TODO: Do something with initializer as default value
      configurable: false,
      enumerable: true,

      /**
       * Returns the value handled by this class member, defined at its {@link Types}
       * @return {any} - The value in memory, translated by the {@link Types} getter
       * @this Binary instance
       * @name propertyDescriptorGetter
       */
      get () {
        return get(this._dv, this._initialOffset + offset)
      },

      /**
       * Modifies the value handled by this class member, defined at its {@link Types}
       * @param {any} value - The new value to be assigned, which will be translated by the {@link Types} setter
       * @return {boolean} - Always true, as needed by JS setters
       * @this Binary instance
       * @name propertyDescriptorSetter
       */
      set (value) {
        set(this._dv, this._initialOffset + offset, value)
        return true
      }
    }

    return descriptor
  }
}

// Using `withBinary` as class decorator is 4x times faster in
// instantiation (if using @nonenumerable), at the cost of losing some
// JS class syntax benefits.
// If using withBinary, don't use `extend Binary` and don't use `new` either.
/**
 * Decorator to add Binary behavior to a class containing `@binary` members, but
 * without extending Binary class
 * @param {class} Class - A class not extending Binary containing `@binary` members
 * @return {@link wrapper} A {@link Class} instantiator
 * */
const withBinary = (Class) => {
  /**
   * Class wrapper (object constructor)
   * @param {ArrayBuffer/SharedArrayBuffer/DataView} binOrDV - The buffer where the data lives
   * @param {number} initialOffset - Buffer offset before this object data start
   * @param {array} args - Any arguments passed to the class constructor
   * @return {@link Class} instance
   * @callback wrapper
   * */
  const wrapper = (binOrDV, initialOffset = 0, ...args) => {
    const target = new Class(...args)
    target._dv = binOrDV instanceof DataView ? binOrDV : new DataView(binOrDV)
    target._initialOffset = initialOffset

    /**
     * Get a single byte (as unsigned integer) from a position
     * @param {number} offset - The position of the byte to get
     * @return {number} The unsigned numeric value at this byte
     * @this {@link Class} instance
     */
    target.getByteAt = (offset) =>
      Types.Uint8.get(this._dv, this._initialOffset + offset)

    // This is desirable, but slowers down instantiation time by 4x times
    // Note: `defineProperties` is +0,25 slower
    /*
    Object.defineProperty(target,
      '_dv', {
        enumerable: false,
        configurable: false,
        writable: false,
        value: new DataView(bin, initialOffset, Class._size),
      });
    Object.defineProperty(target,
      '_initialOffset', {
        enumerable: false,
        configurable: false,
        writable: false,
        value: initialOffset,
      });
    */
    return target
  }

  /**
   * Allow getting the class size from outside
   * @this wrapper
   * @static
   * @method
   * @name binarySize
   */
  Object.defineProperty(wrapper, 'binarySize', {
    get () {
      return Class._size
    },
    configurable: false,
    enumerable: false
  })

  /**
   * Allow getting the class binary props from outside
   * @this wrapper
   * @static
   * @method
   * @name binarySize
   */
  Object.defineProperty(wrapper, 'binaryProps', {
    get () {
      return Class._binaryProps
    },
    configurable: false,
    enumerable: false
  })

  /**
   * Array creator helper
   * @param {ArrayBuffer/SharedArrayBuffer/DataView} binOrDv - The buffer where the data lives
   * @param {number} length - The length of elements to create
   * @param {number} initialOffset - The initial offset in the buffer before the first element of the array
   * @param {array} list - The array where new objects will be added
   * @return {array} - The array {@link list} where the objects have been added
   * @this wrapper
   * @static
   * @method
   */
  wrapper.arrayFactory = function (
    binOrDV,
    length,
    initialOffset = 0,
    list = []
  ) {
    // Optimize: Generate a single DataView for all elements
    const dv =
      binOrDV instanceof DataView
        ? binOrDV
        : new DataView(binOrDV, initialOffset, length * Class._size)

    for (let i = 0; i < length; i++) {
      list.push(wrapper(dv, initialOffset + Class._size * i))
    }

    return list
  }

  return wrapper
}

export { withBinary, binary }