# src/Util/Binary/Encoder.js

    import { RootFakeUserID } from '../ID/RootID.js'

    const bits7 = 0b1111111
    const bits8 = 0b11111111

    /**
     * A BinaryEncoder handles the encoding to an ArrayBuffer.
     */
    export default class BinaryEncoder {
      constructor () {
        // TODO: implement chained Uint8Array buffers instead of Array buffer
        // TODO: Rewrite all methods as functions!
        this.data = []
      }

      /**
       * The current length of the encoded data.
       */
      get length () {
        return this.data.length
      }

      /**
       * The current write pointer (the same as {@link length}).
       */
      get pos () {
        return this.data.length
      }

      /**
       * Create an ArrayBuffer.
       *
       * @return {Uint8Array} A Uint8Array that represents the written data.
       */
      createBuffer () {
        return Uint8Array.from(this.data).buffer
      }

      /**
       * Write one byte as an unsigned integer.
       *
       * @param {number} num The number that is to be encoded.
       */
      writeUint8 (num) {
        this.data.push(num & bits8)
      }

      /**
       * Write one byte as an unsigned Integer at a specific location.
       *
       * @param {number} pos The location where the data will be written.
       * @param {number} num The number that is to be encoded.
       */
      setUint8 (pos, num) {
        this.data[pos] = num & bits8
      }

      /**
       * Write two bytes as an unsigned integer.
       *
       * @param {number} num The number that is to be encoded.
       */
      writeUint16 (num) {
        this.data.push(num & bits8, (num >>> 8) & bits8)
      }
      /**
       * Write two bytes as an unsigned integer at a specific location.
       *
       * @param {number} pos The location where the data will be written.
       * @param {number} num The number that is to be encoded.
       */
      setUint16 (pos, num) {
        this.data[pos] = num & bits8
        this.data[pos + 1] = (num >>> 8) & bits8
      }

      /**
       * Write two bytes as an unsigned integer
       *
       * @param {number} num The number that is to be encoded.
       */
      writeUint32 (num) {
        for (let i = 0; i < 4; i++) {
          this.data.push(num & bits8)
          num >>>= 8
        }
      }

      /**
       * Write two bytes as an unsigned integer at a specific location.
       *
       * @param {number} pos The location where the data will be written.
       * @param {number} num The number that is to be encoded.
       */
      setUint32 (pos, num) {
        for (let i = 0; i < 4; i++) {
          this.data[pos + i] = num & bits8
          num >>>= 8
        }
      }

      /**
       * Write a variable length unsigned integer.
       *
       * @param {number} num The number that is to be encoded.
       */
      writeVarUint (num) {
        while (num >= 0b10000000) {
          this.data.push(0b10000000 | (bits7 & num))
          num >>>= 7
        }
        this.data.push(bits7 & num)
      }

      /**
       * Write a variable length string.
       *
       * @param {String} str The string that is to be encoded.
       */
      writeVarString (str) {
        let encodedString = unescape(encodeURIComponent(str))
        let bytes = encodedString.split('').map(c => c.codePointAt())
        let len = bytes.length
        this.writeVarUint(len)
        for (let i = 0; i < len; i++) {
          this.data.push(bytes[i])
        }
      }

      /**
       * Write an ID at the current position.
       *
       * @param {ID} id The ID that is to be written.
       */
      writeID (id) {
        const user = id.user
        this.writeVarUint(user)
        if (user !== RootFakeUserID) {
          this.writeVarUint(id.clock)
        } else {
          this.writeVarString(id.name)
          this.writeVarUint(id.type)
        }
      }
    }