//
// Created by #Suyghur, on 2021/06/11.
//

#include <hex_utils.h>
#include <logger.h>
#include <toolkit.h>
#include "include/aes/aes_utils.h"

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

static const unsigned char CHAR_SET[36] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
                                           'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j',
                                           'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
                                           'u', 'v', 'w', 'x', 'y', 'z'};

uint8_t *AesUtils::GetPaddingInput(const char *input) {
    // 输入的长度
    int input_len = (int) strlen(input);
    int remainder = input_len % BLOCKLEN;
    uint8_t *padding_input = nullptr;
    int group = input_len / BLOCKLEN;
    auto size = (size_t) (BLOCKLEN * (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 AesUtils::FindPaddingIndex(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 AesUtils::RemovePadding(uint8_t *out, size_t length) {
    int index = FindPaddingIndex(out, length);

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

char *AesUtils::GetRawKey() {
    const int len = 26;
    time_t ts = time(nullptr);
    srandom(ts);
    auto *raw_key = static_cast<char *>(malloc(len + 1));
    memset(raw_key, 0, len + 1);
    for (int i = 0; i < 16; ++i) {
        raw_key[i] = CHAR_SET[random() % 36];
    }
    sprintf(raw_key, "%s%ld", raw_key, ts);
    raw_key[len] = '\0';
    return raw_key;
}

uint8_t *AesUtils::GetKey() {
    const int len = 16;
    auto *key = static_cast<uint8_t *>(malloc(len + 1));
    for (int i = 0; i < len; ++i) {
        key[i] = CHAR_SET[random() % 36];
    }
    key[len] = '\0';
    return key;
}

uint8_t *AesUtils::GetIv(const uint8_t *key) {
    const int len = 16;
    auto *iv = static_cast<uint8_t *>(malloc(len + 1));
    for (int i = 0; i < len; i++) {
        iv[i] = key[len - (i + 1)];
    }
    iv[len] = '\0';
    return iv;
}

char *AesUtils::GetIv32(const char *key) {
    const int len = 32;
    auto *iv = static_cast<char *>(malloc(len + 1));
    for (int i = 0; i < len; i++) {
        iv[i] = key[len - (i + 1)];
    }
    iv[len] = '\0';
    return iv;
}

/**
 * AES加密, CBC, PKCS5Padding
 */
char *AesUtils::Encrypt(const char *input, const uint8_t *key) {
//    const uint8_t *key = GetKey();
    const uint8_t *iv = GetIv(key);

    uint8_t *padding_input = GetPaddingInput(input);
    size_t padding_input_length = strlen((char *) padding_input);
    auto *out = (unsigned char *) malloc(padding_input_length);
    AES_CBC_encrypt_buffer(out, padding_input, (uint32_t) padding_input_length, key, iv);

    char *hex_raw = HexUtils::HexEncode(out, padding_input_length);

    free(padding_input);
    free(out);
//    free((void *) key);
    free((void *) iv);

    return hex_raw;
}

/**
 * AES解密, CBC, PKCS5Padding
 */
char *AesUtils::Decrypt(const char *input, const uint8_t *key) {

    const uint8_t *iv = GetIv(key);
//    const uint8_t *iv = reinterpret_cast<const uint8_t *>(ToolKit::StrReverse(reinterpret_cast<const char *>(key)));

    size_t len = strlen(input);
    unsigned char *input_des = HexUtils::HexDecode(input);

    const size_t input_length = (len / 4) * 3 / BLOCKLEN * BLOCKLEN;
    auto *src = static_cast<uint8_t *>(malloc(input_length));
    memset(src, 0, input_length);
    AES_CBC_decrypt_buffer(src, input_des, (uint32_t) input_length, key, iv);

    RemovePadding(src, input_length);
    free(input_des);
//    free((void *) key);
    free((void *) iv);

    return (char *) src;
}

std::string AesUtils::Decrypt2(JNIEnv *env, const std::string &key, const std::string &enc) {
    jclass clz = env->FindClass("cn/yyxx/support/encryption/aes/AesUtils");
    if (clz == nullptr) {
        LOGE("aes impl clz is nullptr !!!");
        return "";
    }
    const char *method_name = "decrypt";
    const char *sig = "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;";
    jmethodID jmethod_id = env->GetStaticMethodID(clz, method_name, sig);
    jstring jkey = env->NewStringUTF(key.c_str());
    jstring jraw = env->NewStringUTF(enc.c_str());
    jstring jresult = (jstring) env->CallStaticObjectMethod(clz, jmethod_id, jkey, jraw);
    return ToolKit::JString2String(env, jresult);
}

std::string AesUtils::Encrypt2(JNIEnv *env, const std::string &key, const std::string &raw) {
    jclass clz = env->FindClass("cn/yyxx/support/encryption/aes/AesUtils");
    if (clz == nullptr) {
        LOGE("aes impl clz is nullptr !!!");
        return "";
    }
    const char *method_name = "encrypt";
    const char *sig = "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;";
    jmethodID jmethod_id = env->GetStaticMethodID(clz, method_name, sig);
    jstring jkey = env->NewStringUTF(key.c_str());
    jstring jraw = env->NewStringUTF(raw.c_str());
    jstring jresult = (jstring) env->CallStaticObjectMethod(clz, jmethod_id, jkey, jraw);
    return ToolKit::JString2String(env, jresult);
}



