#include <stdlib.h>
#include <stdio.h>
#include <limits.h>
#include <string.h>
#include <time.h>

struct session_s {
    char *session_id;
    char *user;
    struct session_s *next;
};

typedef struct session_s session_t;

struct hashtable_s {
    int size;
    struct session_s **table;
};

typedef struct hashtable_s hashtable_t;

static hashtable_t *ht_create( int size );
static int ht_hash( hashtable_t *hashtable, char *session_id );
static session_t *ht_newpair( char *session_id, char *user );
static char *ht_get( hashtable_t *hashtable, char *session_id );
static void ht_set( hashtable_t *hashtable, char *session_id, char *user );
static void ht_delete (hashtable_t *hashtable, char *key);
static char *gen_session_id(int len);
static char *set_cookie_header( const char *champ, const char separator,
                                const char *session_id);


/* Create a new hashtable. */
hashtable_t *ht_create( int size ) {
    hashtable_t *hashtable = NULL;
    int i;

    if( size < 1 ) return NULL;

    /* Allocate the table itself. */
    if( ( hashtable = malloc( sizeof( hashtable_t ) ) ) == NULL ) {
        return NULL;
    }

    /* Allocate pointers to the head nodes. */
    if( ( hashtable->table = malloc( sizeof( session_t * ) * size ) ) == NULL ) {
        return NULL;
    }
    for( i = 0; i < size; i++ ) {
        hashtable->table[i] = NULL;
    }

    hashtable->size = size;

    return hashtable;
}

/* Hash a string for a particular hash table. */
int ht_hash( hashtable_t *hashtable, char *session_id ) {

    unsigned long int hashval = 0;
    int i = 0;

    /* Convert our string to an integer */
    while( hashval < ULONG_MAX && i < strlen( session_id ) ) {
        hashval = hashval << 8;
        hashval += session_id[ i ];
        i++;
    }

    return hashval % hashtable->size;
}

/* Create a key-value pair. */
session_t *ht_newpair( char *session_id, char *user ) {
    session_t *newpair;

    if( ( newpair = malloc( sizeof( session_t ) ) ) == NULL ) {
        return NULL;
    }

    if( ( newpair->session_id = strdup( session_id ) ) == NULL ) {
        return NULL;
    }

    if( ( newpair->user = strdup( user ) ) == NULL ) {
        return NULL;
    }

    newpair->next = NULL;

    return newpair;
}

/* Insert a key-value pair into a hash table. */
static void ht_set( hashtable_t *hashtable, char *session_id, char *user ) {
    int bin = 0;
    session_t *newpair = NULL;
    session_t *next = NULL;
    session_t *last = NULL;

    bin = ht_hash( hashtable, session_id );

    next = hashtable->table[ bin ];

    while( next != NULL && next->session_id != NULL && strcmp( session_id, next->session_id ) > 0 ) {
        last = next;
        next = next->next;
    }

    /* There's already a pair.  Let's replace that string. */
    if( next != NULL && next->session_id != NULL && strcmp( session_id, next->session_id ) == 0 ) {

        free( next->user );
        next->user = strdup( user );

        /* Nope, could't find it.  Time to grow a pair. */
    } else {
        newpair = ht_newpair( session_id, user );

        /* We're at the start of the linked list in this bin. */
        if( next == hashtable->table[ bin ] ) {
            newpair->next = next;
            hashtable->table[ bin ] = newpair;

            /* We're at the end of the linked list in this bin. */
        } else if ( next == NULL ) {
            last->next = newpair;

            /* We're in the middle of the list. */
        } else  {
            newpair->next = next;
            last->next = newpair;
        }
    }
}

/* Retrieve a key-value pair from a hash table. */
static char *ht_get( hashtable_t *hashtable, char *session_id ) {
    int bin = 0;
    session_t *pair;

    bin = ht_hash( hashtable, session_id );

    /* Step through the bin, looking for our value. */
    pair = hashtable->table[ bin ];
    while( pair != NULL && pair->session_id != NULL && strcmp( session_id, pair->session_id ) > 0 ) {
        pair = pair->next;
    }

    /* Did we actually find anything? */
    if( pair == NULL || pair->session_id == NULL || strcmp( session_id, pair->session_id ) != 0 ) {
        return NULL;

    } else {
        return pair->user;
    }

}


void ht_delete (hashtable_t *hashtable, char *key){

    int bin = 0;
    session_t *pair;
    session_t *prec;

    bin = ht_hash( hashtable, key );
    /* Step through the bin, looking for our value. */
    pair = hashtable->table[ bin ];
    while( pair != NULL && pair->session_id != NULL && strcmp( key, pair->session_id ) > 0 ) {
        prec = pair;
        pair = pair->next;
    }

    /* Did we actually find anything? */
    if( pair != NULL || pair->session_id != NULL || strcmp( key, pair->session_id ) == 0 ) {
        free(pair->session_id);
        free(pair->user);
    }
}

char *gen_session_id(int len){

    int index = 0;
    char session_id[len];
    char char1[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789/,.-+=~`<>:";
    int c_tmp = 0;
    char *res;
    srand((unsigned int)(time(NULL)));

    for(index = 0; index < len; index++)
    {
        c_tmp = rand() % sizeof(char1) - 1;
        session_id[index] = char1[c_tmp];
    }
    session_id[len] = '\0';
    res = strdup(session_id);

    return res;
}

char *set_cookie_header( const char *champ, const char separator,
                         const char *session_id) {
    int lch, lse;
    int ls = 0;
    char *result;
    lch = strlen(champ);
    lse = strlen(session_id);
    if (separator)
        ls = 1;
    if ( (result = calloc(lch + ls + lse + 1, sizeof *result)) == NULL )
        return NULL;

    memcpy (result, champ, lch);
    if (separator)
        result[lch] = separator;

    memcpy (result + lch + ls, session_id, lse + 1 );

    return result;
}
