#define CHAZ_USE_SHORT_NAMES

#include "Charmonizer/Core/Util.h"
#include "Charmonizer/Core/ModHandler.h"
#include "Charmonizer/Core/OperSys.h"
#include "Charmonizer/Core/Compiler.h"
#include <errno.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>


/* Write the "_charm.h" file used by every probe.
 */
static void
write_charm_h();

/* Compile a small wrapper application which is used to redirect error output
 * to devnull.
 */
static void
build_charm_run();

/* Clean up the files associated with capture_output().
 */
static void
clean_up_try();

/* global vars */
struct chaz_OperSys  *chaz_ModHand_os = NULL;
struct chaz_Compiler *chaz_ModHand_compiler = NULL;
chaz_bool_t chaz_ModHand_charm_run_available = false;
FILE* chaz_ModHand_charmony_fh = NULL;

/* static vars */
static char *try_app_path = NULL;
static char *try_app_command = NULL;

void
chaz_ModHand_init()
{
    /* set the name of the application which we "try" to execute */
    (void)join_strings(&try_app_path, 0, TRY_APP_BASENAME, os->exe_ext, NULL);

    /* set the invocation string for the "try" application */
    (void)join_strings(&try_app_command, 0, os->local_command_start,
        try_app_path, NULL);

    /* write files needed by this module and others */
    build_charm_run();
    write_charm_h();
}

void
chaz_ModHand_open_charmony_h(const char *charmony_start)
{
    /* open the filehandle */
    charmony_fh = fopen("charmony.h", "w+");
    if (charmony_fh == NULL) 
        die("Can't open 'charmony.h': %s", strerror(errno));

    /* print supplied text (if any) along with warning, open include guard */
    if (charmony_start != NULL)
        fprintf(charmony_fh, charmony_start);
    fprintf(charmony_fh,
        "/* Header file auto-generated by Charmonizer. \n"
        " * DO NOT EDIT THIS FILE!!\n"
        " */\n\n"
        "#ifndef H_CHARMONY\n"
        "#define H_CHARMONY 1\n\n"
    );
}

void
chaz_ModHand_clean_up(void)
{
    /* clean up some temp files */
    remove("_charm.h");
    os->remove_exe(os, "_charm_run");
    os->remove_exe(os, "_charm_stat");

    /* write the last bit of charmony.h and close */
    fprintf(charmony_fh, "#endif /* H_CHARMONY */\n\n");
    if (fclose(charmony_fh))
        die("Couldn't close 'charmony.h': %s", strerror(errno));
}

static char charm_h_code[] = "\n"
    "    #ifndef CHARM_H\n"
    "    #define CHARM_H 1\n"
    "\n"
    "    #include <stdio.h>\n"
    "\n"
    "    #define Charm_Setup \\\n"
    "        freopen(\"_charmonizer_target\", \"w\", stdout)\n"
    "    \n"
    "    #endif\n"
    "";

static void
write_charm_h()
{
    write_file("_charm.h", charm_h_code, strlen(charm_h_code));
}

static char charm_run_code_a[] = "\n"
    "    #include <stdio.h>\n"
    "    #include <stdlib.h>\n"
    "    #include <string.h>\n"
    "    #include <stddef.h>\n"
    "    int main(int argc, char **argv)\n"
    "    {\n"
    "        char *command;\n"
    "        size_t command_len = 1; /* terminating null */\n"
    "        int i;\n"
    "        int retval;\n"
    "        \n"
    "        /* rebuild the command line args, minus the name of this utility */\n"
    "        for (i = 1; i < argc; i++) {\n"
    "            command_len += strlen(argv[i]) + 1;\n"
    "        }\n"
    "        command = (char*)calloc(command_len, sizeof(char));\n"
    "";

static char charm_run_code_b[] = "\n"
    "        if (command == NULL) {\n"
    "            fprintf(stderr, \"calloc failed\\n\");\n"
    "            exit(1);\n"
    "        }\n"
    "        for (i = 1; i < argc; i++) {\n"
    "            strcat( strcat(command, \" \"), argv[i] );\n"
    "        }\n"
    "\n"
    "        /* redirect stdout and stderr to /dev/null or equivalent */\n"
    "        freopen( \n"
    "";

static char charm_run_code_c[] = "\n"
    "             , \"w\", stdout);\n"
    "        freopen( \n"
    "";

static char charm_run_code_d[] = "\n"
    "             , \"w\", stderr);\n"
    "\n"
    "        /* run the commmand and return its value to the parent process */\n"
    "        retval = system(command);\n"
    "        free(command);\n"
    "        return retval;\n"
    "    }\n"
    "";

static void
build_charm_run()
{
    char *code = NULL;
    chaz_bool_t compile_succeeded = false;

    (void)join_strings(&code, 0, charm_run_code_a, charm_run_code_b, " \"", 
        os->devnull, "\" ", charm_run_code_c, " \"", os->devnull, "\" ", 
        charm_run_code_d, NULL);

    compile_succeeded = compiler->compile_exe(compiler, "_charm_run.c", 
        "_charm_run", code, strlen(code));
    if (!compile_succeeded)
        die("failed to compile _charm_run helper utility");

    remove("_charm_run.c");
    free(code);
    charm_run_available = true;
}

chaz_bool_t
chaz_ModHand_test_compile(char *source, size_t source_len)
{
    chaz_bool_t compile_succeeded;

    if ( !remove_and_verify(try_app_path) ) 
        die("Failed to delete file '%s'", try_app_path);

    compile_succeeded = compiler->compile_exe(compiler, TRY_SOURCE_PATH, 
        TRY_APP_BASENAME, source, source_len);

    clean_up_try();

    return compile_succeeded;
}

char*
chaz_ModHand_capture_output(char *source, size_t source_len, 
                            size_t *output_len) 
{
    char *captured_output = NULL;
    chaz_bool_t compile_succeeded;

    /* clear out previous versions and test to make sure removal worked */
    if ( !remove_and_verify(try_app_path) ) 
        die("Failed to delete file '%s'", try_app_path);
    if ( !remove_and_verify(TARGET_PATH) )
        die("Failed to delete file '%s'", TARGET_PATH);

    /* attempt compilation; if successful, run app and slurp output */
    compile_succeeded = compiler->compile_exe(compiler, TRY_SOURCE_PATH, 
        TRY_APP_BASENAME, source, source_len);
    if (compile_succeeded) {
        system(try_app_command);
        captured_output = slurp_file(TARGET_PATH, output_len);
    }
    else {
        *output_len = 0;
    }

    /* remove all the files we just created */
    clean_up_try();

    return captured_output;
}

void
chaz_ModHand_append_conf(char *fmt, ...)
{
    va_list args;

    va_start(args, fmt);
    vfprintf(chaz_ModHand_charmony_fh, fmt, args);
    va_end(args);
}

static void
clean_up_try()
{
    remove(TRY_SOURCE_PATH);
    os->remove_exe(os, TRY_APP_BASENAME);
    remove(TARGET_PATH);
}

void
chaz_ModHand_shorten_macro(const char *sym)
{
    append_conf("  #define %s CHY_%s\n", sym, sym); 
}

void
chaz_ModHand_shorten_typedef(const char *sym)
{
    append_conf("  #define %s chy_%s\n", sym, sym); 
}

void
chaz_ModHand_shorten_function(const char *sym)
{
    append_conf("  #define %s chy_%s\n", sym, sym); 
}


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