//
// Created by xajhu on 2021/7/2 0002.
//
#include <string.h>
#include <openssl/aes.h>
#include <openssl/evp.h>

#include "task_manager.h"
#include "user_errno.h"

#include "crypto.h"
#include "common.h"

static uv_mutex_t *pEvpMutex = NULL;

typedef struct {
    CRYPTO_TYPE    type;
    unsigned char *pInData;
    int            iInSize;
    unsigned char *pOutData;
    unsigned char  pKey[EVP_MAX_KEY_LENGTH + 1];
    on_evp_crypto  onEvpEventCb;
} CRYPT_TASK_PARAMS, *PCRYPT_TASK_PARAMS;

static void freeEVPWorkCb(uv_work_t *pWork, int UNUSED(status)) {
    PCRYPT_TASK_PARAMS pTask = (PCRYPT_TASK_PARAMS)pWork->data;
    free(pTask->pInData);
    free(pTask);
    free(pWork);
}

static void onEVPWorkCb(uv_work_t *pWork) {
    PCRYPT_TASK_PARAMS pTask    = (PCRYPT_TASK_PARAMS)pWork->data;
    unsigned int       uOutSize = 0;
    int                iError   = 0;

    switch (pTask->type) {
#if 0
        case CRYPTO_AES_ENCRYPT:
            iError = EvpAESEncrypto(pTask->pInData,
                                    pTask->iInSize,
                                    pTask->pOutData,
                                    &uOutSize,
                                    pTask->pKey);
            break;

        case CRYPTO_AES_DECRYPT:
            iError = EvpAESDecrypto(pTask->pInData,
                                    pTask->iInSize,
                                    pTask->pOutData,
                                    &uOutSize,
                                    pTask->pKey);
            break;
#endif
        case CRYPTO_BASE64_ENCODE:
            pTask->pOutData = (unsigned char *)base64_encode((unsigned char *)pTask->pInData, pTask->iInSize);
            uOutSize        = strlen((char *)pTask->pOutData);
            break;

        case CRYPTO_BASE64_DECODE:
            pTask->pOutData = (unsigned char *)base64_decode((const char *)pTask->pInData, &uOutSize);
            //uOutSize = strlen((char *)pTask->pOutData);
            break;
#if 0
            case CRYPTO_MD5_FILE:
                pTask->pOutData = (unsigned char *)EvpMD5HashFile((const char *)pTask->pInData);
                uOutSize = strlen((char *)pTask->pOutData);
                break;
#endif
        default:
            iError = -ERR_UNSUP_EVP_TYPE;
    }

    if (iError != 0) {
        pTask->onEvpEventCb(pTask->type, pTask->pOutData, 0, pTask->pInData, iError);
    } else {
        pTask->onEvpEventCb(pTask->type, pTask->pOutData, uOutSize, pTask->pInData, 0);
    }
}

int evp_add_crypto_task(CRYPTO_TYPE    type,
                        unsigned char *pInBuf,
                        unsigned int   iSize,
                        unsigned char *pOutBuf,
                        char          *pKey,
                        on_evp_crypto  onEvpCryptCb) {
    uv_work_t         *puvWork = NULL;
    PCRYPT_TASK_PARAMS pTask   = NULL;

    if (!onEvpCryptCb) {
        return -ERR_INPUT_PARAMS;
    }

    if (type == CRYPTO_AES_ENCRYPT || type == CRYPTO_AES_DECRYPT) {
        if (pKey == NULL || pOutBuf == NULL) {
            return -ERR_INPUT_PARAMS;
        }

        switch (strlen(pKey) * 8) {
            case 128:
            case 192:
            case 256:
                break;
            default:
                return -ERR_EVP_KEY_SIZE;
        }
    } else if (type == CRYPTO_MD5_FILE) {
        uv_fs_t uvFs;
        if (uv_fs_access(get_task_manager(), &uvFs, (const char *)pInBuf, F_OK, NULL) != 0) {
            return -ERR_FILE_NOT_EXISTS;
        }
    }

    pTask   = (PCRYPT_TASK_PARAMS)malloc(sizeof(CRYPT_TASK_PARAMS));
    puvWork = (uv_work_t *)malloc(sizeof(uv_work_t));

    puvWork->data = (void *)pTask;

    pTask->type         = type;
    pTask->pInData      = (unsigned char *)malloc(iSize + 1);
    pTask->iInSize      = (int)iSize;
    pTask->pOutData     = pOutBuf;
    pTask->onEvpEventCb = onEvpCryptCb;

    memset(pTask->pInData, 0, iSize + 1);
    memset(pTask->pKey, 0, EVP_MAX_KEY_LENGTH + 1);

    if (pKey) {
        strncpy((char *)pTask->pKey, pKey, EVP_MAX_KEY_LENGTH);
    }

    memcpy(pTask->pInData, pInBuf, iSize);

    uv_queue_work(get_task_manager(), puvWork, onEVPWorkCb, freeEVPWorkCb);

    return 0;
}

static void evpLockCb(int mode, int n, const char *UNUSED(pFile), int UNUSED(line)) {
    if (n >= CRYPTO_num_locks()) {
        return;
    }

    if (mode & CRYPTO_LOCK) {
        uv_mutex_lock(&pEvpMutex[n]);
    } else {
        uv_mutex_unlock(&pEvpMutex[n]);
    }
}

#if OPENSSL_VERSION_NUMBER >= OPENSSL_VERSION_1_0_0
void evpIdCb(CRYPTO_THREADID *tid) {
    CRYPTO_THREADID_set_numeric(tid, (unsigned long)pthread_self());
}
#else
static unsigned long evpIdCb(void) {
    return ((unsigned long)uv_thread_self());
}
#endif

void evp_system_init(void) {
    int i;

    pEvpMutex = (uv_mutex_t *)malloc(CRYPTO_num_locks() * sizeof(uv_mutex_t));

    for (i = 0; i < CRYPTO_num_locks(); i++) {
        uv_mutex_init(&pEvpMutex[i]);
    }

#if OPENSSL_VERSION_NUMBER >= OPENSSL_VERSION_1_0_0
    CRYPTO_THREADID_set_callback(evpIdCb);
#else
    CRYPTO_set_id_callback(evpIdCb);
#endif

    CRYPTO_set_locking_callback(evpLockCb);
}