library impl.stream_cipher.rc4_engine;

import 'dart:typed_data';

import 'package:pointycastle/api.dart';
import 'package:pointycastle/src/impl/base_stream_cipher.dart';
import 'package:pointycastle/src/registry/registry.dart';

class RC4Engine extends BaseStreamCipher {
  static final FactoryConfig factoryConfig =
      StaticFactoryConfig(StreamCipher, 'RC4', () => RC4Engine());

  static int STATE_LENGTH = 256;

  Uint8List? _engineState;
  int _x = 0;
  int _y = 0;
  late Uint8List _workingKey;

  @override
  String get algorithmName => 'RC4';

  @override
  void init(bool forEncryption, CipherParameters? params) {
    if (params != null) {
      if (params is KeyParameter) {
        _workingKey = params.key;
        setKey(params.key);
      } else {
        throw ArgumentError('Parameters of invalid type');
      }
    } else {
      throw ArgumentError('Missing parameter');
    }
  }

  @override
  Uint8List process(Uint8List data) {
    var out = Uint8List(data.length);
    processBytes(data, 0, data.length, out, 0);
    return out;
  }

  @override
  void processBytes(
      Uint8List inp, int inpOff, int len, Uint8List out, int outOff) {
    if ((inpOff + len) > inp.length) {
      throw ArgumentError('input buffer too short');
    }

    if ((outOff + len) > out.length) {
      throw ArgumentError('output buffer too short');
    }

    for (var i = 0; i < len; i++) {
      _x = (_x + 1) & 0xff;
      _y = (_engineState![_x] + _y) & 0xff;

      // swap
      var tmp = _engineState![_x];
      _engineState![_x] = _engineState![_y];
      _engineState![_y] = tmp;

      // xor
      out[i + outOff] = inp[i + inpOff] ^
          _engineState![(_engineState![_x] + _engineState![_y]) & 0xff];
    }
  }

  @override
  void reset() {
    setKey(_workingKey);
  }

  @override
  int returnByte(int inp) {
    _x = (_x + 1) & 0xff;
    _y = (_engineState![_x] + _y) & 0xff;

    // swap
    var tmp = _engineState![_x];
    _engineState![_x] = _engineState![_y];
    _engineState![_y] = tmp;

    // xor
    return inp ^ _engineState![(_engineState![_x] + _engineState![_y]) & 0xff];
  }

  void setKey(Uint8List keyBytes) {
    _workingKey = keyBytes;

    _x = 0;
    _y = 0;

    _engineState ??= Uint8List(STATE_LENGTH);

    // reset the state of the engine
    for (var i = 0; i < STATE_LENGTH; i++) {
      _engineState![i] = i;
    }

    var i1 = 0;
    var i2 = 0;

    for (var i = 0; i < STATE_LENGTH; i++) {
      i2 = ((keyBytes[i1] & 0xff) + _engineState![i] + i2) & 0xff;
      // do the byte-swap inline
      var tmp = _engineState![i];
      _engineState![i] = _engineState![i2];
      _engineState![i2] = tmp;
      i1 = (i1 + 1) % keyBytes.length;
    }
  }
}
