#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "../../include/scramrs.h"

struct AuthDBData
{
    int data;
};

ScramResult* password_for_user_callback(void *auth_db_inst, 
    CApiScramHasherProvider hash_prov, CApiScramTypeAlias hash_type, const char * username, 
        const char * auzthzid)
{
    struct AuthDBData * auth_inst = auth_db_inst;

    printf("password_for_user_callback() auth_inst->data = %d\n", auth_inst->data);

    if ( strcmp(username, "user") == 0 )
    {
        const char * password_plain = "pencil";
        const char * salt_plain = "[m\x99h\x9d\x12""5\x8e\xec\xa0K\x14\x12""6\xfa\x81\0";
        uint32_t iters = 4096;

        return capi_scram_password_with_params(
            hash_prov,
            hash_type,
            (const uint8_t *) password_plain,
            strlen(password_plain),
            (const uint8_t *) salt_plain,
            strlen(salt_plain),
            &iters,
            NULL
        );
        /*return capi_scram_password_found_secret_base64_password(
            "xKSVEDI6tPlSysH6mUQZOeeOp01r6B3fcJbodRPcYV0=", 
            "W22ZaJ0SNY7soEsUEjb6gQ==", 
            4096, 
            NULL
        );*/
    }
    else
    {
        return capi_scram_password_not_found(hash_prov, hash_type);
    }

}

void * globall_malloc_f(size_t sz, size_t align)
{
    return malloc((unsigned long)sz);
}

void * globall_malloc_zr_f(size_t sz, size_t align)
{
    return calloc(1, (unsigned long) sz);
}


void globall_dealloc_f(void * ptr)
{
    free(ptr);
}

void* globall_realloc(void * ptr, size_t new_size)
{
    return realloc(ptr, (unsigned long) new_size);
}

int main(void)
{
    #ifdef CAPI_SCRAM_NOSTD
        set_allocator(globall_malloc_f, globall_malloc_zr_f, globall_dealloc_f, globall_realloc);
    #endif

    const char *client_nonce = "rOprNGfwEbeRWgbNEkqO";
    const char *client_init = "n,,n=user,r=rOprNGfwEbeRWgbNEkqO";
    const char *server_init = "r=rOprNGfwEbeRWgbNEkqO%hvYDpWUa2RaTCAfuxFIlj)hNlF$k0,s=W22ZaJ0SNY7soEsUEjb6gQ==,i=4096";
    const char *server_nonce = "%hvYDpWUa2RaTCAfuxFIlj)hNlF$k0";
    const char *client_final = "c=biws,r=rOprNGfwEbeRWgbNEkqO%hvYDpWUa2RaTCAfuxFIlj)hNlF$k0,p=dHzbZapWIk4jUhN+Ute9ytag9zjfMHgsqmmiz7AndVQ=";
    const char *server_final = "v=6rriTRBi23WpRR/wtup+mMhUZUn/dB5nLTJRsjl95G4=";

    int32_t res = 0;
    CApiNonce * scram_nonce = NULL;
    CApiScramRuntimeError * err = NULL;

    struct AuthDBData some_auth_data = {1};

    res = capi_scram_nonce_base64(&scram_nonce, server_nonce, &err);
    
    if (res > 0)
    {
        const char * err_text_dest = capi_scram_error_get_descr(err);

        printf("nonce err text: %s", err_text_dest);

        capi_scram_error_free(err);

        return -1;
    }
    else if (res < 0)
    {
        printf("argument %d is invalid", -res);

        return -1;
    }

    CApiScramServer *server = NULL;

    // server instance
    res =
        capi_scram_server_init(112, RUST_NATIVE, scram_nonce, (void *)&some_auth_data, 
            password_for_user_callback, "SCRAM-SHA-256", false, &server, &err);

    if ( res < 0 )
    {
        printf("capi_scram_server_init() argument %d is invalid", res);
        return -1;
    }
    else if ( res > 0 )
    {
        const char * err_text_dest = capi_scram_error_get_descr(err);

        printf("response err text: %s", err_text_dest);

        capi_scram_error_free(err);

        return -1;
    }

    const char * client_msg = client_init;
    const char * server_msg = server_init;

    while (1)
    {
        CApiScramResultServer * reply = NULL;

        res = capi_scram_server_parse_resp_plain(server, client_msg, &reply, &err);
        
        if ( res < 0 )
        {
            printf("capi_scram_server_result_consume() argument %d is invalid", -res);
            return -1;
        }
        else if ( res > 0 )
        {
            const char * err_text_dest = capi_scram_error_get_descr(err);

            printf("response err text: %s", err_text_dest);

            capi_scram_error_free(err);

            return -1;
        }

        CApiScramResultType resp_type;

        res = capi_scram_server_result_type(reply, &resp_type);
        if (res != 0)
        {
            printf("capi_scram_server_result_type() argument %d is invalid\n", -res);

            return -1;
        }
        printf("reply type %d\n", resp_type);

        // send data back to client todo

        const char * msg_out_plain = NULL;

        res = capi_scram_server_result_plain(reply, &msg_out_plain, &err);

        if ( res != 0 )
        {
            printf("capi_scram_server_result_plain() argument %d is invalid\n", -res);
            return -1;
        }



        switch (resp_type)
        {
            case REPLY_DATA: 
                if (strcmp(server_msg, msg_out_plain) == 0)
                {
                    printf("[MATCH]sending message to client \ngener:%s\nconst:%s\n", msg_out_plain, server_msg);
                }
                else
                {
                    printf("[UNMATCH]sending message to client \n%s\n%s\n", msg_out_plain, server_msg);
                    return -1;
                }

                capi_scram_server_result_free(reply);

                client_msg = client_final;
                server_msg = server_final;
            break;

            case REPLY_ERROR:
                { 
                    // error happened!
                    const char * err_text_dest = capi_scram_error_get_descr(err);

                    printf("response err text: %s", err_text_dest);

                    capi_scram_error_free(err);

                    return -1;
                }
            break;

            case REPLY_FINAL: 
                {
                    const char * username = NULL;

                    if (strcmp(server_msg, msg_out_plain) == 0)
                    {
                        printf("[MATCH] final \ngener:%s\nconst:%s\n", msg_out_plain, server_msg);
                    }
                    else
                    {
                        printf("[UNMATCH]final \n%s\n%s\n", msg_out_plain, server_msg);
                        return -1;
                    }

                    res = capi_scram_server_get_username(server, &username);

                    if ( res < 0 )
                    {
                        printf("capi_scram_server_get_username() argument %d is invalid\n", -res);
                    }
                    else if ( res == 0 )
                    {
                        // final message, 
                        printf("AUTH successfull for user: %s!\n", username);
                    }
                    else
                    {
                        printf("capi_scram_server_get_username() username not availabe, but should!\n");
                        return -1;
                    }

                    // deallcoate reply
                    capi_scram_server_result_free(reply);

                    capi_scram_server_free(server);
                    return 0;
                }   
            break;

            default:
                printf("unknown OP %d\n", resp_type);

                return -1;
            break;
        }

        
    }

    
}