//
// Created by #Suyghur, on 2021/10/15.
//

#include <cstring>
#include <cstdlib>
#include <string>
#include "include/tool_kit.h"
#include "include/yylog.h"
#include "include/hex_utils.h"
#include "include/aes.h"
#include "include/aes_kit.h"

static const unsigned char HEX[16] = {0x10, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f};

/**
 * AES加密, CBC, PKCS5Padding
 */
char *AesKit::Encrypt(const char *input) {

    uint8_t *padding_input = GetPaddingInput(input);
    size_t padding_input_length = strlen((char *) padding_input);
    auto *out = (unsigned char *) malloc(padding_input_length);
    AesCbcEncryptBuffer(out, padding_input, (uint32_t) padding_input_length, reinterpret_cast<const uint8_t *>(AES_KEY), reinterpret_cast<const uint8_t *>(IV));
    char *hex_enc = HexUtils::HexEncode(out, padding_input_length);

    free(padding_input);
    free(out);
    return hex_enc;
}

/**
 * AES解密, CBC, PKCS5Padding
 */
char *AesKit::Decrypt(const char *input) {
    size_t len = strlen(input);
    uint8_t *input_des = HexUtils::HexDecode(input);
    const size_t input_len = (len / 4) * 3 / BLOCK_LEN * BLOCK_LEN;
    auto *src = static_cast<uint8_t *>(malloc(input_len));
    memset(src, 0, input_len);
    AesCbcDecryptBuffer(src, input_des, (uint32_t) input_len, reinterpret_cast<const uint8_t *>(AES_KEY), reinterpret_cast<const uint8_t *>(IV));
    RemovePadding(src, input_len);
    free(input_des);
    return reinterpret_cast<char *>(src);
}

uint8_t *AesKit::GetPaddingInput(const char *input) {
    // 输入的长度
    int input_len = (int) strlen(input);
    int remainder = input_len % BLOCK_LEN;
    uint8_t *padding_input = nullptr;
    int group = input_len / BLOCK_LEN;
    auto size = (size_t) (BLOCK_LEN * (group + 1));

    padding_input = (uint8_t *) malloc(size + 1);

    int dif = (int) size - input_len;
    for (int i = 0; i < size; i++) {
        if (i < input_len) {
            padding_input[i] = (uint8_t) input[i];
        } else {
            if (remainder == 0) {
                // 刚好是16倍数, 就填充16个16 (0x10)
                padding_input[i] = HEX[0];
            } else {
                // 如果不足16位 少多少位就补几个几
                padding_input[i] = HEX[dif];
            }
        }
    }
    padding_input[size] = '\0';
    return padding_input;
}

int AesKit::FindPaddingIndex(const uint8_t *str, size_t length) {
    int i, k;
    for (i = 0; i < length; i++) {
        char c = str[i];
        if ('\0' != c) {
            for (k = 0; k < 16; ++k) {
                if (HEX[k] == c) {
                    return i;
                }
            }
        }
    }
    return i;
}

void AesKit::RemovePadding(uint8_t *out, size_t length) {
    int index = FindPaddingIndex(out, length);

    if (index < length) {
        memset(out + index, '\0', length - index);
    }
}

jstring AesKit::Encrypt(JNIEnv *env, const char *input) {
    jclass clz = env->FindClass("cn/yyxx/support/encryption/aes/AesUtils");
    if (clz == nullptr) {
        LOGD("aes impl clz is nullptr !!!");
        return env->NewStringUTF("");
    }
    const char *method_name = "encrypt2hex";
    const char *sig = "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;";
    jmethodID mid = env->GetStaticMethodID(clz, method_name, sig);
    jstring key = env->NewStringUTF(AES_KEY);
    jstring iv = env->NewStringUTF(IV);
    jstring data = env->NewStringUTF(input);
    auto result = (jstring) env->CallStaticObjectMethod(clz, mid, key, iv, data);
    env->DeleteLocalRef(clz);
    env->DeleteLocalRef(key);
    env->DeleteLocalRef(iv);
    env->DeleteLocalRef(data);
    return result;
}

jstring AesKit::Decrypt(JNIEnv *env, const char *input) {
    jclass clz = env->FindClass("cn/yyxx/support/encryption/aes/AesUtils");
    if (clz == nullptr) {
        LOGD("aes impl clz is nullptr !!!");
        return env->NewStringUTF("");
    }
    const char *method_name = "decrypt2hex";
    const char *sig = "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;";
    jmethodID mid = env->GetStaticMethodID(clz, method_name, sig);
    jstring key = env->NewStringUTF(AES_KEY);
    jstring iv = env->NewStringUTF(IV);
    jstring data = env->NewStringUTF(input);
    auto result = (jstring) env->CallStaticObjectMethod(clz, mid, key, iv, data);
    env->DeleteLocalRef(clz);
    env->DeleteLocalRef(key);
    env->DeleteLocalRef(iv);
    env->DeleteLocalRef(data);
    return result;
}


