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

import 'dart:typed_data';

import 'package:hashlib/src/core/block_hash.dart';

// Initialize array of round 64-bit constants
const List<int> _k = [
  0x428A2F98, 0xD728AE22, 0x71374491, 0x23EF65CD, 0xB5C0FBCF, 0xEC4D3B2F, //
  0xE9B5DBA5, 0x8189DBBC, 0x3956C25B, 0xF348B538, 0x59F111F1, 0xB605D019,
  0x923F82A4, 0xAF194F9B, 0xAB1C5ED5, 0xDA6D8118, 0xD807AA98, 0xA3030242,
  0x12835B01, 0x45706FBE, 0x243185BE, 0x4EE4B28C, 0x550C7DC3, 0xD5FFB4E2,
  0x72BE5D74, 0xF27B896F, 0x80DEB1FE, 0x3B1696B1, 0x9BDC06A7, 0x25C71235,
  0xC19BF174, 0xCF692694, 0xE49B69C1, 0x9EF14AD2, 0xEFBE4786, 0x384F25E3,
  0x0FC19DC6, 0x8B8CD5B5, 0x240CA1CC, 0x77AC9C65, 0x2DE92C6F, 0x592B0275,
  0x4A7484AA, 0x6EA6E483, 0x5CB0A9DC, 0xBD41FBD4, 0x76F988DA, 0x831153B5,
  0x983E5152, 0xEE66DFAB, 0xA831C66D, 0x2DB43210, 0xB00327C8, 0x98FB213F,
  0xBF597FC7, 0xBEEF0EE4, 0xC6E00BF3, 0x3DA88FC2, 0xD5A79147, 0x930AA725,
  0x06CA6351, 0xE003826F, 0x14292967, 0x0A0E6E70, 0x27B70A85, 0x46D22FFC,
  0x2E1B2138, 0x5C26C926, 0x4D2C6DFC, 0x5AC42AED, 0x53380D13, 0x9D95B3DF,
  0x650A7354, 0x8BAF63DE, 0x766A0ABB, 0x3C77B2A8, 0x81C2C92E, 0x47EDAEE6,
  0x92722C85, 0x1482353B, 0xA2BFE8A1, 0x4CF10364, 0xA81A664B, 0xBC423001,
  0xC24B8B70, 0xD0F89791, 0xC76C51A3, 0x0654BE30, 0xD192E819, 0xD6EF5218,
  0xD6990624, 0x5565A910, 0xF40E3585, 0x5771202A, 0x106AA070, 0x32BBD1B8,
  0x19A4C116, 0xB8D2D0C8, 0x1E376C08, 0x5141AB53, 0x2748774C, 0xDF8EEB99,
  0x34B0BCB5, 0xE19B48A8, 0x391C0CB3, 0xC5C95A63, 0x4ED8AA4A, 0xE3418ACB,
  0x5B9CCA4F, 0x7763E373, 0x682E6FF3, 0xD6B2B8A3, 0x748F82EE, 0x5DEFB2FC,
  0x78A5636F, 0x43172F60, 0x84C87814, 0xA1F0AB72, 0x8CC70208, 0x1A6439EC,
  0x90BEFFFA, 0x23631E28, 0xA4506CEB, 0xDE82BDE9, 0xBEF9A3F7, 0xB2C67915,
  0xC67178F2, 0xE372532B, 0xCA273ECE, 0xEA26619C, 0xD186B8C7, 0x21C0C207,
  0xEADA7DD6, 0xCDE0EB1E, 0xF57D4F7F, 0xEE6ED178, 0x06F067AA, 0x72176FBA,
  0x0A637DC5, 0xA2C898A6, 0x113F9804, 0xBEF90DAE, 0x1B710B35, 0x131C471B,
  0x28DB77F5, 0x23047D84, 0x32CAAB7B, 0x40C72493, 0x3C9EBE0A, 0x15C9BEBC,
  0x431D67C4, 0x9C100D4C, 0x4CC5D4BE, 0xCB3E42B6, 0x597F299C, 0xFC657E2A,
  0x5FCB6FAB, 0x3AD6FAEC, 0x6C44198C, 0x4A475817,
];

const int _sig1 = 0;
const int _sig2 = _sig1 + 2;
const int _sig3 = _sig2 + 2;
const int _sig4 = _sig3 + 2;
const int _a = _sig4 + 2;
const int _b = _a + 2;
const int _c = _b + 2;
const int _d = _c + 2;
const int _e = _d + 2;
const int _f = _e + 2;
const int _g = _f + 2;
const int _h = _g + 2;
const int _t1 = _h + 2;
const int _t2 = _t1 + 2;
const int _t3 = _t2 + 2;
const int _t4 = _t3 + 2;
const int _t5 = _t4 + 2;

/// Implementation is derived from [RFC6234][rfc6234] which follows the
/// [FIPS 180-4][fips180] standard for SHA and SHA-based HMAC and HKDF.
///
/// It uses 32-bit integers to accommodate 64-bit integer operations, designed
/// specially to be supported by Web VM. It is albeit slower than the native
/// implementation.
///
/// [rfc6234]: https://www.ietf.org/rfc/rfc6234.html
/// [fips180]: https://csrc.nist.gov/publications/detail/fips/180/4/final
class SHA2of1024 extends BlockHashSink {
  final List<int> seed;
  final Uint32List state;
  final Uint32List chunk;
  final _var = Uint32List(_t5 + 2);

  @override
  final int hashLength;

  /// For internal use only.
  SHA2of1024({
    required this.seed,
    required this.hashLength,
  })  : chunk = Uint32List(160),
        state = Uint32List.fromList(seed),
        super(1024 >>> 3);

  @override
  void reset() {
    state.setAll(0, seed);
    super.reset();
  }

  /// z = x ^ y
  static void _xor(List<int> x, int i, List<int> y, int j, List<int> z, int k) {
    z[k] = x[i] ^ y[j];
    z[k + 1] = x[i + 1] ^ y[j + 1];
  }

  /// z = x + y
  static void _add(List<int> x, int i, List<int> y, int j, List<int> z, int k) {
    z[k + 1] = x[i + 1] + y[j + 1];
    z[k] = x[i] + y[j] + (z[k + 1] < x[i + 1] ? 1 : 0);
  }

  /// x += z
  static void _addAndSet(List<int> x, int i, List<int> z, int j) {
    var t = x[i + 1];
    x[i + 1] += z[j + 1];
    x[i] += z[j] + (x[i + 1] < t ? 1 : 0);
  }

  // x >>> n
  static void _shr(int n, List<int> x, int i, List<int> z, int k) {
    var a = x[i];
    var b = x[i + 1];
    if (n == 32) {
      z[k] = 0;
      z[k + 1] = a;
    } else if (n < 32) {
      z[k] = a >>> n;
      z[k + 1] = (a << (32 - n)) | (b >>> n);
    } else {
      z[k] = 0;
      z[k + 1] = a >>> (n - 32);
    }
  }

  // (x << (64 - n)) | (x >>> n)
  static void _rotr(int n, List<int> x, int i, List<int> z, int k) {
    var a = x[i];
    var b = x[i + 1];
    if (n == 32) {
      z[k] = b;
      z[k + 1] = a;
    } else if (n < 32) {
      z[k] = (b << (32 - n)) | (a >>> n);
      z[k + 1] = (a << (32 - n)) | (b >>> n);
    } else {
      z[k] = (a << (64 - n)) | (b >>> (n - 32));
      z[k + 1] = (b << (64 - n)) | (a >>> (n - 32));
    }
  }

  // z = _rotr(x, 28) ^ _rotr(x, 34) ^ _rotr(x, 39)
  void _bsig0(List<int> x, int i, List<int> z, int j) {
    _rotr(28, x, i, _var, _sig1);
    _rotr(34, x, i, _var, _sig2);
    _rotr(39, x, i, _var, _sig3);
    _xor(_var, _sig2, _var, _sig3, _var, _sig4);
    _xor(_var, _sig1, _var, _sig4, z, j);
  }

  // z = _rotr(x, 14) ^ _rotr(x, 18) ^ _rotr(x, 41)
  void _bsig1(List<int> x, int i, List<int> z, int j) {
    _rotr(14, x, i, _var, _sig1);
    _rotr(18, x, i, _var, _sig2);
    _rotr(41, x, i, _var, _sig3);
    _xor(_var, _sig2, _var, _sig3, _var, _sig4);
    _xor(_var, _sig1, _var, _sig4, z, j);
  }

  // z = _rotr(x, 1) ^ _rotr(x, 8) ^ (x >>> 7)
  void _ssig0(List<int> x, int i, List<int> z, int j) {
    _rotr(1, x, i, _var, _sig1);
    _rotr(8, x, i, _var, _sig2);
    _shr(7, x, i, _var, _sig3);
    _xor(_var, _sig2, _var, _sig3, _var, _sig4);
    _xor(_var, _sig1, _var, _sig4, z, j);
  }

  // z = _rotr(x, 19) ^ _rotr(x, 61) ^ (x >>> 6)
  void _ssig1(List<int> x, int i, List<int> z, int j) {
    _rotr(19, x, i, _var, _sig1);
    _rotr(61, x, i, _var, _sig2);
    _shr(6, x, i, _var, _sig3);
    _xor(_var, _sig2, _var, _sig3, _var, _sig4);
    _xor(_var, _sig1, _var, _sig4, z, j);
  }

  // z = (e & f) ^ ((~e) & g)
  static void _ch(List<int> e, int i, List<int> f, int j, List<int> g, int k,
      List<int> z, int l) {
    z[l] = (e[i] & (f[j] ^ g[k])) ^ g[k];
    z[l + 1] = (e[i + 1] & (f[j + 1] ^ g[k + 1])) ^ g[k + 1];
  }

  // z = (a & b) ^ (a & c) ^ (b & c)
  static void _maj(List<int> a, int i, List<int> b, int j, List<int> c, int k,
      List<int> z, int l) {
    z[l] = (a[i] & (b[j] | c[k])) | (b[j] & c[k]);
    z[l + 1] = (a[i + 1] & (b[j + 1] | c[k + 1])) | (b[j + 1] & c[k + 1]);
  }

  @override
  void $update(List<int> block, [int offset = 0, bool last = false]) {
    var w = chunk;

    _var.setAll(_a, state);

    // Convert the block to chunk
    for (int i = 0, j = offset; i < 32; i++, j += 4) {
      w[i] = ((block[j] & 0xFF) << 24) |
          ((block[j + 1] & 0xFF) << 16) |
          ((block[j + 2] & 0xFF) << 8) |
          ((block[j + 3] & 0xFF));
    }

    // Extend the first 32 words into nest 160 words
    for (var i = 32; i < 160; i += 2) {
      // w[i] = _ssig1(w[i - 2]) + w[i - 7] + _ssig0(w[i - 15]) + w[i - 16];
      _ssig1(w, i - 4, _var, _t1);
      _add(_var, _t1, w, i - 14, _var, _t2);
      _ssig0(w, i - 30, _var, _t1);
      _add(_var, _t1, w, i - 32, _var, _t3);
      _add(_var, _t2, _var, _t3, w, i);
    }

    for (int i = 0; i < 160; i += 2) {
      // t1 = h + _bsig1(e) + _ch(e, f, g) + k[i] + w[i];
      _bsig1(_var, _e, _var, _t1);
      _add(_var, _h, _var, _t1, _var, _t2);
      _ch(_var, _e, _var, _f, _var, _g, _var, _t3);
      _add(_var, _t2, _var, _t3, _var, _t4);
      _add(_k, i, w, i, _var, _t5);
      _add(_var, _t4, _var, _t5, _var, _t1);

      // t2 = _bsig0(A) + _maj(a, b, c);
      _bsig0(_var, _a, _var, _t3);
      _maj(_var, _a, _var, _b, _var, _c, _var, _t4);
      _add(_var, _t3, _var, _t4, _var, _t2);

      // h = g;
      _var[_h] = _var[_g];
      _var[_h + 1] = _var[_g + 1];
      // g = f;
      _var[_g] = _var[_f];
      _var[_g + 1] = _var[_f + 1];
      // f = e;
      _var[_f] = _var[_e];
      _var[_f + 1] = _var[_e + 1];
      // e = d + t1;
      _add(_var, _d, _var, _t1, _var, _e);
      // d = c;
      _var[_d] = _var[_c];
      _var[_d + 1] = _var[_c + 1];
      // c = b;
      _var[_c] = _var[_b];
      _var[_c + 1] = _var[_b + 1];
      // b = a;
      _var[_b] = _var[_a];
      _var[_b + 1] = _var[_a + 1];
      // a = t1 + t2;
      _add(_var, _t1, _var, _t2, _var, _a);
    }

    _addAndSet(state, 0, _var, _a);
    _addAndSet(state, 2, _var, _b);
    _addAndSet(state, 4, _var, _c);
    _addAndSet(state, 6, _var, _d);
    _addAndSet(state, 8, _var, _e);
    _addAndSet(state, 10, _var, _f);
    _addAndSet(state, 12, _var, _g);
    _addAndSet(state, 14, _var, _h);
  }

  @override
  Uint8List $finalize() {
    // Adding the signature byte
    buffer[pos++] = 0x80;

    // If no more space left in buffer for the message length
    if (pos > 112) {
      for (; pos < 128; pos++) {
        buffer[pos] = 0;
      }
      $update(buffer);
      pos = 0;
    }

    // Fill remaining buffer to put the message length at the end
    for (; pos < 120; pos++) {
      buffer[pos] = 0;
    }

    // Append original message length in bits to message
    int n = messageLengthInBits;
    buffer[120] = n >>> 56;
    buffer[121] = n >>> 48;
    buffer[122] = n >>> 40;
    buffer[123] = n >>> 32;
    buffer[124] = n >>> 24;
    buffer[125] = n >>> 16;
    buffer[126] = n >>> 8;
    buffer[127] = n;

    // Update with the final block
    $update(buffer);

    // Convert the state to 8-bit byte array
    var bytes = Uint8List(hashLength);
    for (int j = 0, i = 0; j < hashLength; i++, j += 4) {
      bytes[j] = state[i] >>> 24;
      bytes[j + 1] = state[i] >>> 16;
      bytes[j + 2] = state[i] >>> 8;
      bytes[j + 3] = state[i];
    }
    return bytes;
  }
}
