// Copyright (c) 2023, Sudipto Chandra
// All rights reserved. Check LICENSE file for details.

import 'dart:typed_data';

import 'codec.dart';

abstract class BitDecoder extends HashlibConverter {
  /// Creates a new [BitDecoder] instance.
  const BitDecoder();

  /// Converts [encoded] array of numbers with bit-length of [source] to an array
  /// of numbers with bit-length of [target]. The [encoded] array will be treated
  /// as a sequence of bits to convert.
  ///
  /// If the [encoded] array contains negative numbers or numbers having more than
  /// the [source] bits, it will be treated as the end of the input sequence.
  ///
  /// After consuming all of input sequence, if there are some non-zero partial
  /// word remains, it will throw [FormatException].
  @override
  List<int> convert(covariant List<int> encoded) {
    int x, p, s, t, l, n, sb, tb;
    sb = source;
    tb = target;
    if (sb < 2 || sb > 64) {
      throw ArgumentError('The source bit length should be between 2 to 64');
    }
    if (tb < 2 || tb > 64) {
      throw ArgumentError('The target bit length should be between 2 to 64');
    }

    l = encoded.length * sb;
    var out = Uint8List(l ~/ tb);

    // generate words from the input bits
    p = n = t = l = 0;
    s = 1 << (sb - 1);
    s = s ^ (s - 1);
    for (x in encoded) {
      if (x < 0 || x > s) break;
      p = (p << sb) ^ x;
      t = (t << sb) ^ s;
      n += sb;
      while (n >= tb) {
        n -= tb;
        out[l++] = p >>> n;
        t >>>= tb;
        p &= t;
      }
    }

    // p > 0 means that there is a non-zero partial word remaining
    if (p > 0) {
      throw FormatException('Invalid length');
    }

    if (l < out.length) {
      return out.sublist(0, l);
    }
    return out;
  }
}
