/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is mozilla.org code.
 *
 * The Initial Developer of the Original Code is
 * Netscape Communications Corporation.
 * Portions created by the Initial Developer are Copyright (C) 1998
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

/*
 * Generate XPCOM headers from XPIDL.
 */

#include "xpidl.h"
#include <ctype.h>

#define AS_DECL 0
#define AS_CALL 1
#define AS_IMPL 2

static gboolean write_method_signature(IDL_tree method_tree, FILE *outfile,
                                       int mode, const char *className);
static gboolean write_attr_accessor(IDL_tree attr_tree, FILE * outfile,
                                    gboolean getter, 
                                    int mode, const char *className);

static void
write_indent(FILE *outfile) {
    fputs("  ", outfile);
}

static gboolean
header_prolog(TreeState *state)
{
    const char *define = xpidl_basename(state->basename);
    fprintf(state->file, "/*\n * DO NOT EDIT.  THIS FILE IS GENERATED FROM"
            " %s.idl\n */\n", state->basename);
    fprintf(state->file,
            "\n#ifndef __gen_%s_h__\n"
            "#define __gen_%s_h__\n",
            define, define);
    if (state->base_includes != NULL) {
        guint len = g_slist_length(state->base_includes);
        guint i;

        fputc('\n', state->file);
        for (i = 0; i < len; i++) {
            char *ident, *dot;
            
            ident = (char *)g_slist_nth_data(state->base_includes, i);
            
            /* suppress any trailing .extension */
            
            /* XXX use g_basename instead ? ? */
            
            dot = strrchr(ident, '.');
            if (dot != NULL)
                *dot = '\0';
            

            /* begin include guard */            
            fprintf(state->file,
                    "\n#ifndef __gen_%s_h__\n",
                     ident);

            fprintf(state->file, "#include \"%s.h\"\n",
                    (char *)g_slist_nth_data(state->base_includes, i));

            fprintf(state->file, "#endif\n");
            
        }
        if (i > 0)
            fputc('\n', state->file);
    }
    /*
     * Support IDL files that don't include a root IDL file that defines
     * NS_NO_VTABLE.
     */
    fprintf(state->file,
            "/* For IDL files that don't want to include root IDL files. */\n"
            "#ifndef NS_NO_VTABLE\n"
            "#define NS_NO_VTABLE\n"
            "#endif\n");
    
    return TRUE;
}

static gboolean
header_epilog(TreeState *state)
{
    const char *define = xpidl_basename(state->basename);
    fprintf(state->file, "\n#endif /* __gen_%s_h__ */\n", define);
    return TRUE;
}

static void
write_classname_iid_define(FILE *file, const char *className)
{
    const char *iidName;
    if (className[0] == 'n' && className[1] == 's') {
        /* backcompat naming styles */
        fputs("NS_", file);
        iidName = className + 2;
    } else {
        iidName = className;
    }
    while (*iidName)
        fputc(toupper(*iidName++), file);
    fputs("_IID", file);
}

static gboolean
interface(TreeState *state)
{
    IDL_tree iface = state->tree, iter, orig;
    char *className = IDL_IDENT(IDL_INTERFACE(iface).ident).str;
    char *classNameUpper = NULL;
    char *classNameImpl = NULL;
    char *cp;
    gboolean ok = TRUE;
    gboolean keepvtable;
    const char *iid;
    const char *name_space;
    struct nsID id;
    char iid_parsed[UUID_LENGTH];
    GSList *doc_comments = IDL_IDENT(IDL_INTERFACE(iface).ident).comments;

    if (!verify_interface_declaration(iface))
        return FALSE;

#define FAIL    do {ok = FALSE; goto out;} while(0)

    fprintf(state->file,   "\n/* starting interface:    %s */\n",
            className);

    name_space = IDL_tree_property_get(IDL_INTERFACE(iface).ident, "namespace");
    if (name_space) {
        fprintf(state->file, "/* namespace:             %s */\n",
                name_space);
        fprintf(state->file, "/* fully qualified name:  %s.%s */\n",
                name_space,className);
    }

    iid = IDL_tree_property_get(IDL_INTERFACE(iface).ident, "uuid");
    if (iid) {
        /* Redundant, but a better error than 'cannot parse.' */
        if (strlen(iid) != 36) {
            IDL_tree_error(state->tree, "IID %s is the wrong length\n", iid);
            FAIL;
        }

        /*
         * Parse uuid and then output resulting nsID to string, to validate
         * uuid and normalize resulting .h files.
         */
        if (!xpidl_parse_iid(&id, iid)) {
            IDL_tree_error(state->tree, "cannot parse IID %s\n", iid);
            FAIL;
        }
        if (!xpidl_sprint_iid(&id, iid_parsed)) {
            IDL_tree_error(state->tree, "error formatting IID %s\n", iid);
            FAIL;
        }

        /* #define NS_ISUPPORTS_IID_STR "00000000-0000-0000-c000-000000000046" */
        fputs("#define ", state->file);
        write_classname_iid_define(state->file, className);
        fprintf(state->file, "_STR \"%s\"\n", iid_parsed);
        fputc('\n', state->file);

        /* #define NS_ISUPPORTS_IID { {0x00000000 .... 0x46 }} */
        fprintf(state->file, "#define ");
        write_classname_iid_define(state->file, className);
        fprintf(state->file, " \\\n"
                "  {0x%.8x, 0x%.4x, 0x%.4x, \\\n"
                "    { 0x%.2x, 0x%.2x, 0x%.2x, 0x%.2x, "
                "0x%.2x, 0x%.2x, 0x%.2x, 0x%.2x }}\n",
                id.m0, id.m1, id.m2,
                id.m3[0], id.m3[1], id.m3[2], id.m3[3],
                id.m3[4], id.m3[5], id.m3[6], id.m3[7]);
        fputc('\n', state->file);
    } else {
        IDL_tree_error(state->tree, "interface %s lacks a uuid attribute\n", 
            className);
        FAIL;
    }

    if (doc_comments != NULL)
        printlist(state->file, doc_comments);

    /*
     * NS_NO_VTABLE is defined in nsISupportsUtils.h, and defined on windows
     * to __declspec(novtable) on windows.  This optimization is safe
     * whenever the constructor calls no virtual methods.  Writing in IDL
     * almost guarantees this, except for the case when a %{C++ block occurs in
     * the interface.  We detect that case, and emit a macro call that disables
     * the optimization.
     */
    keepvtable = FALSE;
    for (iter = IDL_INTERFACE(state->tree).body;
         iter != NULL;
         iter = IDL_LIST(iter).next)
    {
        IDL_tree data = IDL_LIST(iter).data;
        if (IDL_NODE_TYPE(data) == IDLN_CODEFRAG)
            keepvtable = TRUE;
    }
    
    /* The interface declaration itself. */
    fprintf(state->file,
            "class %s%s",
            (keepvtable ? "" : "NS_NO_VTABLE "), className);
    
    if ((iter = IDL_INTERFACE(iface).inheritance_spec)) {
        fputs(" : ", state->file);
        if (IDL_LIST(iter).next != NULL) {
            IDL_tree_error(iter,
                           "multiple inheritance is not supported by xpidl");
            FAIL;
        }
        fprintf(state->file, "public %s", IDL_IDENT(IDL_LIST(iter).data).str);
    }
    fputs(" {\n"
          " public: \n\n", state->file);
    if (iid) {
        fputs("  NS_DEFINE_STATIC_IID_ACCESSOR(", state->file);
        write_classname_iid_define(state->file, className);
        fputs(")\n\n", state->file);
    }

    orig = state->tree; /* It would be nice to remove this state-twiddling. */

    state->tree = IDL_INTERFACE(iface).body;

    if (state->tree && !xpidl_process_node(state))
        FAIL;

    fputs("};\n", state->file);
    fputc('\n', state->file);

    /*
     * #define NS_DECL_NSIFOO - create method prototypes that can be used in
     * class definitions that support this interface.
     *
     * Walk the tree explicitly to prototype a reworking of xpidl to get rid of
     * the callback mechanism.
     */
    state->tree = orig;
    fputs("/* Use this macro when declaring classes that implement this "
          "interface. */\n", state->file);
    fputs("#define NS_DECL_", state->file);
    classNameUpper = xpidl_strdup(className);
    for (cp = classNameUpper; *cp != '\0'; cp++)
        *cp = toupper(*cp);
    fprintf(state->file, "%s \\\n", classNameUpper);
    if (IDL_INTERFACE(state->tree).body == NULL) {
        write_indent(state->file);
        fputs("/* no methods! */\n", state->file);
    }

    for (iter = IDL_INTERFACE(state->tree).body;
         iter != NULL;
         iter = IDL_LIST(iter).next)
    {
        IDL_tree data = IDL_LIST(iter).data;

        switch(IDL_NODE_TYPE(data)) {
          case IDLN_OP_DCL:
            write_indent(state->file);
            write_method_signature(data, state->file, AS_DECL, NULL);
            break;

          case IDLN_ATTR_DCL:
            write_indent(state->file);
            if (!write_attr_accessor(data, state->file, TRUE, AS_DECL, NULL))
                FAIL;
            if (!IDL_ATTR_DCL(data).f_readonly) {
                fputs("; \\\n", state->file); /* Terminate the previous one. */
                write_indent(state->file);
                if (!write_attr_accessor(data, state->file,
                                         FALSE, AS_DECL, NULL))
                    FAIL;
                /* '; \n' at end will clean up. */
            }
            break;

          case IDLN_CONST_DCL:
            /* ignore it here; it doesn't contribute to the macro. */
            continue;

          case IDLN_CODEFRAG:
            XPIDL_WARNING((iter, IDL_WARNING1,
                           "%%{ .. %%} code fragment within interface "
                           "ignored when generating NS_DECL_%s macro; "
                           "if the code fragment contains method "
                           "declarations, the macro probably isn't "
                           "complete.", classNameUpper));
            continue;

          default:
            IDL_tree_error(iter,
                           "unexpected node type %d! "
                           "Please file a bug against the xpidl component.",
                           IDL_NODE_TYPE(data));
            FAIL;
        }

        if (IDL_LIST(iter).next != NULL) {
            fprintf(state->file, "; \\\n");
        } else {
            fprintf(state->file, "; \n");
        }
    }
    fputc('\n', state->file);

    /* XXX abstract above and below into one function? */
    /*
     * #define NS_FORWARD_NSIFOO - create forwarding methods that can delegate
     * behavior from in implementation to another object.  As generated by
     * idlc.
     */
    fprintf(state->file,
            "/* Use this macro to declare functions that forward the "
            "behavior of this interface to another object. */\n"
            "#define NS_FORWARD_%s(_to) \\\n",
            classNameUpper);
    if (IDL_INTERFACE(state->tree).body == NULL) {
        write_indent(state->file);
        fputs("/* no methods! */\n", state->file);
    }

    for (iter = IDL_INTERFACE(state->tree).body;
         iter != NULL;
         iter = IDL_LIST(iter).next)
    {
        IDL_tree data = IDL_LIST(iter).data;

        switch(IDL_NODE_TYPE(data)) {
          case IDLN_OP_DCL:
            write_indent(state->file);
            write_method_signature(data, state->file, AS_DECL, NULL);
            fputs(" { return _to ", state->file);
            write_method_signature(data, state->file, AS_CALL, NULL);
            break;

          case IDLN_ATTR_DCL:
            write_indent(state->file);
            if (!write_attr_accessor(data, state->file, TRUE, AS_DECL, NULL))
                FAIL;
            fputs(" { return _to ", state->file);
            if (!write_attr_accessor(data, state->file, TRUE, AS_CALL, NULL))
                FAIL;
            if (!IDL_ATTR_DCL(data).f_readonly) {
                fputs("; } \\\n", state->file); /* Terminate the previous one. */
                write_indent(state->file);
                if (!write_attr_accessor(data, state->file,
                                         FALSE, AS_DECL, NULL))
                    FAIL;
                fputs(" { return _to ", state->file);
                if (!write_attr_accessor(data, state->file,
                                         FALSE, AS_CALL, NULL))
                    FAIL;
                /* '; } \n' at end will clean up. */
            }
            break;

          case IDLN_CONST_DCL:
          case IDLN_CODEFRAG:
              continue;

          default:
            FAIL;
        }

        if (IDL_LIST(iter).next != NULL) {
            fprintf(state->file, "; } \\\n");
        } else {
            fprintf(state->file, "; } \n");
        }
    }
    fputc('\n', state->file);


    /* XXX abstract above and below into one function? */
    /*
     * #define NS_FORWARD_SAFE_NSIFOO - create forwarding methods that can delegate
     * behavior from in implementation to another object.  As generated by
     * idlc.
     */
    fprintf(state->file,
            "/* Use this macro to declare functions that forward the "
            "behavior of this interface to another object in a safe way. */\n"
            "#define NS_FORWARD_SAFE_%s(_to) \\\n",
            classNameUpper);
    if (IDL_INTERFACE(state->tree).body == NULL) {
        write_indent(state->file);
        fputs("/* no methods! */\n", state->file);
    }

    for (iter = IDL_INTERFACE(state->tree).body;
         iter != NULL;
         iter = IDL_LIST(iter).next)
    {
        IDL_tree data = IDL_LIST(iter).data;

        switch(IDL_NODE_TYPE(data)) {
          case IDLN_OP_DCL:
            write_indent(state->file);
            write_method_signature(data, state->file, AS_DECL, NULL);
            fputs(" { return !_to ? NS_ERROR_NULL_POINTER : _to->", state->file);
            write_method_signature(data, state->file, AS_CALL, NULL);
            break;

          case IDLN_ATTR_DCL:
            write_indent(state->file);
            if (!write_attr_accessor(data, state->file, TRUE, AS_DECL, NULL))
                FAIL;
            fputs(" { return !_to ? NS_ERROR_NULL_POINTER : _to->", state->file);
            if (!write_attr_accessor(data, state->file, TRUE, AS_CALL, NULL))
                FAIL;
            if (!IDL_ATTR_DCL(data).f_readonly) {
                fputs("; } \\\n", state->file); /* Terminate the previous one. */
                write_indent(state->file);
                if (!write_attr_accessor(data, state->file,
                                         FALSE, AS_DECL, NULL))
                    FAIL;
                fputs(" { return !_to ? NS_ERROR_NULL_POINTER : _to->", state->file);
                if (!write_attr_accessor(data, state->file,
                                         FALSE, AS_CALL, NULL))
                    FAIL;
                /* '; } \n' at end will clean up. */
            }
            break;

          case IDLN_CONST_DCL:
          case IDLN_CODEFRAG:
              continue;

          default:
            FAIL;
        }

        if (IDL_LIST(iter).next != NULL) {
            fprintf(state->file, "; } \\\n");
        } else {
            fprintf(state->file, "; } \n");
        }
    }
    fputc('\n', state->file);

    /*
     * Build a sample implementation template.
     */
    if (strlen(className) >= 3 && className[2] == 'I') {
        classNameImpl = xpidl_strdup(className);
        if (!classNameImpl)
            FAIL;
        memmove(&classNameImpl[2], &classNameImpl[3], strlen(classNameImpl) - 2);
    } else {
        classNameImpl = xpidl_strdup("_MYCLASS_");
        if (!classNameImpl)
            FAIL;
    }

    fputs("#if 0\n"
          "/* Use the code below as a template for the "
          "implementation class for this interface. */\n"
          "\n"
          "/* Header file */"
          "\n",
          state->file);
    fprintf(state->file, "class %s : public %s\n", classNameImpl, className);
    fputs("{\n"
          "public:\n", state->file);
    write_indent(state->file);
    fputs("NS_DECL_ISUPPORTS\n", state->file);
    write_indent(state->file);
    fprintf(state->file, "NS_DECL_%s\n", classNameUpper);
    fputs("\n", state->file);
    write_indent(state->file);
    fprintf(state->file, "%s();\n", classNameImpl);
    fputs("\n"
          "private:\n", state->file);
    write_indent(state->file);
    fprintf(state->file, "~%s();\n", classNameImpl);
    fputs("\n"
          "protected:\n", state->file);
    write_indent(state->file);
    fputs("/* additional members */\n", state->file);
    fputs("};\n\n", state->file);

    fputs("/* Implementation file */\n", state->file);

    fprintf(state->file, 
            "NS_IMPL_ISUPPORTS1(%s, %s)\n", classNameImpl, className);
    fputs("\n", state->file);
    
    fprintf(state->file, "%s::%s()\n", classNameImpl, classNameImpl);
    fputs("{\n", state->file);
    write_indent(state->file);
    fputs("/* member initializers and constructor code */\n", state->file);
    fputs("}\n\n", state->file);
    
    fprintf(state->file, "%s::~%s()\n", classNameImpl, classNameImpl);
    fputs("{\n", state->file);
    write_indent(state->file);
    fputs("/* destructor code */\n", state->file);
    fputs("}\n\n", state->file);

    for (iter = IDL_INTERFACE(state->tree).body;
         iter != NULL;
         iter = IDL_LIST(iter).next)
    {
        IDL_tree data = IDL_LIST(iter).data;

        switch(IDL_NODE_TYPE(data)) {
          case IDLN_OP_DCL:
            /* It would be nice to remove this state-twiddling. */
            orig = state->tree; 
            state->tree = data;
            xpidl_write_comment(state, 0);
            state->tree = orig;

            write_method_signature(data, state->file, AS_IMPL, classNameImpl);
            fputs("\n{\n", state->file);
            write_indent(state->file);
            write_indent(state->file);
            fputs("return NS_ERROR_NOT_IMPLEMENTED;\n"
                  "}\n"
                  "\n", state->file);
            break;

          case IDLN_ATTR_DCL:
            /* It would be nice to remove this state-twiddling. */
            orig = state->tree; 
            state->tree = data;
            xpidl_write_comment(state, 0);
            state->tree = orig;
            
            if (!write_attr_accessor(data, state->file, TRUE, 
                                     AS_IMPL, classNameImpl))
                FAIL;
            fputs("\n{\n", state->file);
            write_indent(state->file);
            write_indent(state->file);
            fputs("return NS_ERROR_NOT_IMPLEMENTED;\n"
                  "}\n", state->file);

            if (!IDL_ATTR_DCL(data).f_readonly) {
                if (!write_attr_accessor(data, state->file, FALSE, 
                                         AS_IMPL, classNameImpl))
                    FAIL;
                fputs("\n{\n", state->file);
                write_indent(state->file);
                write_indent(state->file);
                fputs("return NS_ERROR_NOT_IMPLEMENTED;\n"
                      "}\n", state->file);
            }
            fputs("\n", state->file);
            break;

          case IDLN_CONST_DCL:
          case IDLN_CODEFRAG:
              continue;

          default:
            FAIL;
        }
    }

    fputs("/* End of implementation class template. */\n"
          "#endif\n"
          "\n", state->file);

#undef FAIL

out:
    if (classNameUpper)
        free(classNameUpper);
    if (classNameImpl)
        free(classNameImpl);
    return ok;
}

static gboolean
list(TreeState *state)
{
    IDL_tree iter;
    for (iter = state->tree; iter; iter = IDL_LIST(iter).next) {
        state->tree = IDL_LIST(iter).data;
        if (!xpidl_process_node(state))
            return FALSE;
    }
    return TRUE;
}

static gboolean
write_type(IDL_tree type_tree, gboolean is_out, FILE *outfile)
{
    if (!type_tree) {
        fputs("void", outfile);
        return TRUE;
    }

    switch (IDL_NODE_TYPE(type_tree)) {
      case IDLN_TYPE_INTEGER: {
        gboolean sign = IDL_TYPE_INTEGER(type_tree).f_signed;
        switch (IDL_TYPE_INTEGER(type_tree).f_type) {
          case IDL_INTEGER_TYPE_SHORT:
            fputs(sign ? "PRInt16" : "PRUint16", outfile);
            break;
          case IDL_INTEGER_TYPE_LONG:
            fputs(sign ? "PRInt32" : "PRUint32", outfile);
            break;
          case IDL_INTEGER_TYPE_LONGLONG:
            fputs(sign ? "PRInt64" : "PRUint64", outfile);
            break;
          default:
            g_error("Unknown integer type %d\n",
                    IDL_TYPE_INTEGER(type_tree).f_type);
            return FALSE;
        }
        break;
      }
      case IDLN_TYPE_CHAR:
        fputs("char", outfile);
        break;
      case IDLN_TYPE_WIDE_CHAR:
        fputs("PRUnichar", outfile); /* wchar_t? */
        break;
      case IDLN_TYPE_WIDE_STRING:
        fputs("PRUnichar *", outfile);
        break;
      case IDLN_TYPE_STRING:
        fputs("char *", outfile);
        break;
      case IDLN_TYPE_BOOLEAN:
        fputs("PRBool", outfile);
        break;
      case IDLN_TYPE_OCTET:
        fputs("PRUint8", outfile);
        break;
      case IDLN_TYPE_FLOAT:
        switch (IDL_TYPE_FLOAT(type_tree).f_type) {
          case IDL_FLOAT_TYPE_FLOAT:
            fputs("float", outfile);
            break;
          case IDL_FLOAT_TYPE_DOUBLE:
            fputs("double", outfile);
            break;
          /* XXX 'long double' just ignored, or what? */
          default:
            fprintf(outfile, "unknown_type_%d", IDL_NODE_TYPE(type_tree));
            break;
        }
        break;
      case IDLN_IDENT:
        if (UP_IS_NATIVE(type_tree)) {
            if (IDL_tree_property_get(type_tree, "domstring") ||
                IDL_tree_property_get(type_tree, "astring")) {
                fputs("nsAString", outfile);
            } else if (IDL_tree_property_get(type_tree, "utf8string")) {
                fputs("nsACString", outfile);
            } else if (IDL_tree_property_get(type_tree, "cstring")) {
                fputs("nsACString", outfile);
            } else {
                fputs(IDL_NATIVE(IDL_NODE_UP(type_tree)).user_type, outfile);
            }
            if (IDL_tree_property_get(type_tree, "ptr")) {
                fputs(" *", outfile);
            } else if (IDL_tree_property_get(type_tree, "ref")) {
                fputs(" &", outfile);
            }
        } else {
            fputs(IDL_IDENT(type_tree).str, outfile);
        }
        if (UP_IS_AGGREGATE(type_tree))
            fputs(" *", outfile);
        break;
      default:
        fprintf(outfile, "unknown_type_%d", IDL_NODE_TYPE(type_tree));
        break;
    }
    return TRUE;
}

/*
 * An attribute declaration looks like:
 *
 * [ IDL_ATTR_DCL]
 *   - param_type_spec [IDL_TYPE_* or NULL for void]
 *   - simple_declarations [IDL_LIST]
 *     - data [IDL_IDENT]
 *     - next [IDL_LIST or NULL if no more idents]
 *       - data [IDL_IDENT]
 */

#define ATTR_IDENT(tree) (IDL_IDENT(IDL_LIST(IDL_ATTR_DCL(tree).simple_declarations).data))
#define ATTR_TYPE_DECL(tree) (IDL_ATTR_DCL(tree).param_type_spec)
#define ATTR_TYPE(tree) (IDL_NODE_TYPE(ATTR_TYPE_DECL(tree)))

/*
 *  AS_DECL writes 'NS_IMETHOD foo(string bar, long sil)'
 *  AS_IMPL writes 'NS_IMETHODIMP className::foo(string bar, long sil)'
 *  AS_CALL writes 'foo(bar, sil)'
 */
static gboolean
write_attr_accessor(IDL_tree attr_tree, FILE * outfile,
                    gboolean getter, int mode, const char *className)
{
    char *attrname = ATTR_IDENT(attr_tree).str;

    if (mode == AS_DECL) {
        fputs("NS_IMETHOD ", outfile);
    } else if (mode == AS_IMPL) {
        fprintf(outfile, "NS_IMETHODIMP %s::", className);
    }
    fprintf(outfile, "%cet%c%s(",
            getter ? 'G' : 'S',
            toupper(*attrname), attrname + 1);
    if (mode == AS_DECL || mode == AS_IMPL) {
        /* Setters for string, wstring, nsid, domstring, utf8string, 
         * cstring and astring get const. 
         */
        if (!getter &&
            (IDL_NODE_TYPE(ATTR_TYPE_DECL(attr_tree)) == IDLN_TYPE_STRING ||
             IDL_NODE_TYPE(ATTR_TYPE_DECL(attr_tree)) == IDLN_TYPE_WIDE_STRING ||
             IDL_tree_property_get(ATTR_TYPE_DECL(attr_tree), "nsid") ||
             IDL_tree_property_get(ATTR_TYPE_DECL(attr_tree), "domstring")  ||
             IDL_tree_property_get(ATTR_TYPE_DECL(attr_tree), "utf8string") ||
             IDL_tree_property_get(ATTR_TYPE_DECL(attr_tree), "cstring")    ||
             IDL_tree_property_get(ATTR_TYPE_DECL(attr_tree), "astring")))
        {
            fputs("const ", outfile);
        }

        if (!write_type(ATTR_TYPE_DECL(attr_tree), getter, outfile))
            return FALSE;
        fprintf(outfile, "%s%s",
                (STARRED_TYPE(attr_tree) ? "" : " "),
                (getter && !DIPPER_TYPE(ATTR_TYPE_DECL(attr_tree)))? "*" : "");
    }
    fprintf(outfile, "a%c%s)", toupper(attrname[0]), attrname + 1);
    return TRUE;
}

static gboolean
attr_dcl(TreeState *state)
{
    GSList *doc_comments;

    if (!verify_attribute_declaration(state->tree))
        return FALSE;

    doc_comments =
        IDL_IDENT(IDL_LIST(IDL_ATTR_DCL
                           (state->tree).simple_declarations).data).comments;

    if (doc_comments != NULL) {
        write_indent(state->file);
        printlist(state->file, doc_comments);
    }

    /*
     * XXX lists of attributes with the same type, e.g.
     * attribute string foo, bar sil;
     * are legal IDL... but we don't do anything with 'em.
     */
    if (IDL_LIST(IDL_ATTR_DCL(state->tree).simple_declarations).next != NULL) {
        XPIDL_WARNING((state->tree, IDL_WARNING1,
                       "multiple attributes in a single declaration aren't "
                       "currently supported by xpidl"));
    }

    xpidl_write_comment(state, 2);

    write_indent(state->file);
    if (!write_attr_accessor(state->tree, state->file, TRUE, AS_DECL, NULL))
        return FALSE;
    fputs(" = 0;\n", state->file);

    if (!IDL_ATTR_DCL(state->tree).f_readonly) {
        write_indent(state->file);
        if (!write_attr_accessor(state->tree, state->file, FALSE, AS_DECL, NULL))
            return FALSE;
        fputs(" = 0;\n", state->file);
    }
    fputc('\n', state->file);

    return TRUE;
}

static gboolean
do_enum(TreeState *state)
{
    IDL_tree_error(state->tree, "enums not supported, "
                   "see http://bugzilla.mozilla.org/show_bug.cgi?id=8781");
    return FALSE;
}

static gboolean
do_const_dcl(TreeState *state)
{
    struct _IDL_CONST_DCL *dcl = &IDL_CONST_DCL(state->tree);
    const char *name = IDL_IDENT(dcl->ident).str;
    gboolean is_signed;
    GSList *doc_comments = IDL_IDENT(dcl->ident).comments;
    IDL_tree real_type;
    const char *const_format;

    if (!verify_const_declaration(state->tree))
        return FALSE;

    if (doc_comments != NULL) {
        write_indent(state->file);
        printlist(state->file, doc_comments);
    }

    /* Could be a typedef; try to map it to the real type. */
    real_type = find_underlying_type(dcl->const_type);
    real_type = real_type ? real_type : dcl->const_type;
    is_signed = IDL_TYPE_INTEGER(real_type).f_signed;

    const_format = is_signed ? "%" IDL_LL "d" : "%" IDL_LL "uU";
    write_indent(state->file);
    fprintf(state->file, "enum { %s = ", name);
    fprintf(state->file, const_format, IDL_INTEGER(dcl->const_exp).value);
    fprintf(state->file, " };\n\n");

    return TRUE;
}

static gboolean
do_typedef(TreeState *state)
{
    IDL_tree type = IDL_TYPE_DCL(state->tree).type_spec;
    IDL_tree dcls = IDL_TYPE_DCL(state->tree).dcls;
    IDL_tree complex;
    GSList *doc_comments;

    if (IDL_NODE_TYPE(type) == IDLN_TYPE_SEQUENCE) {
        XPIDL_WARNING((state->tree, IDL_WARNING1,
                       "sequences not supported, ignored"));
    } else {
        if (IDL_NODE_TYPE(complex = IDL_LIST(dcls).data) == IDLN_TYPE_ARRAY) {
            IDL_tree dim = IDL_TYPE_ARRAY(complex).size_list;
            doc_comments = IDL_IDENT(IDL_TYPE_ARRAY(complex).ident).comments;

            if (doc_comments != NULL)
                printlist(state->file, doc_comments);

            fputs("typedef ", state->file);
            if (!write_type(type, FALSE, state->file))
                return FALSE;
            fputs(" ", state->file);

            fprintf(state->file, "%s",
                    IDL_IDENT(IDL_TYPE_ARRAY(complex).ident).str);
            do {
                fputc('[', state->file);
                if (IDL_LIST(dim).data) {
                    fprintf(state->file, "%ld",
                            (long)IDL_INTEGER(IDL_LIST(dim).data).value);
                }
                fputc(']', state->file);
            } while ((dim = IDL_LIST(dim).next) != NULL);
        } else {
            doc_comments = IDL_IDENT(IDL_LIST(dcls).data).comments;

            if (doc_comments != NULL)
                printlist(state->file, doc_comments);

            fputs("typedef ", state->file);
            if (!write_type(type, FALSE, state->file))
                return FALSE;
            fputs(" ", state->file);
            fputs(IDL_IDENT(IDL_LIST(dcls).data).str, state->file);
        }
        fputs(";\n\n", state->file);
    }
    return TRUE;
}

/*
 * param generation:
 * in string foo        -->     nsString *foo
 * out string foo       -->     nsString **foo;
 * inout string foo     -->     nsString **foo;
 */

/* If notype is true, just write the param name. */
static gboolean
write_param(IDL_tree param_tree, FILE *outfile)
{
    IDL_tree param_type_spec = IDL_PARAM_DCL(param_tree).param_type_spec;
    gboolean is_in = IDL_PARAM_DCL(param_tree).attr == IDL_PARAM_IN;
    /* in string, wstring, nsid, domstring, utf8string, cstring and 
     * astring any explicitly marked [const] are const 
     */

    if (is_in &&
        (IDL_NODE_TYPE(param_type_spec) == IDLN_TYPE_STRING ||
         IDL_NODE_TYPE(param_type_spec) == IDLN_TYPE_WIDE_STRING ||
         IDL_tree_property_get(IDL_PARAM_DCL(param_tree).simple_declarator,
                               "const") ||
         IDL_tree_property_get(param_type_spec, "nsid") ||
         IDL_tree_property_get(param_type_spec, "domstring")  ||
         IDL_tree_property_get(param_type_spec, "utf8string") ||
         IDL_tree_property_get(param_type_spec, "cstring")    ||
         IDL_tree_property_get(param_type_spec, "astring"))) {
        fputs("const ", outfile);
    }
    else if (IDL_PARAM_DCL(param_tree).attr == IDL_PARAM_OUT &&
             IDL_tree_property_get(IDL_PARAM_DCL(param_tree).simple_declarator, 
                                   "shared")) {
        fputs("const ", outfile);
    }

    if (!write_type(param_type_spec, !is_in, outfile))
        return FALSE;

    /* unless the type ended in a *, add a space */
    if (!STARRED_TYPE(param_type_spec))
        fputc(' ', outfile);

    /* out and inout params get a bonus '*' (unless this is type that has a 
     * 'dipper' class that is passed in to receive 'out' data) 
     */
    if (IDL_PARAM_DCL(param_tree).attr != IDL_PARAM_IN &&
        !DIPPER_TYPE(param_type_spec)) {
        fputc('*', outfile);
    }
    /* arrays get a bonus * too */
    /* XXX Should this be a leading '*' or a trailing "[]" ?*/
    if (IDL_tree_property_get(IDL_PARAM_DCL(param_tree).simple_declarator,
                              "array"))
        fputc('*', outfile);

    fputs(IDL_IDENT(IDL_PARAM_DCL(param_tree).simple_declarator).str, outfile);

    return TRUE;
}

/*
 * A forward declaration, usually an interface.
 */
static gboolean
forward_dcl(TreeState *state)
{
    IDL_tree iface = state->tree;
    const char *className = IDL_IDENT(IDL_FORWARD_DCL(iface).ident).str;

    if (!className)
        return FALSE;

    fprintf(state->file, "class %s; /* forward declaration */\n\n", className);
    return TRUE;
}

/*
 * Shared between the interface class declaration and the NS_DECL_IFOO macro
 * provided to aid declaration of implementation classes.  
 * mode...
 *  AS_DECL writes 'NS_IMETHOD foo(string bar, long sil)'
 *  AS_IMPL writes 'NS_IMETHODIMP className::foo(string bar, long sil)'
 *  AS_CALL writes 'foo(bar, sil)'
 */
static gboolean
write_method_signature(IDL_tree method_tree, FILE *outfile, int mode,
                       const char *className)
{
    struct _IDL_OP_DCL *op = &IDL_OP_DCL(method_tree);
    gboolean no_generated_args = TRUE;
    gboolean op_notxpcom =
        (IDL_tree_property_get(op->ident, "notxpcom") != NULL);
    const char *name;
    IDL_tree iter;

    if (mode == AS_DECL) {
        if (op_notxpcom) {
            fputs("NS_IMETHOD_(", outfile);
            if (!write_type(op->op_type_spec, FALSE, outfile))
                return FALSE;
            fputc(')', outfile);
        } else {
            fputs("NS_IMETHOD", outfile);
        }
        fputc(' ', outfile);
    }
    else if (mode == AS_IMPL) {
        if (op_notxpcom) {
            fputs("NS_IMETHODIMP_(", outfile);
            if (!write_type(op->op_type_spec, FALSE, outfile))
                return FALSE;
            fputc(')', outfile);
        } else {
            fputs("NS_IMETHODIMP", outfile);
        }
        fputc(' ', outfile);
    }
    name = IDL_IDENT(op->ident).str;
    if (mode == AS_IMPL) {
        fprintf(outfile, "%s::%c%s(", className, toupper(*name), name + 1);
    } else {
        fprintf(outfile, "%c%s(", toupper(*name), name + 1);
    }
    for (iter = op->parameter_dcls; iter; iter = IDL_LIST(iter).next) {
        if (mode == AS_DECL || mode == AS_IMPL) {
            if (!write_param(IDL_LIST(iter).data, outfile))
                return FALSE;
        } else {
            fputs(IDL_IDENT(IDL_PARAM_DCL(IDL_LIST(iter).data)
                            .simple_declarator).str,
                  outfile);
        }
        if ((IDL_LIST(iter).next ||
             (!op_notxpcom && op->op_type_spec) || op->f_varargs))
            fputs(", ", outfile);
        no_generated_args = FALSE;
    }

    /* make IDL return value into trailing out argument */
    if (op->op_type_spec && !op_notxpcom) {
        IDL_tree fake_param = IDL_param_dcl_new(IDL_PARAM_OUT,
                                                op->op_type_spec,
                                                IDL_ident_new("_retval"));
        if (!fake_param)
            return FALSE;
        if (mode == AS_DECL || mode == AS_IMPL) {
            if (!write_param(fake_param, outfile))
                return FALSE;
        } else {
            fputs("_retval", outfile);
        }
        if (op->f_varargs)
            fputs(", ", outfile);
        no_generated_args = FALSE;
    }

    /* varargs go last */
    if (op->f_varargs) {
        if (mode == AS_DECL || mode == AS_IMPL) {
            fputs("nsVarArgs *", outfile);
        }
        fputs("_varargs", outfile);
        no_generated_args = FALSE;
    }

    /*
     * If generated method has no arguments, output 'void' to avoid C legacy
     * behavior of disabling type checking.
     */
    if (no_generated_args && mode == AS_DECL) {
        fputs("void", outfile);
    }

    fputc(')', outfile);

    return TRUE;
}

/*
 * A method is an `operation', therefore a method decl is an `op dcl'.
 * I blame Elliot.
 */
static gboolean
op_dcl(TreeState *state)
{
    GSList *doc_comments = IDL_IDENT(IDL_OP_DCL(state->tree).ident).comments;

    /*
     * Verify that e.g. non-scriptable methods in [scriptable] interfaces
     * are declared so.  Do this in a separate verification pass?
     */
    if (!verify_method_declaration(state->tree))
        return FALSE;

    if (doc_comments != NULL) {
        write_indent(state->file);
        printlist(state->file, doc_comments);
    }
    xpidl_write_comment(state, 2);

    write_indent(state->file);
    if (!write_method_signature(state->tree, state->file, AS_DECL, NULL))
        return FALSE;
    fputs(" = 0;\n\n", state->file);

    return TRUE;
}

static void
write_codefrag_line(gpointer data, gpointer user_data)
{
    TreeState *state = (TreeState *)user_data;
    const char *line = (const char *)data;
    fputs(line, state->file);
    fputc('\n', state->file);
}

static gboolean
codefrag(TreeState *state)
{
    const char *desc = IDL_CODEFRAG(state->tree).desc;
    GSList *lines = IDL_CODEFRAG(state->tree).lines;
    guint fragment_length;
    
    if (strcmp(desc, "C++") && /* libIDL bug? */ strcmp(desc, "C++\r")) {
        XPIDL_WARNING((state->tree, IDL_WARNING1,
                       "ignoring '%%{%s' escape. "
                       "(Use '%%{C++' to escape verbatim C++ code.)", desc));

        return TRUE;
    }

    /*
     * Emit #file directive to point debuggers back to the original .idl file
     * for the duration of the code fragment.  We look at internal IDL node
     * properties _file, _line to do this; hopefully they won't change.
     *
     * _line seems to refer to the line immediately after the closing %}, so
     * we backtrack to get the proper line for the beginning of the block.
     */
    /*
     * Looks like getting this right means maintaining an accurate line
     * count of everything generated, so we can set the file back to the
     * correct line in the generated file afterwards.  Skipping for now...
     */

    fragment_length = g_slist_length(lines);
/*      fprintf(state->file, "#line %d \"%s\"\n", */
/*              state->tree->_line - fragment_length - 1, */
/*              state->tree->_file); */

    g_slist_foreach(lines, write_codefrag_line, (gpointer)state);

    return TRUE;
}

backend *
xpidl_header_dispatch(void)
{
    static backend result;
    static nodeHandler table[IDLN_LAST];
    static gboolean initialized = FALSE;
    
    result.emit_prolog = header_prolog;
    result.emit_epilog = header_epilog;

    if (!initialized) {
        table[IDLN_LIST] = list;
        table[IDLN_ATTR_DCL] = attr_dcl;
        table[IDLN_OP_DCL] = op_dcl;
        table[IDLN_FORWARD_DCL] = forward_dcl;
        table[IDLN_TYPE_ENUM] = do_enum;
        table[IDLN_INTERFACE] = interface;
        table[IDLN_CODEFRAG] = codefrag;
        table[IDLN_TYPE_DCL] = do_typedef;
        table[IDLN_CONST_DCL] = do_const_dcl;
        table[IDLN_NATIVE] = check_native;
        initialized = TRUE;
    }

    result.dispatch_table = table;
    return &result;
}
