#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>

#include "log.h"
#include "smart_sound.h"
#include "crypto.h"
#include "libuv_dbus.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];
    OnEVPCrypto                    onEvpEventCb;
} CRYPT_TASK_PARAMS, *PCRYPT_TASK_PARAMS;

static void FreeEVPWorkCb(uv_work_t* pWork, int 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;
    int iOutSize = 0;
    int iError = 0;

    switch(pTask->type)
    {
        case CRYPTO_AES_ENCRYPT:
            iError = EvpAESEncrypto(pTask->pInData, 
                            pTask->iInSize,
                            pTask->pOutData,
                            &iOutSize,
                            pTask->pKey);
            break;
            
        case CRYPTO_AES_DECRYPT:
            iError  = EvpAESDecrypto(pTask->pInData, 
                            pTask->iInSize,
                            pTask->pOutData,
                            &iOutSize,
                            pTask->pKey);
            break;
        
        case CRYPTO_BASE64_ENCODE:
            pTask->pOutData = (unsigned char*)EvpBase64Encode((const char*)pTask->pInData);
            iOutSize        = strlen((char*)pTask->pOutData);
            break;

        case CRYPTO_BASE64_DECODE:
            pTask->pOutData = (unsigned char*)EvpBase64Decode((const char*)pTask->pInData);
            iOutSize        = strlen((char*)pTask->pOutData);
            break;

        case CRYPTO_MD5_FILE:
            pTask->pOutData = (unsigned char*)EvpMD5HashFile((const char*)pTask->pInData);
            iOutSize        = strlen((char*)pTask->pOutData);
            break;
            
        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, iOutSize, pTask->pInData, 0);
    }
}

int EvpAddCryptoTask(CRYPTO_TYPE type,
                    unsigned char* pInBuf, 
                    int iSize, 
                    unsigned char* pOutBuf, 
                    char* pKey,
                    OnEVPCrypto onEvpCryptCb)
{
    uv_work_t*   puvWork            = NULL;
    PCRYPT_TASK_PARAMS pTask        = NULL;
    PLIBUV_DBUS_PARAMS pContext     = DBusLibuvGetRuntime();    
    
    if(!pContext || !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(pContext->pLoop, &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       = 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(pTask->pKey, pKey, EVP_MAX_KEY_LENGTH);
    }

    memcpy(pTask->pInData, pInBuf, iSize);
    
    uv_queue_work(pContext->pLoop, puvWork, OnEVPWorkCb, FreeEVPWorkCb);

    return 0;
}

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

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


static unsigned long __evpIdCb(void) 
{
    return ((unsigned long)uv_thread_self());
}

void EvpSystemInit(void)
{
    int i = 0;

    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]);
    }

    CRYPTO_set_id_callback(__evpIdCb);
    CRYPTO_set_locking_callback(__evpLockCb);
}
