#define CHAZ_USE_SHORT_NAMES

#include "Charmonizer/Core/ModHandler.h"
#include "Charmonizer/Core/Util.h"
#include "Charmonizer/Core/HeadCheck.h"
#include <string.h>
#include <stdlib.h>

typedef struct Header {
    char        *name;
    chaz_bool_t  exists;
} Header;

static char *code_buf   = NULL;
static int code_buf_len = 0;

/* hello_world.c without the hello or the world */
static char test_code[] = "int main() { return 0; }\n";

/* Keep a sorted, dynamically-sized array of names of all headers we've
 * checked for so far.
 */
static int cache_size          = 0;
static Header **header_cache   = NULL;

/* Comparison function to feed to qsort, bsearch, etc.
 */
static int
compare_headers(const void *vptr_a, const void *vptr_b);

/* Run a test compilation and return a new Header object encapsulating the
 * results.
 */
static Header* 
discover_header(const char *header_name);

/* Extend the cache, add this Header object to it, and sort.
 */
static void
add_to_cache(Header *header);

/* Like add_to_cache, but takes a individual elements instead of a Header* and
 * checks if header exists in array first.
 */
static void
maybe_add_to_cache(const char *header_name, chaz_bool_t exists);

void
chaz_HeadCheck_init()
{
    Header *null_header = (Header*)malloc(sizeof(Header));

    /* create terminating record for the dynamic array of Header objects */
    null_header->name   = NULL;
    null_header->exists = false;
    header_cache = (Header**)malloc(sizeof(void*));
    *header_cache = null_header;
    cache_size = 1;
}

chaz_bool_t
chaz_HeadCheck_check_header(const char *header_name)
{
    Header *header;
    Header  key;
    Header *fake = &key;

    /* fake up a key to feed to bsearch; see if the header's already there */
    key.name = (char*)header_name;
    key.exists = false;
    header = bsearch(&fake, header_cache, cache_size, sizeof(void*),
        compare_headers);
    
    /* if it's not there, go try a test compile */
    if (header == NULL) {
        header = discover_header(header_name);
        add_to_cache(header);
    }

    return header->exists;
}

chaz_bool_t
chaz_HeadCheck_check_many_headers(const char **header_names)
{
    chaz_bool_t success;
    int i;

    /* build the source code string */
    code_buf_len = join_strings(&code_buf, code_buf_len, " ", NULL);
    for (i = 0; header_names[i] != NULL; i++) {
        code_buf_len = append_strings(&code_buf, code_buf_len, "#include <", 
            header_names[i], ">\n", NULL); 
    }
    code_buf_len = append_strings(&code_buf, code_buf_len, test_code, NULL);

    /* if the code compiles, bulk add all header names to the cache */
    success = test_compile(code_buf, strlen(code_buf));
    if (success) {
        for (i = 0; header_names[i] != NULL; i++) {
            maybe_add_to_cache(header_names[i], true);
        }
    }

    return success;
}

static int
compare_headers(const void *vptr_a, const void *vptr_b) {
    Header **const a = (Header**)vptr_a;
    Header **const b = (Header**)vptr_b;

    if ((*a)->name == NULL) /* null is "greater than" any string */
        return 1;
    else if ((*b)->name == NULL)
        return -1;
    else
        return strcmp((*a)->name, (*b)->name);
}

static Header* 
discover_header(const char *header_name) {
    Header* header = (Header*)malloc(sizeof(Header));
    
    /* assign */
    header->name = strdup(header_name);

    /* see whether code that tries to pull in this header compiles */
    code_buf_len = join_strings(&code_buf, code_buf_len, "#include <",
        header_name, ">\n", test_code, NULL);
    header->exists = test_compile(code_buf, strlen(code_buf))
        ? true : false;

    return header;
}

static void
add_to_cache(Header *header)
{
    /* realloc array -- inefficient, but this isn't a bottleneck */
    cache_size++;
    header_cache = (Header**)realloc(header_cache, (cache_size * sizeof(void*)));
    header_cache[ cache_size - 1 ] = header;

    /* keep the list of headers sorted */
    qsort(header_cache, cache_size, sizeof(*header_cache), compare_headers);
}

static void
maybe_add_to_cache(const char *header_name, chaz_bool_t exists)
{
    Header *header;
    Header  key;
    Header *fake = &key;

    /* fake up a key and bsearch for it */
    key.name   = (char*)header_name;
    key.exists = exists;
    header = bsearch(&fake, header_cache, cache_size, sizeof(void*),
        compare_headers);
    
    /* we've already done the test compile, so skip that step and add it */
    if (header == NULL) {
        header = (Header*)malloc(sizeof(Header));
        header->name   = strdup(header_name);
        header->exists = exists;
        add_to_cache(header);
    }
}

/* Copyright 2006-2007 Marvin Humphrey
 *
 * This program is free software; you can redistribute it and/or modify
 * under the same terms as Perl itself.
 */