// See file LICENSE for more information.

import 'dart:typed_data';

import 'package:pointycastle/src/platform_check/platform_check.dart';

void arrayCopy(Uint8List? sourceArr, int sourcePos, Uint8List? outArr,
    int outPos, int len) {
  for (var i = 0; i < len; i++) {
    outArr![outPos + i] = sourceArr![sourcePos + i];
  }
}

///
///A constant time equals comparison - does not terminate early if
///test will fail. For best results always pass the expected value
///as the first parameter.
///
/// @param expected first array
/// @param supplied second array
/// @return true if arrays equal, false otherwise.
///
bool constantTimeAreEqual(Uint8List expected, Uint8List supplied) {
  if (expected == supplied) {
    return true;
  }

  var len =
      (expected.length < supplied.length) ? expected.length : supplied.length;

  var nonEqual = expected.length ^ supplied.length;

  for (var i = 0; i != len; i++) {
    nonEqual |= expected[i] ^ supplied[i];
  }
  for (var i = len; i < supplied.length; i++) {
    nonEqual |= supplied[i] ^ ~supplied[i];
  }

  return nonEqual == 0;
}

Uint8List concatUint8List(Iterable<Uint8List> list) =>
    Uint8List.fromList(list.expand((element) => element).toList());

/// Decode a BigInt from bytes in big-endian encoding.
/// Twos compliment.
BigInt decodeBigInt(List<int> bytes) {
  var negative = bytes.isNotEmpty && bytes[0] & 0x80 == 0x80;

  BigInt result;

  if (bytes.length == 1) {
    result = BigInt.from(bytes[0]);
  } else {
    result = BigInt.zero;
    for (var i = 0; i < bytes.length; i++) {
      var item = bytes[bytes.length - i - 1];
      result |= BigInt.from(item) << (8 * i);
    }
  }
  return result != BigInt.zero
      ? negative
          ? result.toSigned(result.bitLength)
          : result
      : BigInt.zero;
}

/// Decode a big integer with arbitrary sign.
/// When:
/// sign == 0: Zero regardless of magnitude
/// sign < 0: Negative
/// sign > 0: Positive
BigInt decodeBigIntWithSign(int sign, List<int> magnitude) {
  if (sign == 0) {
    return BigInt.zero;
  }

  BigInt result;

  if (magnitude.length == 1) {
    result = BigInt.from(magnitude[0]);
  } else {
    result = BigInt.from(0);
    for (var i = 0; i < magnitude.length; i++) {
      var item = magnitude[magnitude.length - i - 1];
      result |= BigInt.from(item) << (8 * i);
    }
  }

  if (result != BigInt.zero) {
    if (sign < 0) {
      result = result.toSigned(result.bitLength);
    } else {
      result = result.toUnsigned(result.bitLength);
    }
  }
  return result;
}

var _byteMask = BigInt.from(0xff);
final negativeFlag = BigInt.from(0x80);

/// Encode a BigInt into bytes using big-endian encoding.
/// It encodes the integer to a minimal twos-compliment integer as defined by
/// ASN.1
Uint8List encodeBigInt(BigInt? number) {
  if (number == BigInt.zero) {
    return Uint8List.fromList([0]);
  }

  int needsPaddingByte;
  int rawSize;

  if (number! > BigInt.zero) {
    rawSize = (number.bitLength + 7) >> 3;
    needsPaddingByte =
        ((number >> (rawSize - 1) * 8) & negativeFlag) == negativeFlag ? 1 : 0;
  } else {
    needsPaddingByte = 0;
    rawSize = (number.bitLength + 8) >> 3;
  }

  final size = rawSize + needsPaddingByte;
  var result = Uint8List(size);
  for (var i = 0; i < rawSize; i++) {
    result[size - i - 1] = (number! & _byteMask).toInt();
    number = number >> 8;
  }
  return result;
}

/// Encode as Big Endian unsigned byte array.
Uint8List encodeBigIntAsUnsigned(BigInt number) {
  if (number == BigInt.zero) {
    return Uint8List.fromList([0]);
  }
  var size = number.bitLength + (number.isNegative ? 8 : 7) >> 3;
  var result = Uint8List(size);
  for (var i = 0; i < size; i++) {
    result[size - i - 1] = (number & _byteMask).toInt();
    number = number >> 8;
  }
  return result;
}

bool constantTimeAreEqualOffset(
    int len, Uint8List a, int aOff, Uint8List b, int bOff) {
  if (len < 0) {
    throw ArgumentError('"len" cannot be negative');
  }
  if (aOff > (a.length - len)) {
    throw ArgumentError('"aOff" value invalid for specified length');
  }
  if (bOff > (b.length - len)) {
    throw ArgumentError('"bOff" value invalid for specified length');
  }

  var d = 0;
  for (var i = 0; i < len; ++i) {
    d |= a[aOff + i] ^ b[bOff + i];
  }
  return 0 == d;
}

abstract class Pack {
  static int littleEndianToLong(Uint8List bs, int off) {
    var data = ByteData.view(bs.buffer);
    return data.getInt64(off, Endian.little);
  }

  static void littleEndianToLongAtList(Uint8List bs, int off, Uint64List ns) {
    for (var i = 0; i < ns.length; ++i) {
      ns[i] = littleEndianToLong(bs, off);
      off += 8;
    }
  }

  static void littleEndianToInt32AtList(Uint8List bs, int off, Uint32List ns) {
    for (var i = 0; i < ns.length; ++i) {
      ns[i] = littleEndianToInt(bs, off);
      off += 4;
    }
  }

  static void intToLittleEndian(int n, Uint8List bs, int off) {
    var data = ByteData.view(bs.buffer);
    data.setInt32(off, n, Endian.little);
  }

  static Uint8List longToLittleEndianList(int n) {
    var bs = Uint8List(8);
    longToLittleEndianAtList(n, bs, 0);
    return bs;
  }

  static void longToLittleEndianAtList(int n, Uint8List bs, int off) {
    var data = ByteData.view(bs.buffer);
    data.setInt64(off, n, Endian.little);
  }

  static Uint8List longListToLittleEndianList(Uint64List ns) {
    var bs = Uint8List(8 * ns.length);
    longListToLittleEndianAtList(ns, bs, 0);
    return bs;
  }

  static void longListToLittleEndianAtList(
      Uint64List ns, Uint8List bs, int off) {
    for (var i = 0; i < ns.length; ++i) {
      longToLittleEndianAtList(ns[i], bs, off);
      off += 8;
    }
  }

  static int littleEndianToInt(Uint8List bs, int off) {
    var data = ByteData.view(bs.buffer);
    return data.getInt32(off, Endian.little);
  }

  static Uint8List intToLittleEndianList(int n) {
    var bs = Uint8List(4);
    intToLittleEndianAtList(n, bs, 0);
    return bs;
  }

  static void intToLittleEndianAtList(int n, Uint8List bs, int off) {
    var data = ByteData.view(bs.buffer);
    data.setInt32(off, n, Endian.little);
  }

  static Uint8List intListToLittleEndian(Uint32List ns) {
    var bs = Uint8List(4 * ns.length);
    intListToLittleEndianAtList(ns, bs, 0);
    return bs;
  }

  static void intListToLittleEndianAtList(
      Uint32List ns, Uint8List bs, int off) {
    for (var i = 0; i < ns.length; ++i) {
      intToLittleEndianAtList(ns[i], bs, off);
      off += 4;
    }
  }
}

abstract class Longs {
  static const _MASK_32 = 0xFFFFFFFF;

  static const _MASK32_HI_BITS = <int>[
    0xFFFFFFFF,
    0x7FFFFFFF,
    0x3FFFFFFF,
    0x1FFFFFFF,
    0x0FFFFFFF,
    0x07FFFFFF,
    0x03FFFFFF,
    0x01FFFFFF,
    0x00FFFFFF,
    0x007FFFFF,
    0x003FFFFF,
    0x001FFFFF,
    0x000FFFFF,
    0x0007FFFF,
    0x0003FFFF,
    0x0001FFFF,
    0x0000FFFF,
    0x00007FFF,
    0x00003FFF,
    0x00001FFF,
    0x00000FFF,
    0x000007FF,
    0x000003FF,
    0x000001FF,
    0x000000FF,
    0x0000007F,
    0x0000003F,
    0x0000001F,
    0x0000000F,
    0x00000007,
    0x00000003,
    0x00000001,
    0x00000000
  ];

  static int rotateRight(int n, int distance) {
    if (distance == 0) {
      // do nothing:
      return n;
    }

    var hi32 = (n >> 32) & 0xFFFFFFFF;
    var lo32 = n & 0xFFFFFFFF;

    if (distance >= 32) {
      var swap = hi32;
      hi32 = lo32;
      lo32 = swap;
      distance -= 32;

      if (distance == 0) {
        return (hi32 << 32) | lo32;
      }
    }

    final distance32 = 32 - distance;
    final m = _MASK32_HI_BITS[distance32];

    final hi32cp = hi32;

    hi32 = hi32 >> distance;
    hi32 |= ((lo32 & m) << distance32) & _MASK_32;

    lo32 = lo32 >> distance;
    lo32 |= ((hi32cp & m) << distance32) & _MASK_32;

    return (hi32 << 32) | lo32;
  }

  static int toInt32(int n) => n & 0xFFFFFFFF;
}

const mask64 = (0xFFFFFFFF << 32) + 0xFFFFFFFF;

int unsignedShiftRight64(int n, int count) {
  if (Platform.instance.isNative) {
    return (n >> count) & ~(-1 << (64 - count));
  } else {
    count &= 0x1f;
    if (n >= 0) {
      return n >> count;
    } else {
      return (n >> count) ^ (mask64 ^ ((1 << (64 - count)) - 1));
    }
  }
}
