/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * The contents of this file are subject to the Netscape Public License
 * Version 1.0 (the "NPL"); you may not use this file except in
 * compliance with the NPL.  You may obtain a copy of the NPL at
 * http://www.mozilla.org/NPL/
 *
 * Software distributed under the NPL is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL
 * for the specific language governing rights and limitations under the
 * NPL.
 *
 * The Initial Developer of this code under the NPL is Netscape
 * Communications Corporation.  Portions created by Netscape are
 * Copyright (C) 1998 Netscape Communications Corporation.  All Rights
 * Reserved.
 */

/*
 * Common IDL-processing code.
 */

#include "xpidl.h"

FILE *typelib_file = NULL;
FILE *doc_file = NULL;
FILE *header_file = NULL;

nodeHandler *nodeDispatch[TREESTATE_NUM] = { NULL };

/*
 * Pass 1 generates #includes for headers and the like.
 */
static gboolean
process_tree_pass1(TreeState *state)
{
    nodeHandler handler;

    if ((handler = nodeDispatch[state->mode][0]))
        return handler(state);
    return TRUE;
}

gboolean
node_is_error(TreeState *state)
{
    fprintf(stderr, "ERROR: Unexpected node type %d\n",
            IDL_NODE_TYPE(state->tree));
    return FALSE;
}

void
xpidl_list_foreach(IDL_tree p, IDL_tree_func foreach, gpointer user_data)
{
    IDL_tree iter = p;
    for (; iter; iter = IDL_LIST(iter).next)
        if (!foreach(IDL_LIST(iter).data,
                     IDL_tree_get_scope(IDL_LIST(iter).data), user_data))
            return;
}

/*
 * The bulk of the generation happens here.
 */
gboolean
process_node(TreeState *state)
{
    char *name = NULL;
    nodeHandler *handlerp = nodeDispatch[state->mode], handler;
    gint type;
    assert(state->tree);

    type = IDL_NODE_TYPE(state->tree);

    /*
     * type == 0 shouldn't ever happen for real, so we use that slot for
     * pass-1 processing
     */
    if (type && handlerp && (handler = handlerp[type]))
        return handler(state);
    return TRUE;
}

/*
 * Call the IDLN_NONE handler for pre-generation, then process the tree,
 * then call the IDLN_NONE handler again with state->tree == NULL for
 * post-generation.
 */
static gboolean
process_tree(TreeState *state)
{
    IDL_tree top = state->tree;
    if (!process_tree_pass1(state))
        return FALSE;
    state->tree = top;		/* pass1 might mutate state */
    if (!process_node(state))
        return FALSE;
    state->tree = NULL;
    if (!process_tree_pass1(state))
        return FALSE;
    return TRUE;
}

static int
msg_callback(int level, int num, int line, const char *file,
             const char *message)
{
    /* XXX Mac */
    fprintf(stderr, "%s:%d: %s\n", file, line, message);
    return 1;
}

#define INPUT_BUF_CHUNK		8192

struct input_callback_data {
    FILE *input;                /* stream for getting data */
    char *filename;             /* where did I come from? */
    int lineno;                 /* last lineno processed */
    char *buf;                  /* buffer for data */
    char *point;                /* next char to feed to libIDL */
    int len;                    /* amount of data read into the buffer */
    int max;                    /* size of the buffer */
    struct input_callback_data *next; /* file from which we were included */
    char f_raw : 2,             /* in a raw block when starting next block */
         f_comment : 2,         /* in a comment when starting next block */
         f_include : 2;         /* in an #include when starting next block */
    char last_read[2];          /* last 1/2 chars read, for spanning blocks */
};

/* values for f_{raw,comment,include} */
#define INPUT_IN_NONE   0x0
#define INPUT_IN_FULL   0x1     /* we've already started one */
#define INPUT_IN_START  0x2     /* we're about to start one */
#define INPUT_IN_MAYBE  0x3     /* we might be about to start one (check
                                   last_read to be sure) */

struct input_callback_stack {
    struct input_callback_data *top;
    GHashTable *includes;
    IncludePathEntry *include_path;
};

static FILE *
fopen_from_includes(const char *filename, const char *mode,
                    IncludePathEntry *include_path)
{
    char *filebuf = NULL;
    FILE *file = NULL;
    for (; include_path && !file; include_path = include_path->next) {
        filebuf = g_strdup_printf("%s" G_DIR_SEPARATOR_S "%s", 
                                  include_path->directory, filename);
        if (!filebuf)
            return NULL;
#ifdef DEBUG_shaver_bufmgmt
        fprintf(stderr, "looking for %s as %s\n", filename, filebuf);
#endif
        file = fopen(filebuf, mode);
        free(filebuf);
    }
    if (!file)
        fprintf(stderr, "can't open %s for reading\n", filename);
    return file;
}

static struct input_callback_data *
new_input_callback_data(const char *filename, IncludePathEntry *include_path)
{
    struct input_callback_data *new_data = calloc(1, sizeof *new_data);
    if (!new_data)
        return NULL;
    new_data->input = fopen_from_includes(filename, "r", include_path);
    if (!new_data->input)
        return NULL;
    new_data->buf = malloc(INPUT_BUF_CHUNK + 1); /* trailing NUL */
    if (!new_data->buf) {
        fclose(new_data->input);
        return NULL;
    }
    new_data->len = 0;
    new_data->point = new_data->buf;
    new_data->max = INPUT_BUF_CHUNK;
    new_data->filename = strdup(filename);
    if (!new_data->filename) {
        free(new_data->buf);
        fclose(new_data->input);
        return NULL;
    }
    new_data->lineno = 1;
    new_data->next = NULL;
    return new_data;
}

static int
input_callback(IDL_input_reason reason, union IDL_input_data *cb_data,
               gpointer user_data)
{
    struct input_callback_stack *stack = user_data;
    struct input_callback_data *data = stack->top, *new_data = NULL;
    int rv, avail, copy;
    char *search, *check_point, *ptr, *end_copy, *raw_start, *comment_start,
        *include_start;

    switch(reason) {
      case IDL_INPUT_REASON_INIT:
        new_data = new_input_callback_data(cb_data->init.filename,
                                           stack->include_path);
        if (!new_data)
            return -1;

        IDL_file_set(new_data->filename, new_data->lineno);
        stack->top = new_data;
        return 0;
	
      case IDL_INPUT_REASON_FILL:
    fill_start:
        avail = data->buf + data->len - data->point;
        assert(avail >= 0);

        if (!avail) {
            data->point = data->buf;

            /* fill the buffer */
        fill_buffer:
            data->len = fread(data->buf, 1, data->max, data->input);
            if (!data->len) {
                if (ferror(data->input))
                    return -1;
                /* pop include */
                if (data->next) {
#ifdef DEBUG_shaver_includes
                    fprintf(stderr, "leaving %s, returning to %s\n",
                            data->filename, data->next->filename);
#endif
                    data = data->next;
                    stack->top = data;
                    IDL_file_set(data->filename, ++data->lineno);
                    IDL_inhibit_pop();
                    data->f_include = INPUT_IN_NONE;
                    goto fill_start;
                }
                return 0;
            }
            data->buf[data->len] = 0;
        }

    scan_for_special:
        check_point = data->point;
        end_copy = data->buf + data->len;
        /*
         * When we're stripping comments and processing #includes,
         * we need to be sure that we don't process anything inside
         * \n%{ and \n%}.  In order to simplify things, we only process
         * comment, include or raw-block stuff when they're at the
         * beginning of the block we're about to send (data->point).
         * This makes the processing much simpler, since we can skip
         * data->point ahead for comments and #include, and skip
         * check_point ahead for raw blocks.
         */

        if (!(data->f_raw || data->f_comment || data->f_include)) {
            /* look for first raw/comment/include */
#ifdef DEBUG_shaver_bufmgmt
            fprintf(stderr, "looking for specials\n");
#endif
            /* raw block */
            if ((raw_start = strstr(check_point, "\n%{"))) {
#ifdef DEBUG_shaver_bufmgmt
                fprintf(stderr, "found raw at %x\n", raw_start);
#endif
                end_copy = raw_start;
            }

            /* comment */
            if ((comment_start = strstr(check_point, "/*")) &&
                (!raw_start || comment_start < raw_start)) {
#ifdef DEBUG_shaver_bufmgmt
                fprintf(stderr, "comment starts with %.7s\n", comment_start);
#endif
                end_copy = comment_start;
            }
                        
            /* include */
            if ((include_start = strstr(check_point, "#include")) &&
                (!raw_start || include_start < raw_start) &&
                (!comment_start || include_start < comment_start)) {
                end_copy = include_start;
            }

            if (end_copy == raw_start)
                data->f_raw = INPUT_IN_START;
            else if (end_copy == comment_start)
                data->f_comment = INPUT_IN_START;
            else if (end_copy == include_start)
                data->f_include = INPUT_IN_START;
#ifdef DEBUG_shaver_bufmgmt
            fprintf(stderr,
                    "specials: %d/%d/%d, end = %x, buf = %x, b+l = %x\n",
                    data->f_raw, data->f_comment, data->f_include,
                    end_copy, data->buf, data->buf + data->len);
#endif
        } else {
#ifdef DEBUG_shaver_bufmgmt
            fprintf(stderr, "already have special %d/%d/%d\n", 
                    data->f_raw, data->f_comment, data->f_include);
#endif
        }
        
        if ((end_copy == data->point || /* just found one at the start */
             end_copy == data->buf + data->len /* left over */) &&
            (data->f_raw || data->f_comment || data->f_include)) {

            if (data->f_raw) {
                ptr = strstr(check_point, "\n%}\n");
                if (ptr) {
                    data->f_raw = INPUT_IN_NONE;
                    end_copy = ptr + 4;
#ifdef DEBUG_shaver_bufmgmt
                    fprintf(stderr, "RAW->%.*s<-RAW\n", end_copy - data->point,
                            data->point);
#endif
                }
                assert(!data->f_comment && !data->f_include);
            } else if (data->f_comment) {
                /* XXX process doc comment */
                ptr = strstr(check_point, "*/");
                if (ptr) {
                    data->f_comment = INPUT_IN_NONE;
                    data->point = ptr + 2; /* star-slash */
#ifdef DEBUG_shaver_bufmgmt
                    fprintf(stderr, "COMMENT->%.*s<-COMMENT\n",
                            data->point - check_point, check_point);
#endif
                }
                assert(!data->f_raw && !data->f_include);
                /*
                 * Now that we've advanced data->point to skip the comment,
                 * we want to check for specials that were ``shadowed'' by
                 * the comment.
                 */
                goto scan_for_special;
            } else if (data->f_include) {
                /* process include */
                const char *scratch;
                char *filename;
                include_start = data->point;

                assert(!strncmp(include_start, "#include \"", 10));
                filename = include_start + 10; /* skip #include " */

                assert(filename < data->buf + data->len);
                ptr = strchr(filename, '\"');
                if (!ptr) {
                    /* XXX report error */
                    return -1;
                }
                data->point = ptr+1;
                
                *ptr = 0;
                ptr = strrchr(filename, '.');

#ifdef DEBUG_shaver_bufmgmt
                fprintf(stderr, "found #include %s\n", filename);
#endif
                assert(stack->includes);
                if (!g_hash_table_lookup(stack->includes, filename)) {
                    char *basename = filename;
                    filename = strdup(filename);
                    ptr = strrchr(basename, '.');
                    if (ptr)
                        *ptr = 0;
                    basename = strdup(basename);
                    g_hash_table_insert(stack->includes, filename, basename);
                    new_data = new_input_callback_data(filename,
                                                       stack->include_path);
                    if (!new_data) {
                        free(filename);
                        return -1;
                    }
                    new_data->next = stack->top;
                    IDL_inhibit_push();
                    IDL_file_get(&scratch, &data->lineno);
                    data = stack->top = new_data;
                    IDL_file_set(data->filename, data->lineno);
#ifdef DEBUG_shaver_bufmgmt
                    fprintf(stderr, "processing #include %s\n", filename);
#endif
                    /* now continue getting data from new file */
                    goto fill_start;
                } else {
                    /*
                     * if we started with a #include, but we've already
                     * processed that file, we need to continue scanning
                     * for special sequences.
                     */
                    data->f_include = INPUT_IN_NONE;
                    goto scan_for_special;
                }
            }
        } else {
#ifdef DEBUG_shaver_bufmgmt
            fprintf(stderr, "no specials\n");
#endif
        }
        
        avail = MIN(data->buf + data->len, end_copy) - data->point;
#ifdef DEBUG_shaver_bufmgmt
        fprintf(stderr,
                "avail[%d] = MIN((data->buf[%x] + data->len[%d])[%x], "
                "end_copy[%x])[%x] - data->point[%x]\n",
                avail, data->buf, data->len, data->buf + data->len,
                end_copy, MIN(data->buf + data->len, end_copy),
                data->point);
#endif
        copy = MIN(avail, cb_data->fill.max_size);
#ifdef DEBUG_shaver_bufmgmt
        fprintf(stderr, "COPYING->%.*s<-COPYING\n", copy, data->point);
#endif
        memcpy(cb_data->fill.buffer, data->point, copy);
        data->point += copy;
        return copy;
	
      case IDL_INPUT_REASON_ABORT:
      case IDL_INPUT_REASON_FINISH:
        fclose(data->input);
        data->input = NULL;
        free(data->buf);
        data->buf = data->point = NULL;
        data->len = data->max = 0;
        return 0;

      default:
        g_error("unknown input reason %d!", reason);
        return -1;
    }
}

int
xpidl_process_idl(char *filename, IncludePathEntry *include_path,
                  char *basename)
{
    char *tmp, *outname, *mode_outname;
    IDL_tree top;
    TreeState state;
    int rv;
    struct input_callback_stack stack;
    gboolean ok;

    stack.includes = g_hash_table_new(g_str_hash, g_str_equal);
    if (!stack.includes) {
        fprintf(stderr, "failed to create hashtable (EOM?)\n");
        return 0;
    }
    stack.include_path = include_path;

    rv = IDL_parse_filename_with_input(filename, input_callback, &stack,
                                       msg_callback, &top,
                                       &state.ns, IDLF_XPIDL,
                                       enable_warnings ? IDL_WARNING1 : 0);
    if (rv != IDL_SUCCESS) {
        if (rv == -1) {
            g_warning("Parse of %s failed: %s", filename, g_strerror(errno));
        } else {
            g_warning("Parse of %s failed", filename);
        }
        return 0;
    }

    state.basename = strdup(filename);
    tmp = strrchr(state.basename, '.');
    if (tmp)
        *tmp = '\0';
    
    if (!basename)
        outname = strdup(state.basename);
    else
        outname = strdup(basename);

    state.includes = stack.includes;
    state.include_path = include_path;
    nodeDispatch[TREESTATE_HEADER] = headerDispatch();
    nodeDispatch[TREESTATE_TYPELIB] = typelibDispatch();
    nodeDispatch[TREESTATE_DOC] = docDispatch();
    if (generate_headers) {
        if (strcmp(outname, "-")) {
            mode_outname = g_strdup_printf("%s.h", outname);
            state.file = fopen(mode_outname, "w");
            if (!state.file) {
                perror("error opening output file");
                free(mode_outname);
                return 0;
            }
            free(mode_outname);
        } else {
            state.file = stdout;
        }
        state.mode = TREESTATE_HEADER;
        state.tree = top;
        ok = process_tree(&state);
        if (state.file != stdout)
            fclose(state.file);
        if (!ok)
            return 0;
    }
    if (generate_typelib) {
        if (strcmp(basename, "-")) {
            outname = g_strdup_printf("%s.xpt", basename);
            state.file = fopen(outname, "wb");
            if (!state.file) {
                perror("error opening output file");
                free(outname);
                return 0;
            }
        } else {
            state.file = stdout;
        }
        state.mode = TREESTATE_TYPELIB;
        state.tree = top;
        ok = process_tree(&state);
        free(outname);
        if (state.file != stdout)
            fclose(state.file);
        if (!ok)
            return 0;
    }
    if (generate_docs) {
        state.mode = TREESTATE_DOC;
        state.tree = top;
        if (!process_tree(&state))
            return 0;
    }
    free(state.basename);
    free(outname);
    /* g_hash_table_foreach(state.includes, free_name, NULL);
       g_hash_table_destroy(state.includes);
    */
    IDL_ns_free(state.ns);
    IDL_tree_free(top);
    
    return 1;
}
