import 'dart:convert';
import 'dart:ffi';
import 'dart:io';
import 'dart:math';
import 'dart:typed_data';
import 'package:ffi/ffi.dart';
import 'package:path_provider/path_provider.dart';


// TODO: 让_Block的size可以设置


class Block16 extends Struct {
  @Array(16) external Array<Uint8> buf;
}
class Block32 extends Struct {
  @Array(32) external Array<Uint8> buf;
}

extension ExBlock16 on Pointer<Block16> {
  Pointer<Uint8> get p => Pointer.fromAddress(address);
  Uint8List get list => p.asTypedList(16);
}
extension ExBlock32 on Pointer<Block32> {
  Pointer<Uint8> get p => Pointer.fromAddress(address);
  Uint8List get list => p.asTypedList(32);
  void copy(Pointer<Block32> key) {
    for (int i=0; i<32; ++i) {
      key.ref.buf[i]=1;
    }
  }
}

class SM2Point extends Struct {
  external Block32 x, y;
}

extension ExSM2Point on Pointer<SM2Point> {
  Pointer<Block32> get x => Pointer.fromAddress(address);
  Pointer<Block32> get y => x.elementAt(1);
}

class Block {
  Block(this.size): p = malloc.allocate(size);

  final int size;
  final Pointer<Uint8> p;
  Uint8List get list => p.asTypedList(size);
  // Pointer<Uint8> elementAt(int index) => p.elementAt(index);
  // int operator[](int index) => p[index];
  // void operator[]=(int index, int value) => p[index] = value;
}

class SM2Key extends Struct {
  external SM2Point publicKey;
  external Block32 privateKey;
}
extension ExSM2Key on Pointer<SM2Key> {
  Pointer<SM2Point> get publicKey => Pointer.fromAddress(address);
  Pointer<Block32> get privateKey => Pointer.fromAddress(address + 64);
  void generate() {
    assert(gm.sm2KeyGenerate(this) == 1);
  }
  void copy(Pointer<SM2Key> key) {
  }
}


class SM4cbcCtx extends Struct {
  @Array(32)
  external Array<Uint32> sm4Key;
  external Block16 iv, block;
  @Size()
  external int blockNBytes;
}

extension ExSM4CBCCtx on Pointer<SM4cbcCtx> {
  Pointer<Uint32> get sm4Key => Pointer.fromAddress(address);
  Pointer<Block16> get iv => Pointer.fromAddress(sm4Key.elementAt(32).address);
  Pointer<Block16> get block => iv.elementAt(1);
  int get size => ref.blockNBytes;
}


final _lib = DynamicLibrary.open("libgmssl.so");
final gm = GmSSL._();

final stderrAddress = _lib.lookup<UintPtr>("stderr").value;


final stdoutAddress = _lib.lookup<UintPtr>("stdout").value;

final _fputs = _lib.lookupFunction<Int Function(Pointer<Utf8>, UintPtr),
    int Function(Pointer<Utf8>, int)>("fputs");

int fputs(String str, int stream) {
  return _fputs(str.toNativeUtf8(), stream);
}

final _puts = _lib.lookupFunction<Int Function(Pointer<Utf8>),
    int Function(Pointer<Utf8>)>("puts");

int puts(String str) {
  return _puts(str.toNativeUtf8());
}

final _freopen = _lib.lookupFunction<
    UintPtr Function(Pointer<Utf8>, Pointer<Utf8>, UintPtr),
    int Function(Pointer<Utf8>, Pointer<Utf8>, int)>("freopen");

int freopen(String path, String mode, int stream) {
  return _freopen(path.toNativeUtf8(), mode.toNativeUtf8(), stream);
}


const sm2MaxPlainText = 255;
const sm2MaxCipherTextSize = 366;

class GmSSL {
  GmSSL._();

  Pointer<SM2Key> newSM2Key() => malloc<SM2Key>();

  final _fopen = _lib.lookupFunction<
      UintPtr Function(Pointer<Utf8>, Pointer<Utf8>),
      int Function(Pointer<Utf8>, Pointer<Utf8>)
  >("fopen");
  final fflush = _lib.lookupFunction<Uint32 Function(UintPtr),
      int Function(int)>("fflush");
  final fclose = _lib.lookupFunction<Uint32 Function(UintPtr),
      int Function(int)>("fclose");

  int fopen(String path, String mode) {
    return _fopen(path.toNativeUtf8(), mode.toNativeUtf8());
  }

  final sm2KeyGenerate = _lib.lookupFunction<Int Function(Pointer<SM2Key>),
      int Function(Pointer<SM2Key>)>("sm2_key_generate");
  // TODO: something wrong ...
  final sm2PrivateKeyInfoEncryptToDER = _lib.lookupFunction<
      Int Function(SM2Key, Pointer<Utf8>, Pointer<Pointer<Uint8>>, Pointer<Size>),
      int Function(SM2Key, Pointer<Utf8>, Pointer<Pointer<Uint8>>, Pointer<Size>)
  >("sm2_private_key_info_encrypt_to_der");

  // TODO: unimplemented ...
  final sm2PrivateKeyInfoDecryptFromDER = _lib.lookupFunction<
      Int Function(SM2Key, Pointer<Pointer<Uint8>>, Pointer<Size>,
          Pointer<Utf8>, Pointer<Pointer<Uint8>>, Pointer<Size>),
      int Function(SM2Key, Pointer<Pointer<Uint8>>, Pointer<Size>,
          Pointer<Utf8>, Pointer<Pointer<Uint8>>, Pointer<Size>)
  >("sm2_private_key_info_decrypt_from_der");


  final sm2PrivateKeyInfoEncryptToPEM = _lib.lookupFunction<
      Int Function(Pointer<SM2Key>, Pointer<Utf8>, UintPtr),
      int Function(Pointer<SM2Key>, Pointer<Utf8>, int)
  >("sm2_private_key_info_encrypt_to_pem");

  final sm2PrivateKeyInfoDecryptFromPEM = _lib.lookupFunction<
      Int Function(Pointer<SM2Key>, Pointer<Utf8>, UintPtr),
      int Function(Pointer<SM2Key>, Pointer<Utf8>, int)
  >("sm2_private_key_info_decrypt_from_pem");

  final sm2PublicKeyInfoToPEM = _lib.lookupFunction<
      Int Function(Pointer<SM2Key>, UintPtr), int Function(Pointer<SM2Key>, int)
  >("sm2_public_key_info_to_pem");
  final sm2PublicKeyInfoFromPEM = _lib.lookupFunction<
      Int Function(Pointer<SM2Key>, UintPtr), int Function(Pointer<SM2Key>, int)
  >("sm2_public_key_info_from_pem");
  final sm2PublicKeyInfoToDER = _lib.lookupFunction<
      Int Function(Pointer<SM2Key>, Pointer<UintPtr>, Pointer<Size>),
      int Function(Pointer<SM2Key>, Pointer<UintPtr>, Pointer<Size>)
  >("sm2_public_key_info_to_der");
  final sm2PublicKeyInfoFromDER = _lib.lookupFunction<
      Int Function(Pointer<SM2Key>, Pointer<Pointer<Uint8>>, Pointer<Size>),
      int Function(Pointer<SM2Key>, Pointer<Pointer<Uint8>>, Pointer<Size>)
  >("sm2_public_key_info_from_der");

  final sm2Encrypt = _lib.lookupFunction<
      Int Function(Pointer<SM2Key>, Pointer<Uint8>, Size, Pointer<Uint8>, Pointer<Size>),
      int Function(Pointer<SM2Key>, Pointer<Uint8>, int, Pointer<Uint8>, Pointer<Size>)
  >("sm2_encrypt");
  final sm2Decrypt = _lib.lookupFunction<
      Int Function(Pointer<SM2Key>, Pointer<Uint8>, Size, Pointer<Uint8>, Pointer<Size>),
      int Function(Pointer<SM2Key>, Pointer<Uint8>, int, Pointer<Uint8>, Pointer<Size>)
  >("sm2_decrypt");

  
  final sm4cbcEncryptInit = _lib.lookupFunction<
      Int Function(Pointer<SM4cbcCtx>, Pointer<Block16>, Pointer<Block16>),
      int Function(Pointer<SM4cbcCtx>, Pointer<Block16>, Pointer<Block16>)
  >("sm4_cbc_encrypt_init");
  final sm4cbcEncryptUpdate = _lib.lookupFunction<
      Int Function(Pointer<SM4cbcCtx>, Pointer<Uint8>, Size, Pointer<Uint8>, Pointer<Size>),
      int Function(Pointer<SM4cbcCtx>, Pointer<Uint8>, int, Pointer<Uint8>, Pointer<Size>)
  >("sm4_cbc_encrypt_update");
  final sm4cbcEncryptFinish = _lib.lookupFunction<
      Int Function(Pointer<SM4cbcCtx>, Pointer<Uint8>, Pointer<Size>),
      int Function(Pointer<SM4cbcCtx>, Pointer<Uint8>, Pointer<Size>)
  >("sm4_cbc_encrypt_finish");
  final sm4cbcDecryptInit = _lib.lookupFunction<
      Int Function(Pointer<SM4cbcCtx>, Pointer<Block16>, Pointer<Block16>),
      int Function(Pointer<SM4cbcCtx>, Pointer<Block16>, Pointer<Block16>)
  >("sm4_cbc_decrypt_init");
  final sm4cbcDecryptUpdate = _lib.lookupFunction<
      Int Function(Pointer<SM4cbcCtx>, Pointer<Uint8>, Size, Pointer<Uint8>, Pointer<Size>),
      int Function(Pointer<SM4cbcCtx>, Pointer<Uint8>, int, Pointer<Uint8>, Pointer<Size>)
  >("sm4_cbc_decrypt_update");
  final sm4cbcDecryptFinish = _lib.lookupFunction<
      Int Function(Pointer<SM4cbcCtx>, Pointer<Uint8>, Pointer<Size>),
      int Function(Pointer<SM4cbcCtx>, Pointer<Uint8>, Pointer<Size>)
  >("sm4_cbc_decrypt_finish");
}

Uint8List sm4cbcEncrypt(Uint8List input, Uint8List sm4key, Uint8List iv) {
  assert(sm4key.length == 16, "key length error");
  assert(iv.length == 16, "key length error");
  // print("sm4cbcEncrypt start...");
  // print("malloc...");
  final ctx = malloc<SM4cbcCtx>();
  final key = malloc<Block16>();
  final civ = malloc<Block16>();
  final inBuf = malloc<Uint8>(4096);
  final outBuf = malloc<Uint8>(4196);
  final outLen = malloc<Size>();
  final output = <int>[];
  // print("copy...");
  for (int i=0; i<16; ++i) {
    key.p[i] = sm4key[i];
  }
  for (int i=0; i<16; ++i) {
    civ.p[i] = iv[i];
  }
  // print("init...");
  assert(gm.sm4cbcEncryptInit(ctx, key, civ) == 1,
  "sm4cbcEncryptInit error");
  // print("encrypt...");
  for (int i=0; i<input.length; i+=4096) {
    final j = min(i+4096, input.length), len = j-i;
    for (int k=i; k<j; ++k) {
      inBuf[k-i] = input[k];
    }
    assert(gm.sm4cbcEncryptUpdate(ctx, inBuf, len, outBuf, outLen) == 1,
    "sm4cbcEncryptUpdate error");
    output.addAll(outBuf.asTypedList(outLen.value));

  }
  assert(gm.sm4cbcEncryptFinish(ctx, outBuf, outLen) == 1,
  "sm4cbcEncryptFinish error");

  output.addAll(outBuf.asTypedList(outLen.value));

  malloc.free(ctx);
  malloc.free(key);
  malloc.free(civ);
  malloc.free(inBuf);
  malloc.free(outBuf);
  malloc.free(outLen);
  // print("sm4cbcEncrypt return...");
  return Uint8List.fromList(output);
}

Uint8List sm4cbcDecrypt(Uint8List input, Uint8List sm4key, Uint8List iv) {
  assert(sm4key.length == 16, "key length error");
  assert(iv.length == 16, "key length error");
  // print("sm4cbcDecrypt start...");
  final ctx = malloc<SM4cbcCtx>();
  final key = malloc<Block16>();
  final civ = malloc<Block16>();
  final inBuf = malloc<Uint8>(4096);
  final outBuf = malloc<Uint8>(4196);
  final outLen = malloc<Size>();
  final output = <int>[];
  for (int i=0; i<16; ++i) {
    key.p[i] = sm4key[i];
  }
  for (int i=0; i<16; ++i) {
    civ.p[i] = iv[i];
  }
  assert(gm.sm4cbcDecryptInit(ctx, key, civ) == 1,
  "sm4cbcDecryptInit error");
  for (int i=0; i<input.length; i+=4096) {
    final j = min(i+4096, input.length), len = j-i;
    for (int k=i; k<j; ++k) {
      inBuf[k-i] = input[k];
    }
    assert(gm.sm4cbcDecryptUpdate(ctx, inBuf, len, outBuf, outLen) == 1,
    "sm4cbcEncryptUpdate error");
    output.addAll(outBuf.asTypedList(outLen.value));
  }
  assert(gm.sm4cbcDecryptFinish(ctx, outBuf, outLen) == 1,
  "sm4cbcDecryptFinish error");

  output.addAll(outBuf.asTypedList(outLen.value));

  malloc.free(ctx);
  malloc.free(key);
  malloc.free(civ);
  malloc.free(inBuf);
  malloc.free(outBuf);
  malloc.free(outLen);
  // print("sm4cbcDecrypt return...");
  return Uint8List.fromList(output);
}

Uint8List sm2PublicKeyInfoToDER(Pointer<SM2Key> key) {
  final buf = malloc<Uint8>(512);
  final p = malloc<UintPtr>(1);
  p.value = buf.address;
  final len = malloc<Size>(1);
  len.value = 0;
  assert(gm.sm2PublicKeyInfoToDER(key, p, len) == 1);
  assert(len.value >= 0 && len.value < 512, "error: len.value == ${len.value}");
  final res = buf.asTypedList(len.value);
  malloc.free(buf);
  malloc.free(len);
  malloc.free(p);
  return res;
}

int sm2PublicKeyInfoFromDER(Pointer<SM2Key> key, Uint8List der) {
  final buf = malloc<Uint8>(512);
  final len = malloc<Size>();
  len.value = der.length;
  final p = malloc<Pointer<Uint8>>();
  p.value = buf;
  for (int i=0; i<der.length; ++i) {
    buf[i] = der[i];
  }
  final res = gm.sm2PublicKeyInfoFromDER(key, p, len);
  malloc.free(buf);
  malloc.free(p);
  malloc.free(len);
  return res;
}

// Future<String> get _tempPemPath async =>
//     "${(await getTemporaryDirectory()).path}/temp.pem";


// Future<String> sm2PublicKeyInfoToPEMString(SM2Key key) async {
//   final path = await _tempPemPath;
//   final res = sm2PublicKeyInfoToPEM(key, path);
//   assert(res == 1);
//   return File(path).readAsStringSync();
// }

int sm2PublicKeyInfoToPEM(Pointer<SM2Key> key, String path) {
  final pf = gm.fopen(path, "w");
  final res = gm.sm2PublicKeyInfoToPEM(key, pf);
  // TODO: 封装一个file类隐式执行fflush & fclose
  gm.fflush(pf);
  gm.fclose(pf);
  return res;
}

int sm2PublicKeyInfoFromPEM(Pointer<SM2Key> key, String path) {
  final pf = gm.fopen(path, "r");
  assert(pf != 0, "fopen $path failed");
  final res = gm.sm2PublicKeyInfoFromPEM(key, pf);
  gm.fflush(pf);
  gm.fclose(pf);
  return res;
}

// Future<int> sm2PublicKeyInfoFromPEMString(SM2Key key, String pem) async {
//   final path = await _tempPemPath;
//   File file = File(path);
//   file.writeAsStringSync(pem);
//   return sm2PublicKeyInfoFromPEM(key, path);
// }

// Future<String> sm2PrivateKeyInfoEncryptToPEMString(
//     SM2Key key, Pointer<Utf8> pass) async {
//   final path = await _tempPemPath;
//   final res = sm2PrivateKeyInfoEncryptToPEM(key, pass, path);
//   return File(path).readAsStringSync();
//   // pf = gm.fopen(savePath, "r");
//   // assert(gm.sm2PrivateKeyInfoDecryptFromPEM(key, pass, pf) == 1);
// }

void sm2PrivateKeyInfoEncryptToPEM(Pointer<SM2Key> key, Pointer<Utf8> pass, String path) {
  final pf = gm.fopen(path, "w");
  assert(pf != 0, "fopen $path failed");
  final res = gm.sm2PrivateKeyInfoEncryptToPEM(key, pass, pf);
  gm.fflush(pf);
  gm.fclose(pf);
  assert(res == 1);
}

int sm2PrivateKeyInfoDecryptFromPEM(Pointer<SM2Key> key, Pointer<Utf8> pass, String path) {
  final pf = gm.fopen(path, "r");
  assert(pf != 0, "fopen $path failed");
  final res = gm.sm2PrivateKeyInfoDecryptFromPEM(key, pass, pf);
  gm.fflush(pf);
  gm.fclose(pf);
  return res;
}

Uint8List sm2Encrypt(Pointer<SM2Key> key, Uint8List plaintext) {
  final inLen = plaintext.length;
  assert(inLen <= sm2MaxPlainText,
  "plaintext length > $sm2MaxPlainText");
  Pointer<Uint8> input = malloc(sm2MaxPlainText + 1);
  Pointer<Uint8> output = malloc(sm2MaxCipherTextSize);
  Pointer<Size> outLen = malloc<Size>();
  for (int i=0; i<inLen; ++i) {
    input[i] = plaintext[i];
  }
  final res = gm.sm2Encrypt(key, input, inLen, output, outLen);
  assert(res == 1);
  malloc.free(input);
  malloc.free(output);
  malloc.free(outLen);
  return output.asTypedList(outLen.value);
}

Uint8List sm2Decrypt(Pointer<SM2Key> key, Uint8List ciphertext) {
  final inLen = ciphertext.length;
  assert(inLen <= sm2MaxCipherTextSize, "ciphertext length > $sm2MaxCipherTextSize");
  Pointer<Uint8> input = malloc(sm2MaxCipherTextSize);
  Pointer<Uint8> output = malloc(sm2MaxCipherTextSize);
  Pointer<Size> outLen = malloc<Size>();
  for (int i=0; i<inLen; ++i) {
    input[i] = ciphertext[i];
  }
  final res = gm.sm2Decrypt(key, input, inLen, output, outLen);
  assert(res == 1);
  malloc.free(input);
  malloc.free(output);
  malloc.free(outLen);
  return output.asTypedList(outLen.value);
}

Uint8List sm2PrivateKeyInfoEncryptToDER(SM2Key key, Pointer<Utf8> pass) {
  // throw UnimplementedError("this method run incorrectly");
  // TODO: something wrong...
  // TODO: 检查使用完成后是否需要清理&释放内存
  Pointer<Uint8> buf = malloc.allocate(1024 * sizeOf<Uint8>());
  Pointer<Pointer<Uint8>> p = malloc.allocate(sizeOf<Pointer>());
  p.value = buf;
  Pointer<Size> len = calloc.allocate(sizeOf<Size>());

  final res = gm.sm2PrivateKeyInfoEncryptToDER(key, pass, p, len);
  assert(res == 1);
  final list = buf.asTypedList(len.value);
  // TODO: safety clean
  malloc.free(buf);
  malloc.free(p);
  malloc.free(len);
  return list;
}
