#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <pcem/logging.h>
#include "config.h"

char config_file_default[256];
char config_name[256];
config_callback_t config_callbacks[CALLBACK_MAX];
int num_config_callbacks = 0;
static char config_file[256];

typedef struct list_t {
        struct list_t *next;
} list_t;

static list_t global_config_head;
static list_t machine_config_head;

typedef struct section_t {
        struct list_t list;

        char name[256];

        struct list_t entry_head;
} section_t;

typedef struct entry_t {
        struct list_t list;

        char name[256];
        char data[256];
} entry_t;

#define list_add(new, head)                                                                                                      \
        {                                                                                                                        \
                struct list_t *next = head;                                                                                      \
                                                                                                                                 \
                while (next->next)                                                                                               \
                        next = next->next;                                                                                       \
                                                                                                                                 \
                (next)->next = new;                                                                                              \
                (new)->next = NULL;                                                                                              \
        }

void config_dump(int is_global) {
        section_t *current_section;
        list_t *head = is_global ? &global_config_head : &machine_config_head;

        pclog("Config data :\n");

        current_section = (section_t *)head->next;

        while (current_section) {
                entry_t *current_entry;

                pclog("[%s]\n", current_section->name);

                current_entry = (entry_t *)current_section->entry_head.next;

                while (current_entry) {
                        pclog("%s = %s\n", current_entry->name, current_entry->data);

                        current_entry = (entry_t *)current_entry->list.next;
                }

                current_section = (section_t *)current_section->list.next;
        }
}

void config_free(int is_global) {
        section_t *current_section;
        list_t *head = is_global ? &global_config_head : &machine_config_head;
        current_section = (section_t *)head->next;

        while (current_section) {
                section_t *next_section = (section_t *)current_section->list.next;
                entry_t *current_entry;

                current_entry = (entry_t *)current_section->entry_head.next;

                while (current_entry) {
                        entry_t *next_entry = (entry_t *)current_entry->list.next;

                        free(current_entry);
                        current_entry = next_entry;
                }

                free(current_section);
                current_section = next_section;
        }
}

int config_free_section(int is_global, char *name) {
        section_t *current_section, *prev_section;
        list_t *head = is_global ? &global_config_head : &machine_config_head;
        current_section = (section_t *)head->next;
        prev_section = 0;

        while (current_section) {
                section_t *next_section = (section_t *)current_section->list.next;
                if (!strcmp(current_section->name, name)) {
                        entry_t *current_entry;

                        current_entry = (entry_t *)current_section->entry_head.next;

                        while (current_entry) {
                                entry_t *next_entry = (entry_t *)current_entry->list.next;

                                free(current_entry);
                                current_entry = next_entry;
                        }

                        free(current_section);
                        if (!prev_section)
                                head->next = (list_t *)next_section;
                        else
                                prev_section->list.next = (list_t *)next_section;
                        return 1;
                }
                prev_section = current_section;
                current_section = next_section;
        }
        return 0;
}

void config_load(int is_global, char *fn) {
        FILE *f = fopen(fn, "rt");
        section_t *current_section;
        list_t *head = is_global ? &global_config_head : &machine_config_head;

        memset(head, 0, sizeof(list_t));

        current_section = malloc(sizeof(section_t));
        memset(current_section, 0, sizeof(section_t));
        list_add(&current_section->list, head);

        if (!f)
                return;

        while (1) {
                int c;
                char buffer[256];

                fgets(buffer, 255, f);
                if (feof(f))
                        break;

                c = 0;

                while (buffer[c] == ' ' && buffer[c])
                        c++;

                if (!buffer[c])
                        continue;

                if (buffer[c] == '#') /*Comment*/
                        continue;

                if (buffer[c] == '[') /*Section*/
                {
                        section_t *new_section;
                        char name[256];
                        int d = 0;

                        c++;
                        while (buffer[c] != ']' && buffer[c])
                                name[d++] = buffer[c++];

                        if (buffer[c] != ']')
                                continue;
                        name[d] = 0;

                        new_section = malloc(sizeof(section_t));
                        memset(new_section, 0, sizeof(section_t));
                        strncpy(new_section->name, name, 256);
                        list_add(&new_section->list, head);

                        current_section = new_section;

                        //                        pclog("New section : %s %p\n", name, (void *)current_section);
                } else {
                        entry_t *new_entry;
                        char name[256];
                        int d = 0, data_pos;

                        while (buffer[c] != '=' && buffer[c] != ' ' && buffer[c])
                                name[d++] = buffer[c++];

                        if (!buffer[c])
                                continue;
                        name[d] = 0;

                        while ((buffer[c] == '=' || buffer[c] == ' ') && buffer[c])
                                c++;

                        if (!buffer[c])
                                continue;

                        data_pos = c;
                        while (buffer[c]) {
                                if (buffer[c] == '\n' || buffer[c] == '\r')
                                        buffer[c] = 0;
                                c++;
                        }

                        new_entry = malloc(sizeof(entry_t));
                        memset(new_entry, 0, sizeof(entry_t));
                        strncpy(new_entry->name, name, 256);
                        strncpy(new_entry->data, &buffer[data_pos], 256);
                        list_add(&new_entry->list, &current_section->entry_head);

                        //                        pclog("New data under section [%s] : %s = %s\n", current_section->name,
                        //                        new_entry->name, new_entry->data);
                }
        }

        fclose(f);
}

void config_new() {
        FILE *f = fopen(config_file, "wt");
        fclose(f);
}

static section_t *find_section(char *name, int is_global) {
        section_t *current_section;
        char blank[256] = "";
        list_t *head = is_global ? &global_config_head : &machine_config_head;

        current_section = (section_t *)head->next;
        if (!name)
                name = blank;

        while (current_section) {
                if (!strncmp(current_section->name, name, 256))
                        return current_section;

                current_section = (section_t *)current_section->list.next;
        }
        return NULL;
}

static entry_t *find_entry(section_t *section, char *name) {
        entry_t *current_entry;

        current_entry = (entry_t *)section->entry_head.next;

        while (current_entry) {
                if (!strncmp(current_entry->name, name, 256))
                        return current_entry;

                current_entry = (entry_t *)current_entry->list.next;
        }
        return NULL;
}

static section_t *create_section(char *name, int is_global) {
        section_t *new_section = malloc(sizeof(section_t));
        list_t *head = is_global ? &global_config_head : &machine_config_head;

        memset(new_section, 0, sizeof(section_t));
        strncpy(new_section->name, name, 256);
        list_add(&new_section->list, head);

        return new_section;
}

static entry_t *create_entry(section_t *section, char *name) {
        entry_t *new_entry = malloc(sizeof(entry_t));
        memset(new_entry, 0, sizeof(entry_t));
        strncpy(new_entry->name, name, 256);
        list_add(&new_entry->list, &section->entry_head);

        return new_entry;
}

int config_get_int(int is_global, char *head, char *name, int def) {
        section_t *section;
        entry_t *entry;
        int value;

        section = find_section(head, is_global);

        if (!section)
                return def;

        entry = find_entry(section, name);

        if (!entry)
                return def;

        sscanf(entry->data, "%i", &value);

        return value;
}

float config_get_float(int is_global, char *head, char *name, float def) {
        section_t *section;
        entry_t *entry;
        float value;

        section = find_section(head, is_global);

        if (!section)
                return def;

        entry = find_entry(section, name);

        if (!entry)
                return def;

        sscanf(entry->data, "%f", &value);

        return value;
}

char *config_get_string(int is_global, char *head, char *name, char *def) {
        section_t *section;
        entry_t *entry;

        section = find_section(head, is_global);

        if (!section)
                return def;

        entry = find_entry(section, name);

        if (!entry)
                return def;

        return entry->data;
}

void config_set_int(int is_global, char *head, char *name, int val) {
        section_t *section;
        entry_t *entry;

        section = find_section(head, is_global);

        if (!section)
                section = create_section(head, is_global);

        entry = find_entry(section, name);

        if (!entry)
                entry = create_entry(section, name);

        sprintf(entry->data, "%i", val);
}

void config_set_float(int is_global, char *head, char *name, float val) {
        section_t *section;
        entry_t *entry;

        section = find_section(head, is_global);

        if (!section)
                section = create_section(head, is_global);

        entry = find_entry(section, name);

        if (!entry)
                entry = create_entry(section, name);

        sprintf(entry->data, "%f", val);
}

void config_set_string(int is_global, char *head, char *name, char *val) {
        section_t *section;
        entry_t *entry;

        section = find_section(head, is_global);

        if (!section)
                section = create_section(head, is_global);

        entry = find_entry(section, name);

        if (!entry)
                entry = create_entry(section, name);

        strncpy(entry->data, val, 256);
}

char *get_filename(char *s) {
        int c = strlen(s) - 1;
        while (c > 0) {
                if (s[c] == '/' || s[c] == '\\')
                        return &s[c + 1];
                c--;
        }
        return s;
}

void append_filename(char *dest, char *s1, char *s2, int size) { sprintf(dest, "%s%s", s1, s2); }

void append_slash(char *s, int size) {
        int c = strlen(s) - 1;
        if (s[c] != '/' && s[c] != '\\') {
                if (c < size - 2)
                        strcat(s, "/");
                else
                        s[c] = '/';
        }
}

void put_backslash(char *s) {
        int c = strlen(s) - 1;
        if (s[c] != '/' && s[c] != '\\') {
                s[c + 1] = '/';
                s[c + 2] = 0;
        }
}

char *get_extension(char *s) {
        int c = strlen(s) - 1;

        if (c <= 0)
                return s;

        while (c && s[c] != '.')
                c--;

        if (!c)
                return &s[strlen(s)];

        return &s[c + 1];
}

void config_save(int is_global, char *fn) {
        FILE *f = fopen(fn, "wt");
        section_t *current_section;
        list_t *head = is_global ? &global_config_head : &machine_config_head;

        current_section = (section_t *)head->next;

        while (current_section) {
                entry_t *current_entry;

                if (current_section->name[0])
                        fprintf(f, "\n[%s]\n", current_section->name);

                current_entry = (entry_t *)current_section->entry_head.next;

                while (current_entry) {
                        fprintf(f, "%s = %s\n", current_entry->name, current_entry->data);

                        current_entry = (entry_t *)current_entry->list.next;
                }

                current_section = (section_t *)current_section->list.next;
        }

        fclose(f);
}

void add_config_callback(void (*loadconfig)(), void (*saveconfig)(), void (*onloaded)()) {
        config_callbacks[num_config_callbacks].loadconfig = loadconfig;
        config_callbacks[num_config_callbacks].saveconfig = saveconfig;
        config_callbacks[num_config_callbacks].onloaded = onloaded;
        num_config_callbacks++;
}
