
#include <stdlib.h>
#include <stdio.h>
#include <string.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_found_secret_base64_password(
            "xeR41ZKIyEGqUw22hFxMjZYok6ABzk4RpJY4c6qYE0o=", 
            "c2FsdA==", 
            4096, 
            NULL
        );
    }
    else
    {
        return capi_scram_password_not_found(hash_prov, hash_type);
    }

}

int init_client(const char * usename, const char * password, CApiScramClient ** o_scram_client)
{
    ScramKey * scram_key = NULL;
    CApiScramRuntimeError * err = NULL;

    int32_t res = 
        capi_scram_key(NULL, 0, NULL, 0, &scram_key, &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;
    }

    CApiNonce * scram_nonce = NULL;

    res = capi_scram_nonce_none(&scram_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;
    }
    CApiScramClient * scram_client = NULL;

    // init the instance
    res = 
        capi_scram_client_init(RUST_NATIVE, "SCRAM-SHA-256", usename, NULL, password, scram_key, 
            scram_nonce, false, &scram_client, &err);

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

        printf("client init error: err code %i, err text: %s", err_code, err_text_dest);

        capi_scram_error_free(err);

        return -1;
    }    

    *o_scram_client = scram_client;

    return 0;
}

int init_server(struct AuthDBData * some_auth_data, CApiScramServer **o_server)
{
    int res = 0;
    CApiNonce * scram_nonce = NULL;
    CApiScramRuntimeError * err = NULL;

    res = capi_scram_nonce_none(&scram_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(120, 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;
    }

    *o_server = server;

    return 0;
}

int init_client_msg(CApiScramClient * scram_client, CApiScramResultClient **init_msg, const char ** init_msg_base64)
{

    int32_t res_init = capi_scram_client_init_msg(scram_client, init_msg);

    
    if (capi_scram_client_result_base64(*init_msg, init_msg_base64) != 0)
    {
        printf("init message failed");

        capi_scram_client_result_free(*init_msg);

        return -1;
    }

    return 0;
}

// 0 - ok, 1 - auth ok, -1 auth error.
int server_parse_msg(CApiScramServer *scram_server, const char * client_msg_base64,
    CApiScramRuntimeError ** out_err, CApiScramResultServer ** out_reply, 
    const char ** msg_out_base64)
{
    CApiScramRuntimeError * loc_err = NULL;
    CApiScramResultType resp_type;

    int res = 
        capi_scram_server_parse_resp_plain(scram_server,  client_msg_base64, out_reply, &loc_err);

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

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

        exit(1);
    }

    // get server resp type, to take next action
    res = capi_scram_server_result_type(*out_reply, &resp_type);
    if (res != 0)
    {
        printf("capi_scram_server_result_type() argument %d is invalid\n", -res);

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


    res = capi_scram_server_result_base64(*out_reply, msg_out_base64, out_err);

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

    switch (resp_type)
    {
        case REPLY_DATA: 

            return 0;
        break;

        case REPLY_ERROR:

            return -1;
        break;

        case REPLY_FINAL: 

            return 1;
        break;

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

            exit(1);
        break;
    }
}

// 0 - continue, 1 - completed, -1 error
int client_parse(CApiScramClient * scram_client, const char * serv_msg_base64, CApiScramRuntimeError ** out_err,
    CApiScramResultClient **client_resp_msg, const char ** client_msg)
{
     // parse "resp" from server
     int res = 
     capi_scram_client_parse_resp_base64(scram_client, (const uint8_t *) serv_msg_base64, 
         strlen(serv_msg_base64), client_resp_msg, out_err);


    if ( res < 0 )
    {
        printf("aqrgument %d is invalid\n", res);
        exit(1);
    }
    else if ( res > 0 )
    {
        return -1;
    }

    // set is final to exit
    bool is_final = capi_scram_client_result_is_final(*client_resp_msg);
    if ( is_final == true )
    {
        printf("Final MSG received, AUTH completed!\n");

        return 1;
    }

    res = capi_scram_client_result_base64(*client_resp_msg, client_msg);
    if ( res > 0 )
    {
        printf("can not obtain result\n");

        exit(1);
    }

    return 0;
}


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 * usename = "user";
    const char * password = "password";

    CApiScramClient * scram_client = NULL;

    int res = init_client(usename, password, &scram_client);

    if (res != 0)
    {
        return -1;
    }

    struct AuthDBData auth_serv_data = { 1 };
    CApiScramServer *scram_server = NULL;

    res = init_server(&auth_serv_data, &scram_server);
    if (res != 0)
    {
        return -1;
    }

    // client sends the message to server
    // create init message
    CApiScramRuntimeError *cli_err = NULL;
    CApiScramResultClient *client_resp_msg = NULL;
    const char * cliemsg_out_base64 = NULL;

    if (init_client_msg(scram_client, &client_resp_msg, &cliemsg_out_base64) != 0)
    {
        return -1;
    }

    // server receives the message
    CApiScramRuntimeError *serv_err = NULL;
    CApiScramResultServer * serv_reply = NULL;
    const char * servmsg_out_base64 = NULL;

    printf("CLIENT -> SERVER init msg\n");

    while(1)
    {
        int rep = server_parse_msg(scram_server, cliemsg_out_base64, &serv_err, &serv_reply, &servmsg_out_base64);

        printf("SERVER parsed client message\n");

        // free client init msg
        capi_scram_client_result_free(client_resp_msg);
        cliemsg_out_base64 = NULL;

        if (rep == 0)
        {
            printf("SERVER -> CLIENT reply message\n");

            rep = client_parse(scram_client, servmsg_out_base64, &cli_err, &client_resp_msg, &cliemsg_out_base64);

            capi_scram_server_result_free(serv_reply);
            servmsg_out_base64 = NULL;
        }
        else if (rep == -1)
        {
            const char * err_text_dest = capi_scram_error_get_descr(serv_err);

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

            printf("SERVER -> CLIENT error message\n");
            rep = client_parse(scram_client, servmsg_out_base64, &cli_err, &client_resp_msg, &cliemsg_out_base64);
        }
        else if (rep == 1)
        {
            rep = client_parse(scram_client, servmsg_out_base64, &cli_err, &client_resp_msg, &cliemsg_out_base64);

            capi_scram_server_result_free(serv_reply);
            servmsg_out_base64 = NULL;
        }
        

        if (rep == -1)
        {
            const char * err_text_dest = capi_scram_error_get_descr(cli_err);

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

            capi_scram_error_free(cli_err);

            return -1;
        }
        else if (rep == 0)
        {
            printf("CLIENT parsed server message\n");

            printf("CLIENT -> SERVER reply message\n");
            continue;
        }
        else 
        {
            printf("CLIENT AUTH OK!\n");

            
            capi_scram_client_free(scram_client);
            capi_scram_server_free(scram_server);
            return 0;
        }
    }

}
