#ifndef HEADER_H_INCLUDED
#define HEADER_H_INCLUDED
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
typedef struct hashentry__s
{
    char songtitle[256], interpreter[256];
} hashentry_t;

typedef struct hasharray__s
{
    hashentry_t *entries;
    long num_entries;
} hasharray_t;

typedef struct hashcontainer__s
{
    hasharray_t *harrays;
    long hashsize;
} hashcontainer_t;

long hash_key(char songtitle[], char interpreter[], long hash_max);
hashcontainer_t *create_hash(long hashsize);
void delete_hash(hashcontainer_t *hashcontainer);
void insert_entry(hashcontainer_t *hashcontainer, char songtitle[], char interpreter[]);
hashentry_t *search_entry(hashcontainer_t *hashcontainer, char songtitle[], char interpreter[]);
void delete_entry(hashcontainer_t *hashcontainer, char songtitle[], char interpreter[]);
void print_hash(hashcontainer_t *hashcontainer);
long count_entries(hashcontainer_t *hashcontainer);
long count_entries_with_songtitle(hashcontainer_t *hashcontainer, char songtitle[]);
void insert_entry_ext_exp(hashcontainer_t *hashcontainer, char songtitle[], char interpreter[], long(*hashkey_gen)(hashentry_t *entry, long hash_max));
void insert_entry_ext(hashcontainer_t *hashcontainer, hashentry_t *hashentry, long(*hashkey_gen)(hashentry_t *entry, long hash_max));
long hashkey_gen1(hashentry_t *entry, long hash_max);
long hashkey_gen2(hashentry_t *entry, long hash_max);
void rehash1(hashcontainer_t *hashcontainer, long(*hashkey_gen)(hashentry_t *entry, long hash_max), long hash_max);
void rehash2(hashcontainer_t *hashcontainer, long(*hashkey_gen)(hashentry_t *entry, long hash_max), long hash_max);

#endif // HEADER_H_INCLUDED

#include "header.h"

long hash_key(char songtitle[], char interpreter[], long hash_max)
{
    unsigned long i, index = 0;
    for (i = 0; i < strlen(songtitle); ++i)
        index = 64 * index + (long)(songtitle[i]);
    for (i = 0; i < strlen(interpreter); ++i)
        index = 64 * index + (long)(interpreter[i]);
    return index % hash_max;
}

long hashkey_gen1(hashentry_t *entry, long hash_max)
{
    unsigned long i, index = 0;
    for (i = 0; i < strlen(entry->songtitle); ++i)
        index = 64 * index + (long)(entry->songtitle[i]);
    for (i = 0; i < strlen(entry->interpreter); ++i)
        index = 64 * index + (long)(entry->interpreter[i]);
    return index % hash_max;
}

hashcontainer_t *create_hash(long hashsize)
{
    unsigned long i;
    hashcontainer_t *new = 0;
    if (!(new = malloc(sizeof(hashcontainer_t))))
    {
        fprintf(stderr, "Memory allocation error in create_hash()");
        exit(-1);
    }

    if (!(new->harrays = calloc(hashsize, sizeof(hasharray_t))))
    {
        fprintf(stderr, "Memory allocation error in create_hash()");
        exit(-1);
    }
    new->hashsize = hashsize;
    for (i = 0; i < hashsize; i++)
    {
        new->harrays[i];
    }
    return new;
}

void delete_hash(hashcontainer_t *hashcontainer)
{
    unsigned long i;
    for (i = 0; i < hashcontainer->hashsize; i++)
    {
        if (hashcontainer->harrays[i].entries)
        {
            free(hashcontainer->harrays[i].entries);
            hashcontainer->harrays[i].entries = 0;
            hashcontainer->harrays[i].num_entries = 0;
        }

    }

    free(hashcontainer->harrays);
    hashcontainer->harrays = 0;
    hashcontainer->hashsize = 0;
}


void insert_entry(hashcontainer_t *hashcontainer, char songtitle[], char interpreter[])
{
    unsigned long index = hash_key(songtitle, interpreter, hashcontainer->hashsize);
    hashcontainer->harrays[index].num_entries++;
    hashcontainer->harrays[index].entries = realloc(hashcontainer->harrays[index].entries, hashcontainer->harrays[index].num_entries * sizeof(hashentry_t));
    strcpy(hashcontainer->harrays[index].entries[hashcontainer->harrays[index].num_entries - 1].songtitle, songtitle);
    strcpy(hashcontainer->harrays[index].entries[hashcontainer->harrays[index].num_entries - 1].interpreter, interpreter);
}

hashentry_t *search_entry(hashcontainer_t *hashcontainer, char songtitle[], char interpreter[])
{
    unsigned long index = hash_key(songtitle, interpreter, hashcontainer->hashsize);
    unsigned long len = hashcontainer->harrays[index].num_entries, i;
    for (i = 0; i < len; i++)
    {
        if ((strcmp(hashcontainer->harrays[index].entries[i].interpreter, interpreter) == 0) && (strcmp(hashcontainer->harrays[index].entries[i].songtitle, songtitle) == 0))
            return (hashcontainer->harrays[index].entries)+i;
    }
    return 0;
}

void delete_entry(hashcontainer_t *hashcontainer, char songtitle[], char interpreter[])
{
    unsigned long index = hash_key(songtitle, interpreter, hashcontainer->hashsize);
    unsigned long len = hashcontainer->harrays[index].num_entries, i;
    for (i = 0; i < len; i++)
    {
        if ((strcmp(hashcontainer->harrays[index].entries[i].interpreter, interpreter) == 0) && (strcmp(hashcontainer->harrays[index].entries[i].songtitle, songtitle) == 0))
        {
            if (i == 0 && len == 1)
            {
                free(hashcontainer->harrays[index].entries);
                hashcontainer->harrays[index].entries = 0;
                hashcontainer->harrays[index].num_entries = 0;
            }
            else
            {
                if (i != len - 1)
                    memcpy(hashcontainer->harrays[index].entries + i, hashcontainer->harrays[index].entries + i + 1, (len - i - 1)* sizeof(hashentry_t));
                hashcontainer->harrays[index].entries = realloc(hashcontainer->harrays[index].entries, --hashcontainer->harrays[index].num_entries * sizeof(hashentry_t));
            }
        }
    }
}


void print_hash(hashcontainer_t *hashcontainer)
{
    unsigned long i, j;
    for (i = 0; i < hashcontainer->hashsize; i++)
    {
        fprintf(stdout, "Hash index %ld\n", i);
        for (j = 0; j < hashcontainer->harrays[i].num_entries; j++)
        {
            fprintf(stdout, "%s\t%s\n", hashcontainer->harrays[i].entries[j].songtitle, hashcontainer->harrays[i].entries[j].interpreter);
        }
        fprintf(stdout, "\n");
    }
}

long count_entries(hashcontainer_t *hashcontainer)
{
    unsigned long i, j, count = 0;
    for (i = 0; i < hashcontainer->hashsize; i++)
    {
        for (j = 0; j < hashcontainer->harrays[i].num_entries; j++)
        {
                count++;
        }
    }
    return count;
}

long count_entries_with_songtitle(hashcontainer_t *hashcontainer, char songtitle[])
{
    unsigned long i, j, count = 0;
    for (i = 0; i < hashcontainer->hashsize; i++)
    {
        for (j = 0; j < hashcontainer->harrays[i].num_entries; j++)
        {
            if (strcmp(hashcontainer->harrays[i].entries[j].songtitle, songtitle) == 0)
                count++;
        }
    }
    return count;
}

void insert_entry_ext_exp(hashcontainer_t *hashcontainer, char songtitle[], char interpreter[], long(*hashkey_gen)(hashentry_t *entry, long hash_max))
{
    hashentry_t *new = malloc(sizeof(hashentry_t));
    strcpy(new->songtitle, songtitle);
    strcpy(new->interpreter, interpreter);
    unsigned long index = hashkey_gen(new, hashcontainer->hashsize);
    hashcontainer->harrays[index].num_entries++;
    hashcontainer->harrays[index].entries = realloc(hashcontainer->harrays[index].entries, hashcontainer->harrays[index].num_entries * sizeof(hashentry_t));
    memcpy(hashcontainer->harrays[index].entries + hashcontainer->harrays[index].num_entries - 1, new, sizeof(hashentry_t));
}

void insert_entry_ext(hashcontainer_t *hashcontainer, hashentry_t *new, long(*hashkey_gen)(hashentry_t *entry, long hash_max))
{
    unsigned long index = hashkey_gen(new, hashcontainer->hashsize);
    hashcontainer->harrays[index].num_entries++;
    hashcontainer->harrays[index].entries = realloc(hashcontainer->harrays[index].entries, hashcontainer->harrays[index].num_entries * sizeof(hashentry_t));
    memcpy(hashcontainer->harrays[index].entries + hashcontainer->harrays[index].num_entries - 1, new, sizeof(hashentry_t));
}

hashentry_t *search_entry_ext(hashcontainer_t *hashcontainer, char songtitle[], char interpreter[], long(*hashkey_gen)(hashentry_t *entry, long hash_max))
{
    hashentry_t *new = malloc(sizeof(hashentry_t));
    strcpy(new->songtitle, songtitle);
    strcpy(new->interpreter, interpreter);
    unsigned long index = hashkey_gen(new, hashcontainer->hashsize);
    unsigned long i;
    for (i = 0; i < hashcontainer->harrays[index].num_entries; i++)
    {
        if ((strcmp(hashcontainer->harrays[index].entries[i].interpreter, interpreter) == 0) && (strcmp(hashcontainer->harrays[index].entries[i].songtitle, songtitle) == 0))
       return (hashcontainer->harrays[index].entries)+i;
    }
    return 0;
}

void rehash1(hashcontainer_t *hashcontainer, long(*hashkey_gen)(hashentry_t *entry, long hash_max), long hash_max)
{
    hashcontainer_t *new = create_hash(hash_max);
    unsigned long i, j;
    for (i = 0; i < hashcontainer->hashsize; i++)
    {
        for (j = 0; j < hashcontainer->harrays[i].num_entries; j++)
        {
            insert_entry_ext(new, hashcontainer->harrays[i].entries + j, hashkey_gen);
        }
    }
    delete_hash(hashcontainer);
    hashcontainer->harrays = new->harrays;
    hashcontainer->hashsize = new->hashsize;
}


void rehash2(hashcontainer_t *hashcontainer, long(*hashkey_gen)(hashentry_t *entry, long hash_max), long hash_max)
{
    unsigned long i, j, count = 0, len = count_entries(hashcontainer);
    hashentry_t temp[len];
    for (i = 0; i < hashcontainer->hashsize; i++)
    {
        for (j = 0; j < hashcontainer->harrays[i].num_entries; j++)
        {
            strcpy(temp[count].songtitle, hashcontainer->harrays[i].entries[j].songtitle);
            strcpy(temp[count].interpreter, hashcontainer->harrays[i].entries[j].interpreter);
            count++;
        }
    }
    delete_hash(hashcontainer);
    hashcontainer_t *new = create_hash(hash_max);
    for (i = 0; i < len; i++)
    {
        insert_entry_ext(new, &temp[i], hashkey_gen);
    }
    hashcontainer->harrays = new->harrays;
    hashcontainer->hashsize = new->hashsize;
    free(new);
}

#include "source.c"

int main()
{
    hashcontainer_t *container = create_hash(3);
    insert_entry_ext_exp(container, "Title1", "Inter1", hashkey_gen1);
    insert_entry_ext_exp(container, "Title2", "Inter2", hashkey_gen1);
    insert_entry_ext_exp(container, "Title2", "Inter2", hashkey_gen1);
    insert_entry_ext_exp(container, "Title3", "Inter3", hashkey_gen1);
    insert_entry_ext_exp(container, "Title3", "Inter3", hashkey_gen1);
    insert_entry_ext_exp(container, "Title3", "Inter3", hashkey_gen1);
    insert_entry_ext_exp(container, "Title3", "Inter3", hashkey_gen1);
    insert_entry_ext_exp(container, "Title4", "Inter4", hashkey_gen1);
    insert_entry_ext_exp(container, "Title7", "Inter7", hashkey_gen1);
    insert_entry_ext_exp(container, "Title7", "Inter7", hashkey_gen1);
    delete_entry(container, "Title7", "Inter7");

    print_hash(container);
    fprintf(stdout, "\nRehashing...\n\n");
    rehash1(container, hashkey_gen1, 11);
    print_hash(container);
    hashentry_t *temp = search_entry_ext(container, "Title7", "Inter7", hashkey_gen1);
    if (temp)
        fprintf(stdout, "%s\t%s\n", temp->songtitle, temp->interpreter);
    else
        fprintf(stdout, "Not found.\n");
    fprintf(stdout, "%ld entries found.", count_entries(container));
    delete_hash(container);
    free(container);
    return 0;
}