const { end_of_stream } = require('../utils');
const { label_to_encoding } = require('../table');

class Stream {
  /**
   * A stream represents an ordered sequence of tokens.
   * @param {!(Array.<number>|Uint8Array)} tokens Array of tokens that provide
   * the stream.
   */
  constructor(tokens) {
    this.tokens = [...tokens]
    // Reversed as push/pop is more efficient than shift/unshift.
    this.tokens.reverse()
  }
  /**
   * @returns True if end-of-stream has been hit.
   */
  endOfStream() {
    return !this.tokens.length
  }
  /**
   * When a token is read from a stream, the first token in the
   * stream must be returned and subsequently removed, and
   * end-of-stream must be returned otherwise.
   *
   * @return Get the next token from the stream, or end_of_stream.
   */
  read() {
    if (!this.tokens.length)
      return end_of_stream
    return this.tokens.pop()
  }
  /**
   * When one or more tokens are prepended to a stream, those tokens
   * must be inserted, in given order, before the first token in the
   * stream.
   *
   * @param {(number|!Array.<number>)} token The token(s) to prepend to the
   * stream.
   */
  prepend(token) {
    if (Array.isArray(token)) {
      var tokens = /**@type {!Array.<number>}*/(token)
      while (tokens.length)
        this.tokens.push(tokens.pop())
    } else {
      this.tokens.push(token)
    }
  }
  /**
   * When one or more tokens are pushed to a stream, those tokens
   * must be inserted, in given order, after the last token in the
   * stream.
   *
   * @param {(number|!Array.<number>)} token The tokens(s) to push to the
   * stream.
   */
  push(token) {
    if (Array.isArray(token)) {
      const tokens = /**@type {!Array.<number>}*/(token)
      while (tokens.length)
        this.tokens.unshift(tokens.shift())
    } else {
      this.tokens.unshift(token)
    }
  }
}

const DEFAULT_ENCODING = 'utf-8'


/**
 * Returns the encoding for the label.
 * @param {string} label The encoding label.
 */
function getEncoding(label) {
  // 1. Remove any leading and trailing ASCII whitespace from label.
  label = String(label).trim().toLowerCase()

  // 2. If label is an ASCII case-insensitive match for any of the
  // labels listed in the table below, return the corresponding
  // encoding, and failure otherwise.
  if (Object.prototype.hasOwnProperty.call(label_to_encoding, label)) {
    return label_to_encoding[label]
  }
  return null
}


//
// 5. Encodings
//

// 5.1 Encoders and decoders

// /** @interface */
// function Decoder() {}
// Decoder.prototype = {
//   /**
//    * @param {Stream} stream The stream of bytes being decoded.
//    * @param {number} bite The next byte read from the stream.
//    * @return {?(number|!Array.<number>)} The next code point(s)
//    *     decoded, or null if not enough data exists in the input
//    *     stream to decode a complete code point, or |finished|.
//    */
//   handler: function(stream, bite) {},
// }

// /** @interface */
// function Encoder() {}
// Encoder.prototype = {
//   /**
//    * @param {Stream} stream The stream of code points being encoded.
//    * @param {number} code_point Next code point read from the stream.
//    * @return {(number|!Array.<number>)} Byte(s) to emit, or |finished|.
//    */
//   handler: function(stream, code_point) {},
// }

module.exports = Stream
module.exports.DEFAULT_ENCODING = DEFAULT_ENCODING
module.exports.getEncoding = getEncoding