#include <stdio.h>
#include <stdlib.h>
#include <cstring>
#include "cipher/aes.h"
#include "cipher/des.h"
#include "cipher/des3.h"
#include "cipher/rc4.h"
#include "cipher/rsa.h"
#include "cipher/ecc.h"


#include "lzo/minilzo.h"
#include "ucl/ucl.h"
#include "zstd/zstd.h"
#include "lz4/lz4.h"


#ifdef __cplusplus
extern "C" {
#endif
#include "lzma/FileIO.h"
#include "lzma/TinyLzmaCompress.h"
#include "lzma/TinyLzmaDecompress.h"
#ifdef __cplusplus
}
#endif

#define HEAP_ALLOC(var, size) \
    lzo_align_t __LZO_MMODEL var [ ((size) + (sizeof(lzo_align_t) - 1)) / sizeof(lzo_align_t) ]

// 定义各个字段的偏移量
#define SYMMETRIC_ENC_TYP_OFFSET      0
#define ASYMMETRIC_ENC_TYP_OFFSET     1
#define COMP_TYP_OFFSET             2
#define INITIAL_FILE_SIZE_OFFSET    3
#define COMPRESSED_FILE_SIZE_OFFSET 7
#define ENCRYPTED_FILE_SIZE_OFFSET  11
#define ENCRYPTED_KEY_SIZE_OFFSET 15
#define PROTECT_TYPE_OFFSET         19
#define MAC_NUMS_OFFSET             20

static HEAP_ALLOC(wrkmem, LZO1X_1_MEM_COMPRESS);

const int MAGIC_LENGTH = 32;

int deenc(char *f) {
  uint8_t *magic = (uint8_t *) malloc(sizeof(uint8_t) * MAGIC_LENGTH);
  FILE *fp = fopen(f, "rb");
  size_t magic_read_count = fread(magic, sizeof(uint8_t), MAGIC_LENGTH, fp);
  if (magic_read_count != MAGIC_LENGTH) {
    printf("error: Unable to read %d bytes from file\n", MAGIC_LENGTH);
    return EXIT_FAILURE;
  }

  uint8_t symmetric_enc_typ = magic[SYMMETRIC_ENC_TYP_OFFSET];
  uint8_t asymmetric_enc_typ = magic[ASYMMETRIC_ENC_TYP_OFFSET];
  uint8_t comp_typ = magic[COMP_TYP_OFFSET];

  uint32_t initial_file_size;
  memcpy(&initial_file_size, magic + INITIAL_FILE_SIZE_OFFSET, sizeof(uint32_t));

  uint32_t compressed_file_size;
  memcpy(&compressed_file_size, magic + COMPRESSED_FILE_SIZE_OFFSET, sizeof(uint32_t));

  uint32_t encrypted_file_size;
  memcpy(&encrypted_file_size, magic + ENCRYPTED_FILE_SIZE_OFFSET, sizeof(uint32_t));

  uint32_t encrypted_key_size;
  memcpy(&encrypted_key_size, magic + ENCRYPTED_KEY_SIZE_OFFSET, sizeof(uint32_t));

  uint8_t protect_type;
  memcpy(&protect_type, magic + PROTECT_TYPE_OFFSET, sizeof(uint8_t));

  uint8_t mac_num;
  memcpy(&mac_num, magic + MAC_NUMS_OFFSET, sizeof(uint8_t));


  /**
   * DES: 64bit  1 key_length 8
   * AES: 128bit 2 key_length 32
   * 3DES: 64bit 3 key_length 24
   * RC4: 256bit 4 key_length 32
   */
  uint8_t input_length, key_length;
  if (symmetric_enc_typ == 1) input_length = 8, key_length = 8;
  else if (symmetric_enc_typ == 2) input_length = 16, key_length = 32;
  else if (symmetric_enc_typ == 3) input_length = 8, key_length = 24;
  else if (symmetric_enc_typ == 4) input_length = 1, key_length = 32;
  else input_length = 0, key_length = 0;

  printf("uint8_t symmetric_enc_typ: %u\n", symmetric_enc_typ);
  printf("uint8_t asymmetric_enc_typ: %u\n", asymmetric_enc_typ);
  printf("uint8_t comp_typ: %u\n", comp_typ);
  printf("uint32_t file_size: %u\n", initial_file_size);
  printf("uint32_t compressed_file_size: %u\n", compressed_file_size);
  printf("uint32_t encrypted_file_size: %u\n", encrypted_file_size);
  printf("uint8_t input_length: %u\n", input_length);
  printf("uint8_t key_length: %u\n", key_length);

  auto *encrypted_array = (uint8_t *) malloc(sizeof(uint8_t) * encrypted_file_size);
//  auto *shuffle = (uint8_t *) malloc(sizeof(uint8_t) * key_length);
  uint8_t *encrypted_key = (uint8_t *) malloc(encrypted_key_size);

  printf("encrypted_array confused_key malloc success\n");
  size_t encrypted_read_count = fread(encrypted_array, sizeof(uint8_t), encrypted_file_size, fp);
  if (encrypted_read_count != encrypted_file_size) {
    printf("error: unable to read %d bytes from file\n", encrypted_file_size);
    return EXIT_FAILURE;
  }

  struct public_key_class pub[1];
  struct private_key_class priv[1];

  uint8_t pub_key[ECC_KEYSIZE];
  uint8_t prv_key[ECC_KEYSIZE];

  if (asymmetric_enc_typ != 0) {
    size_t key_read_count = fread(encrypted_key, sizeof(uint8_t), encrypted_key_size, fp);
    if (key_read_count != encrypted_key_size) {
      printf("error: unable to read %d bytes from file\n", encrypted_key_size);
      return EXIT_FAILURE;
    }

    if (asymmetric_enc_typ == 5) {
      size_t pub_bytes_read = fread(&pub, sizeof(struct public_key_class), 1, fp);
      if (pub_bytes_read != 1) {
        printf("error: unable to read %d bytes from file\n", 1);
        return EXIT_FAILURE;
      }
      size_t priv_bytes_read = fread(&priv, sizeof(struct private_key_class), 1, fp);
      if (priv_bytes_read != 1) {
        printf("error: unable to read %d bytes from file\n", 1);
        return EXIT_FAILURE;
      }

      /**
       * Private Key:
         Modulus: 3463516351
         Exponent: 2429436001
         Public Key:
         Modulus: 3463516351
         Exponent: 131073
       *
       */
      printf("Private Key:\n Modulus: %lld\n Exponent: %lld\n", (long long) priv->modulus, (long long) priv->exponent);
      printf("Public Key:\n Modulus: %lld\n Exponent: %lld\n", (long long) pub->modulus, (long long) pub->exponent);
      /**
       * 1384984386 1683296021 2114995281 834109981 872949891 832896308 1564298768 1546948745
       */

    } else if (asymmetric_enc_typ == 6) {
      size_t pub_bytes_read = fread(&pub_key, sizeof(uint8_t), ECC_KEYSIZE, fp);
      if (pub_bytes_read != ECC_KEYSIZE) {
        printf("error: unable to read %d bytes from file\n", ECC_KEYSIZE);
        return EXIT_FAILURE;
      }
      size_t priv_bytes_read = fread(&prv_key, sizeof(uint8_t), ECC_KEYSIZE, fp);
      if (priv_bytes_read != ECC_KEYSIZE) {
        printf("error: unable to read %d bytes from file\n", ECC_KEYSIZE);
        return EXIT_FAILURE;
      }
      printf("ECC prv_key:\n");
      for (int i = 0; i < ECC_KEYSIZE; i++) {
        printf("0x%02x,", prv_key[i]);
      }
      printf("\n");
    }
  }

  printf("mac info\n");
  uint8_t mac_array[10][18];
  for (int i = 0; i < mac_num; i++) {
    fread(mac_array[i], sizeof(uint8_t), 18, fp);
    printf("%s\n", mac_array[i]);
  }

  uint8_t *confused_key = (uint8_t *) (encrypted_key);

  printf("enc key:\n");
  for (int i = 0; i < encrypted_key_size; i++) {
    printf("%02x ", confused_key[i]);
  }
  printf("\n");

  if (asymmetric_enc_typ != 0) {
    if (asymmetric_enc_typ == 5) {
      printf("start to decrypt key using RSA\n");
      confused_key = rsa_decrypt((long long *) confused_key, encrypted_key_size, priv);
      if (confused_key == NULL) {
        printf("failed to decrypt using RSA\n");
        return EXIT_FAILURE;
      }
    } else if (asymmetric_enc_typ == 6) {
      printf("start to decrypt key using ECC\n");
      uint32_t tm_encrypted_key_size;
      ecc_decrypt(prv_key, (uint8_t *) encrypted_key, encrypted_key_size, confused_key, &tm_encrypted_key_size);
      if (encrypted_key_size != tm_encrypted_key_size) {
        printf("failed to decrypt using ECC\n");
        return EXIT_FAILURE;
      }
    }
  }

  printf("decrypted challenge data or key:\n");
  for (int i = 0; i < key_length; i++) {
    printf("%02x ", confused_key[i]);
  }
  printf("\n");

  if (protect_type == 1) {
    //通信获取秘钥
    uint8_t puf_value[39] = {0xa5, 0x5a, 0x20, 0x00, 0x8f, 0xad, 0x85, 0xc8, 0xc1, 0xc2, 0x85, 0xbc, 0x4c, 0x22, 0x9d,
                             0xa2, 0x5e, 0x6e, 0x44, 0x4c, 0xf5, 0x65, 0xfb, 0x8c, 0x4b, 0xb3, 0x4f, 0xc0, 0xd9, 0x02,
                             0xa0, 0xaa, 0xb9, 0xb6, 0x0a, 0xb4, 0x08, 0x6c, 0xff};

    memcpy(confused_key, puf_value + 4, 32);
  }


  uint8_t *decryption_array = (uint8_t *) malloc(compressed_file_size * sizeof(uint8_t));
  uint8_t *initial_file_array = (uint8_t *) malloc(initial_file_size * sizeof(uint8_t));

  printf("get key successfully\n");

  printf("final bytes:\n");
  for (int i = 0; i < 8; i++) {
    printf("%02x ", encrypted_array[i]);
  }
  printf("\n");

  for (int i = encrypted_file_size - 8; i < encrypted_file_size; i++) {
    printf("%02x ", encrypted_array[i]);
  }
  printf("\n");

  if (symmetric_enc_typ > 0 && comp_typ > 0) {
    // compress -> cryption
    // decryption
    printf("decryption_array malloc success\n");
    if (symmetric_enc_typ == 1) {
      printf("start decrypt with DES\n");
      DesContext des_context;
      desInit(&des_context, confused_key, key_length);
      for (int i = 0; i < encrypted_file_size; i += input_length) {
        desDecryptBlock(&des_context, encrypted_array + i, decryption_array + i);
      }
      printf("decryption successes with DES\n");
    } else if (symmetric_enc_typ == 2) {
      printf("start decrypt with AES\n");
      AesContext aes_context;
      aesInit(&aes_context, confused_key, key_length);
      for (int i = 0; i < encrypted_file_size; i += input_length) {
        aesDecryptBlock(&aes_context, encrypted_array + i, decryption_array + i);
      }
      printf("decryption successes with AES\n");
    } else if (symmetric_enc_typ == 3) {
      printf("start decrypt with 3DES\n");
      Des3Context des3_context;
      des3Init(&des3_context, confused_key, key_length);
      for (int i = 0; i < encrypted_file_size; i += input_length) {
        des3DecryptBlock(&des3_context, encrypted_array + i, decryption_array + i);
      }
      printf("decryption successes with TDEA\n");
    } else if (symmetric_enc_typ == 4) {
      printf("start decrypt with RC4\n");
      rc4_decrypt(encrypted_array, encrypted_file_size, confused_key, key_length);
      memcpy(decryption_array, encrypted_array, encrypted_file_size);
      printf("decryption successes with RC4\n");
    }
    printf("decrypt success\n");
    printf("compress bytes:\n");
    for (int i = 0; i < 8; i++) {
      printf("%02x ", decryption_array[i]);
    }
    printf("\n");

    // decompress
    printf("initial_file_array malloc success\n");
    int r = -1;
    unsigned long decompressed_file_size;
    if (comp_typ == 1) {
      printf("decompress with lzo...\n");
      r = lzo1x_decompress(decryption_array, compressed_file_size, initial_file_array, &decompressed_file_size, NULL);
      printf("%d\n", r);
    } else if (comp_typ == 2) {
      printf("decompress with ucl...\n");
      r = ucl_nrv2b_decompress_8(decryption_array, compressed_file_size, initial_file_array,
                                 reinterpret_cast<ucl_uint *>(&decompressed_file_size), NULL);
      printf("%d\n", r);
      decompressed_file_size = reinterpret_cast<unsigned long>(decompressed_file_size);

    } else if (comp_typ == 3) {
      printf("decompress with lzma...\n");
      r = tinyLzmaDecompress(decryption_array, compressed_file_size, initial_file_array, &decompressed_file_size);

    } else if (comp_typ == 4) {
      printf("decompress with zstd...\n");
      decompressed_file_size = ZSTD_decompress(initial_file_array, initial_file_size, decryption_array,
                                               compressed_file_size);
      printf("zstd %d\n", decompressed_file_size);
    } else if (comp_typ == 5) {
      printf("decompress with lz4...\n");
      decompressed_file_size = LZ4_decompress_safe(decryption_array, initial_file_array, compressed_file_size,
                                                   initial_file_size);
      printf("LZ4 %d\n", decompressed_file_size);
      decompressed_file_size = reinterpret_cast<unsigned long>(decompressed_file_size);
    }
    printf("decompressed_file_size %u\n", decompressed_file_size);
    if (static_cast<unsigned int>(decompressed_file_size) == initial_file_size) {
      printf("decompressed %u bytes back into %u bytes\n",
             compressed_file_size, decompressed_file_size);
    } else {
      /* this should NEVER happen */
      printf("internal error - decompression failed: %d\n", r);
      return 1;
    }
  } else if (symmetric_enc_typ > 0) {
  }

  int length = strlen("depack_") + strlen(f) + 1;
  char result[length];
  memset(result, 0, sizeof(result));

  // 拼接字符串
  strcat(result, "depack_");
  strcat(result, f);

  printf("拼接结果: %s\n", result);

  FILE *p = fopen(result, "wb");
  fwrite(initial_file_array, sizeof(uint8_t), initial_file_size, p);
  fclose(p);
  return 0;
}

long get_file_size(const char *filename) {
  FILE *file = fopen(filename, "rb");
  if (file == NULL) {
    return -1;
  }

  fseek(file, 0, SEEK_END);
  long size = ftell(file);
  fclose(file);

  return size;
}

void cmp(char *f1, char *f2) {
  FILE *file;
  file = fopen("cmp.txt", "a");

  if (file == NULL) {
    printf("无法打开文件\n");
    return;
  }

  if (freopen("cmp.txt", "a", stdout) == NULL) {
    printf("无法重定向标准输出\n");
    return;
  }

  printf("%s cmp %s ", f1, f2);
  FILE *file1 = fopen(f1, "rb");
  FILE *file2 = fopen(f2, "rb");

  if (file1 == NULL || file2 == NULL) {
    printf("Error opening files.\n");
    return;
  }

  long size1 = get_file_size(f1);
  long size2 = get_file_size(f2);

  if (size1 != size2) {
    printf("File sizes are different.\n");
    return;
  }

  int isSame = 1;
  int byte1, byte2;
  while ((byte1 = fgetc(file1)) != EOF && (byte2 = fgetc(file2)) != EOF) {
    if (byte1 != byte2) {
      isSame = 0;
      break;
    }
  }
  fclose(file1);
  fclose(file2);
  if (isSame) printf("成功\n");
  else printf("失败\n");
  fclose(stdout);
  fclose(file);
}

int main(int argc, char *argv[]) {

  int i = 1, j = 5, k = 5;
  char f1[9];
  // 将 h, i, j, k 格式化写入 result 数组中
  sprintf(f1, "%c%d%d%d.bin", 't', i, j, k);
  deenc(f1);

  char f2[9 + 6];
  strcat(f2, "depack_");
  strcat(f2, f1);

  cmp("test.bin", f2);

/**
 *
 *  compress bytes:
    03 43 00 00 ea 00 00 38
    7f 01 00 00 00 11 00 00

    compress bytes:
    62 23 3f 25 0b af 22 d1
    f4 23 94 2f de 0a 1b a5
 *
 *
 *
 * 151 main
 *  compress bytes:
    03 43 00 00 ea 00 00 38
    7f 01 00 00 00 11 00 00

    compress bytes:
    03 43 00 00 ea 00 00 38
    7f 01 00 00 00 11 00 00

    main
     key:
    6f ec 0f 26 70 ee 07 8e e7 ca 61 5b d9 2a 80 7d 17 67 fa c6 f0 b0 03 31 fc 7a 93 8e d6 a1 f1 fa


   00 ec 00 00 70 00 00 00 e7 ca 00 00 00 00 80 00 51 00 00 c6 00 00 00 00 fc 00 00 00 00 00 f1 00

 *
 */
}