#include <stdio.h>
#include <openssl/md5.h>

#include "psAssert.h"
#include "psMemory.h"
#include "psString.h"
#include "psVector.h"
#include "psImage.h"
#include "psError.h"
#include "psMD5.h"


psVector *psStringMD5(const char *string)
{
    psVector *hash = psVectorAlloc(MD5_DIGEST_LENGTH, PS_TYPE_U8); // The resultant MD5 hash
    if (!MD5((const unsigned char *)string, strlen(string), &hash->data.U8[0])) {
        psError(PS_ERR_UNEXPECTED_NULL, true, "Unable to generate md5 hash.\n");
        psFree(hash);
        return NULL;
    }
    return hash;
}

psVector *psVectorMD5(const psVector *vector)
{
    PS_ASSERT_VECTOR_NON_NULL(vector, NULL);

    psVector *hash = psVectorAlloc(MD5_DIGEST_LENGTH, PS_TYPE_U8); // The resultant MD5 hash
    if (!MD5(vector->data.U8, vector->n * PSELEMTYPE_SIZEOF(vector->type.type), &hash->data.U8[0])) {
        psError(PS_ERR_UNEXPECTED_NULL, true, "Unable to generate md5 hash.\n");
        psFree(hash);
        return NULL;
    }
    return hash;
}


psVector *psImageMD5(const psImage *image)
{
    PS_ASSERT_IMAGE_NON_NULL(image, NULL);

    psVector *hash = psVectorAlloc(MD5_DIGEST_LENGTH, PS_TYPE_U8); // The resultant MD5 hash
    if (!image->parent) {
        // No parent means image is contiguous
        if (!MD5(&image->data.U8[0][0], image->numCols * image->numRows * PSELEMTYPE_SIZEOF(image->type.type),
                 &hash->data.U8[0])) {
            psError(PS_ERR_UNEXPECTED_NULL, true, "Unable to generate md5 hash.\n");
            psFree(hash);
            return NULL;
        }
    } else {
        MD5_CTX ctx;                    // MD5 calculator
        MD5_Init(&ctx);
        for (int row = 0; row < image->numRows; row++) {
            MD5_Update(&ctx, image->data.U8[row], image->numCols * PSELEMTYPE_SIZEOF(image->type.type));
        }
        MD5_Final(&hash->data.U8[0], &ctx);
    }

    return hash;
}


psString psMD5toString(const psVector *hash)
{
    PS_ASSERT_VECTOR_NON_NULL(hash, NULL);
    PS_ASSERT_VECTOR_SIZE(hash, (long int)MD5_DIGEST_LENGTH, NULL);
    PS_ASSERT_VECTOR_TYPE(hash, PS_TYPE_U8, NULL);

    psString string = psStringAlloc(MD5_DIGEST_LENGTH * 2 + 1); // String to return
    for (int i = 0; i < MD5_DIGEST_LENGTH; i++) {
        sprintf(string + i * 2, "%02x", hash->data.U8[i]);
    }
    return string;
}
