/*
 *
 * Generate module.c from module.in and Makefile.am or Makefile.
 *
 *  Copyright (C) 2004-2007  Peter Johnson
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND OTHER CONTRIBUTORS ``AS IS''
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR OTHER CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

#include "compat-queue.h"

#define OUTPUT  "module.c"
#define MAXNAME 128
#define MAXLINE 1024
#define MAXMODULES 128
#define MAXINCLUDES 256

typedef struct include {
    STAILQ_ENTRY(include) link;
    char *filename;
} include;

int
main(int argc, char *argv[])
{
    FILE *in, *out;
    char *str;
    int i;
    size_t len;
    char *strp;
    char *modules[MAXMODULES];
    int num_modules = 0;
    STAILQ_HEAD(includehead, include) includes =
        STAILQ_HEAD_INITIALIZER(includes);
    include *inc;
    int isam = 0;
    int linecont = 0;

    if (argc != 3) {
        fprintf(stderr, "Usage: %s <module.in> <Makefile[.am]>\n", argv[0]);
        return EXIT_FAILURE;
    }

    str = malloc(MAXLINE);

    /* Starting with initial input Makefile, look for include <file> or
     * YASM_MODULES += <module>.  Note this currently doesn't handle
     * a relative starting path.
     */
    len = strlen(argv[2]);
    inc = malloc(sizeof(include));
    inc->filename = malloc(len+1);
    strcpy(inc->filename, argv[2]);
    STAILQ_INSERT_TAIL(&includes, inc, link);

    isam = argv[2][len-2] == 'a' && argv[2][len-1] == 'm';

    while (!STAILQ_EMPTY(&includes)) {
        inc = STAILQ_FIRST(&includes);
        STAILQ_REMOVE_HEAD(&includes, link);
        in = fopen(inc->filename, "rt");
        if (!in) {
            fprintf(stderr, "Could not open `%s'.\n", inc->filename);
            return EXIT_FAILURE;
        }
        free(inc->filename);
        free(inc);

        while (fgets(str, MAXLINE, in)) {
            /* Strip off any trailing whitespace */
            len = strlen(str);
            if (len > 0) {
                strp = &str[len-1];
                while (len > 0 && isspace(*strp)) {
                    *strp-- = '\0';
                    len--;
                }
            }

            strp = str;

            /* Skip whitespace */
            while (isspace(*strp))
                strp++;

            /* Skip comments */
            if (*strp == '#')
                continue;

            /* If line continuation, skip to continue copy */
            if (linecont)
                goto keepgoing;

            /* Check for include if original input is .am file */
            if (isam && strncmp(strp, "include", 7) == 0 && isspace(strp[7])) {
                strp += 7;
                while (isspace(*strp))
                    strp++;
                /* Build new include and add to end of list */
                inc = malloc(sizeof(include));
                inc->filename = malloc(strlen(strp)+1);
                strcpy(inc->filename, strp);
                STAILQ_INSERT_TAIL(&includes, inc, link);
                continue;
            }

            /* Check for YASM_MODULES = or += */
            if (strncmp(strp, "YASM_MODULES", 12) != 0)
                continue;
            strp += 12;
            while (isspace(*strp))
                strp++;
            if (strncmp(strp, "+=", 2) != 0 && *strp != '=')
                continue;
            if (*strp == '+')
                strp++;
            strp++;
            while (isspace(*strp))
                strp++;

keepgoing:
            /* Check for continuation */
            if (len > 0 && str[len-1] == '\\') {
                str[len-1] = '\0';
                while (isspace(*strp))
                    *strp-- = '\0';
                linecont = 1;
            } else
                linecont = 0;

            while (*strp != '\0') {
                /* Copy module name */
                modules[num_modules] = malloc(MAXNAME);
                len = 0;
                while (*strp != '\0' && !isspace(*strp))
                    modules[num_modules][len++] = *strp++;
                modules[num_modules][len] = '\0';
                num_modules++;

                while (isspace(*strp))
                    strp++;
            }
        }
        fclose(in);
    }

    out = fopen(OUTPUT, "wt");

    if (!out) {
        fprintf(stderr, "Could not open `%s'.\n", OUTPUT);
        return EXIT_FAILURE;
    }

    fprintf(out, "/* This file auto-generated by genmodule.c"
                 " - don't edit it */\n\n");

    in = fopen(argv[1], "rt");
    if (!in) {
        fprintf(stderr, "Could not open `%s'.\n", argv[1]);
        fclose(out);
        remove(OUTPUT);
        return EXIT_FAILURE;
    }

    len = 0;
    while (fgets(str, MAXLINE, in)) {
        if (strncmp(str, "MODULES_", 8) == 0) {
            len = 0;
            strp = str+8;
            while (*strp != '\0' && *strp != '_') {
                len++;
                strp++;
            }
            *strp = '\0';

            for (i=0; i<num_modules; i++) {
                if (strncmp(modules[i], str+8, len) == 0) {
                    fprintf(out, "    {\"%s\", &yasm_%s_LTX_%s},\n",
                            modules[i]+len+1, modules[i]+len+1, str+8);
                }
            }
        } else if (strncmp(str, "EXTERN_LIST", 11) == 0) {
            for (i=0; i<num_modules; i++) {
                strcpy(str, modules[i]);
                strp = str;
                while (*strp != '\0' && *strp != '_')
                    strp++;
                *strp++ = '\0';

                fprintf(out, "extern yasm_%s_module yasm_%s_LTX_%s;\n",
                        str, strp, str);
            }
        } else
            fputs(str, out);
    }

    fclose(in);
    fclose(out);

    for (i=0; i<num_modules; i++)
        free(modules[i]);
    free(str);

    return EXIT_SUCCESS;
}