# Copyright (C) 2008 Luke Kenneth Casson Leighton <lkcl@lkcl.net>
# Copyright (C) 2008 Martin Soto <soto@freedesktop.org>
# Copyright (C) 2008 Alp Toker <alp@atoker.com>
# Copyright (C) 2009 Adam Dingle <adam@yorba.org>
# Copyright (C) 2009 Jim Nelson <jim@yorba.org>
# Copyright (C) 2009, 2010 Igalia S.L.
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Library General Public
# License as published by the Free Software Foundation; either
# version 2 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# Library General Public License for more details.
#
# You should have received a copy of the GNU Library General Public License
# along with this library; see the file COPYING.LIB.  If not, write to
# the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
# Boston, MA 02111-1307, USA.

package CodeGeneratorGObject;

use constant FileNamePrefix => "WebKitDOM";

# Global Variables
my %implIncludes = ();
my %hdrIncludes = ();

my $defineTypeMacro = "G_DEFINE_TYPE";
my $defineTypeInterfaceImplementation = ")";
my @txtEventListeners = ();
my @txtInstallProps = ();
my @txtSetProps = ();
my @txtGetProps = ();

my $className = "";

# FIXME: this should be replaced with a function that recurses up the tree
# to find the actual base type.
my %baseTypeHash = ("Object" => 1, "Node" => 1, "NodeList" => 1, "NamedNodeMap" => 1, "DOMImplementation" => 1,
                    "Event" => 1, "CSSRule" => 1, "CSSValue" => 1, "StyleSheet" => 1, "MediaList" => 1,
                    "Counter" => 1, "Rect" => 1, "RGBColor" => 1, "XPathExpression" => 1, "XPathResult" => 1,
                    "NodeIterator" => 1, "TreeWalker" => 1, "AbstractView" => 1, "Blob" => 1, "DOMTokenList" => 1,
                    "HTMLCollection" => 1);

# Only objects derived from Node are released by the DOM object cache and can be
# transfer none. Ideally we could use GetBaseClass with the parent type to check
# whether it's Node, but unfortunately we only have the name of the return type,
# and we can't know its parent base class. Since there are fewer classes in the
# API that are not derived from Node, we will list them here to decide the
# transfer type.
my %transferFullTypeHash = ("AudioTrack" => 1, "AudioTrackList" => 1, "BarProp" => 1, "BatteryManager" => 1,
    "CSSRuleList" => 1, "CSSStyleDeclaration" => 1, "CSSStyleSheet" => 1,
    "DOMApplicationCache" => 1, "DOMMimeType" => 1, "DOMMimeTypeArray" => 1, "DOMNamedFlowCollection" => 1,
    "DOMPlugin" => 1, "DOMPluginArray" => 1, "DOMSecurityPolicy" => 1,
    "DOMSelection" => 1, "DOMSettableTokenList" => 1, "DOMStringList" => 1,
    "DOMWindow" => 1, "DOMWindowCSS" => 1, "EventTarget" => 1,
    "File" => 1, "FileList" => 1, "Gamepad" => 1, "GamepadList" => 1,
    "Geolocation" => 1, "HTMLOptionsCollection" => 1, "History" => 1,
    "KeyboardEvent" => 1, "MediaError" => 1, "MediaController" => 1,
    "MouseEvent" => 1, "MediaQueryList" => 1, "Navigator" => 1, "NodeFilter" => 1,
    "Performance" => 1, "PerformanceEntry" => 1, "PerformanceEntryList" => 1, "PerformanceNavigation" => 1, "PerformanceTiming" => 1,
    "Range" => 1, "Screen" => 1, "SpeechSynthesis" => 1, "SpeechSynthesisVoice" => 1,
    "Storage" => 1, "StyleMedia" => 1, "TextTrack" => 1, "TextTrackCueList" => 1,
    "TimeRanges" => 1, "Touch" => 1, "UIEvent" => 1, "UserMessageHandler" => 1, "UserMessageHandlersNamespace" => 1,
    "ValidityState" => 1, "VideoTrack" => 1, "WebKitNamedFlow" => 1,
    "WebKitNamespace" => 1, "WebKitPoint" => 1, "WheelEvent" => 1, "XPathNSResolver" => 1);

# List of function parameters that are allowed to be NULL
my $canBeNullParams = {
    'webkit_dom_document_create_attribute_ns' => ['namespaceURI'],
    'webkit_dom_document_create_element_ns' => ['namespaceURI'],
    'webkit_dom_document_create_entity_reference' => ['name'],
    'webkit_dom_document_evaluate' => ['inResult', 'resolver'],
    'webkit_dom_document_get_override_style' => ['pseudoElement'],
    'webkit_dom_dom_implementation_create_document' => ['namespaceURI', 'doctype'],
    'webkit_dom_dom_window_get_computed_style' => ['pseudoElement'],
    'webkit_dom_element_set_attribute_ns' => ['namespaceURI'],
    'webkit_dom_node_insert_before' => ['refChild'],
};

# Default constructor
sub new {
    my $object = shift;
    my $reference = { };

    $codeGenerator = shift;

    bless($reference, $object);
}

my $licenceTemplate = << "EOF";
/*
 *  This file is part of the WebKit open source project.
 *  This file has been generated by generate-bindings.pl. DO NOT MODIFY!
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Library General Public
 *  License as published by the Free Software Foundation; either
 *  version 2 of the License, or (at your option) any later version.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Library General Public License for more details.
 *
 *  You should have received a copy of the GNU Library General Public License
 *  along with this library; see the file COPYING.LIB.  If not, write to
 *  the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 *  Boston, MA 02110-1301, USA.
 */
EOF

sub GetParentClassName {
    my $interface = shift;

    return "WebKitDOMObject" unless $interface->parent;
    return "WebKitDOM" . $interface->parent;
}

sub GetParentImplClassName {
    my $interface = shift;

    return "Object" unless $interface->parent;
    return $interface->parent;
}

sub IsBaseType
{
    my $type = shift;

    return 1 if $baseTypeHash{$type};
    return 0;
}

sub GetBaseClass
{
    $parent = shift;
    $interface = shift;

    return $parent if $parent eq "Object" or IsBaseType($parent);
    return "Event" if $codeGenerator->InheritsInterface($interface, "Event");
    return "CSSValue" if $parent eq "SVGColor" or $parent eq "CSSValueList";
    return "Node";
}


# From String::CamelCase 0.01
sub camelize
{
        my $s = shift;
        join('', map{ ucfirst $_ } split(/(?<=[A-Za-z])_(?=[A-Za-z])|\b/, $s));
}

sub decamelize
{
        my $s = shift;
        $s =~ s{([^a-zA-Z]?)([A-Z]*)([A-Z])([a-z]?)}{
                my $fc = pos($s)==0;
                my ($p0,$p1,$p2,$p3) = ($1,lc$2,lc$3,$4);
                my $t = $p0 || $fc ? $p0 : '_';
                $t .= $p3 ? $p1 ? "${p1}_$p2$p3" : "$p2$p3" : "$p1$p2";
                $t;
        }ge;

        # Some strings are not correctly decamelized, apply fix ups
        for ($s) {
            s/domcss/dom_css/;
            s/domhtml/dom_html/;
            s/domdom/dom_dom/;
            s/domcdata/dom_cdata/;
            s/domui/dom_ui/;
            s/x_path/xpath/;
            s/web_kit/webkit/;
            s/htmli_frame/html_iframe/;
        }
        return $s;
}

sub HumanReadableConditional {
    my @conditional = split('_', shift);
    my @upperCaseExceptions = ("SQL", "API");
    my @humanReadable;

    for $part (@conditional) {
        if (!grep {$_ eq $part} @upperCaseExceptions) {
            $part = camelize(lc($part));
        }
        push(@humanReadable, $part);
    }

    return join(' ', @humanReadable);
}

sub GetParentGObjType {
    my $interface = shift;

    return "WEBKIT_TYPE_DOM_OBJECT" unless $interface->parent;
    return "WEBKIT_TYPE_DOM_" . uc(decamelize(($interface->parent)));
}

sub GetClassName {
    my $name = shift;

    return "WebKitDOM$name";
}

sub GetCoreObject {
    my ($interfaceName, $name, $parameter) = @_;

    return "WebCore::${interfaceName}* $name = WebKit::core($parameter);";
}

sub SkipAttribute {
    my $attribute = shift;

    if ($attribute->signature->extendedAttributes->{"Custom"}
        || $attribute->signature->extendedAttributes->{"CustomGetter"}) {
        return 1;
    }

    my $propType = $attribute->signature->type;
    if ($propType =~ /Constructor$/) {
        return 1;
    }

    return 1 if $attribute->isStatic;
    return 1 if $codeGenerator->IsTypedArrayType($propType);

    $codeGenerator->AssertNotSequenceType($propType);

    if ($codeGenerator->GetArrayType($propType)) {
        return 1;
    }

    if ($codeGenerator->IsEnumType($propType)) {
        return 1;
    }

    # This is for DOMWindow.idl location attribute
    if ($attribute->signature->name eq "location") {
        return 1;
    }

    # This is for HTMLInput.idl valueAsDate
    if ($attribute->signature->name eq "valueAsDate") {
        return 1;
    }

    # This is for DOMWindow.idl Crypto attribute
    if ($attribute->signature->type eq "Crypto") {
        return 1;
    }

    if ($attribute->signature->type eq "EventListener") {
        return 1;
    }

    if ($attribute->signature->type eq "MediaQueryListListener") {
        return 1;
    }

    # Skip indexed database attributes for now, they aren't yet supported for the GObject generator.
    if ($attribute->signature->name =~ /^(?:webkit)?[Ii]ndexedDB/ or $attribute->signature->name =~ /^(?:webkit)?IDB/) {
        return 1;
    }

    return 0;
}

sub SkipFunction {
    my $object = shift;
    my $function = shift;
    my $parentNode = shift;
    my $decamelize = shift;
    my $prefix = shift;

    my $functionName = "webkit_dom_" . $decamelize . "_" . $prefix . decamelize($function->signature->name);
    my $functionReturnType = $prefix eq "set_" ? "void" : $function->signature->type;
    my $isCustomFunction = $function->signature->extendedAttributes->{"Custom"};
    my $callWith = $function->signature->extendedAttributes->{"CallWith"};
    my $isUnsupportedCallWith = $codeGenerator->ExtendedAttributeContains($callWith, "ScriptArguments") || $codeGenerator->ExtendedAttributeContains($callWith, "CallStack");

    if (($isCustomFunction || $isUnsupportedCallWith) &&
        $functionName ne "webkit_dom_node_replace_child" &&
        $functionName ne "webkit_dom_node_insert_before" &&
        $functionName ne "webkit_dom_node_remove_child" &&
        $functionName ne "webkit_dom_node_append_child" &&
        $functionName ne "webkit_dom_html_collection_item" &&
        $functionName ne "webkit_dom_html_collection_named_item") {
        return 1;
    }

    # Skip functions that have callback parameters, because this code generator doesn't know
    # how to auto-generate callbacks.  Skip functions that have "MediaQueryListListener" or
    # sequence<T> parameters, because this code generator doesn't know how to auto-generate
    # MediaQueryListListener or sequence<T>. Skip EventListeners because they are handled elsewhere.
    foreach my $param (@{$function->parameters}) {
        if ($codeGenerator->IsCallbackInterface($param->type) ||
            $param->extendedAttributes->{"Clamp"} ||
            $param->type eq "MediaQueryListListener" ||
            $param->type eq "EventListener" ||
            $codeGenerator->GetSequenceType($param->type)) {
            return 1;
        }
    }

    # Skip functions for which we have a custom implementation due to API breaks
    if ($functionName eq "webkit_dom_html_media_element_set_current_time") {
        return 1;
    }

    # This is for DataTransferItemList.idl add(File) method
    if ($functionName eq "webkit_dom_data_transfer_item_list_add" && @{$function->parameters} == 1) {
        return 1;
    }

    # Skip dispatch_event methods, except the one already deprecated.
    if ($parentNode->extendedAttributes->{"EventTarget"} && $function->signature->name eq "dispatchEvent"
        && $functionName ne "webkit_dom_audio_track_list_dispatch_event"
        && $functionName ne "webkit_dom_battery_manager_dispatch_event"
        && $functionName ne "webkit_dom_dom_application_cache_dispatch_event"
        && $functionName ne "webkit_dom_dom_window_dispatch_event"
        && $functionName ne "webkit_dom_node_dispatch_event"
        && $functionName ne "webkit_dom_text_track_cue_dispatch_event"
        && $functionName ne "webkit_dom_text_track_dispatch_event"
        && $functionName ne "webkit_dom_text_track_list_dispatch_event"
        && $functionName ne "webkit_dom_video_track_list_dispatch_event"
        && $functionName ne "webkit_dom_webkit_named_flow_dispatch_event"
        && $functionName ne "webkit_dom_test_event_target_dispatch_event") {
        return 1;
    }

    # Skip Console::profile() and Console::profileEnd() as they're not correctly generated for the moment.
    if ($functionName eq "webkit_dom_console_profile" || $functionName eq "webkit_dom_console_profile_end") {
        return 1;
    }

    if ($function->signature->name eq "set" and $parentNode->extendedAttributes->{"TypedArray"}) {
        return 1;
    }

    if ($object eq "MediaQueryListListener") {
        return 1;
    }

    if ($function->signature->name eq "getSVGDocument") {
        return 1;
    }

    if ($function->signature->name eq "getCSSCanvasContext") {
        return 1;
    }

    if ($function->signature->name eq "setRangeText" && @{$function->parameters} == 1) {
        return 1;
    }

    if ($function->signature->name eq "timeEnd") {
        return 1;
    }

    if ($codeGenerator->GetSequenceType($functionReturnType)) {
        return 1;
    }

    if ($function->signature->name eq "supports" && @{$function->parameters} == 1) {
        return 1;
    }

    return 0;
}

# Name type used in the g_value_{set,get}_* functions
sub GetGValueTypeName {
    my $type = shift;

    my %types = ("DOMString", "string",
                 "DOMTimeStamp", "uint",
                 "float", "float",
                 "double", "double",
                 "boolean", "boolean",
                 "char", "char",
                 "long", "long",
                 "long long", "int64",
                 "byte", "int8",
                 "octet", "uint8",
                 "short", "int",
                 "uchar", "uchar",
                 "unsigned", "uint",
                 "int", "int",
                 "unsigned int", "uint",
                 "unsigned long long", "uint64", 
                 "unsigned long", "ulong",
                 "unsigned short", "uint");

    return $types{$type} ? $types{$type} : "object";
}

# Name type used in C declarations
sub GetGlibTypeName {
    my $type = shift;
    my $name = GetClassName($type);

    my %types = ("DOMString", "gchar*",
                 "DOMTimeStamp", "guint32",
                 "CompareHow", "gushort",
                 "float", "gfloat",
                 "double", "gdouble",
                 "boolean", "gboolean",
                 "char", "gchar",
                 "long", "glong",
                 "long long", "gint64",
                 "byte", "gint8",
                 "octet", "guint8",
                 "short", "gshort",
                 "uchar", "guchar",
                 "unsigned", "guint",
                 "int", "gint",
                 "unsigned int", "guint",
                 "unsigned long", "gulong",
                 "unsigned long long", "guint64",
                 "unsigned short", "gushort",
                 "void", "void");

    return $types{$type} ? $types{$type} : "$name*";
}

sub IsGDOMClassType {
    my $type = shift;

    return 0 if $codeGenerator->IsNonPointerType($type) || $codeGenerator->IsStringType($type);
    return 1;
}

sub IsPropertyReadable {
    my $property = shift;
    return !SkipAttribute($property);
}

sub IsPropertyWriteable {
    my $property = shift;

    if (!IsPropertyReadable($property)) {
        return 0;
    }

    if ($property->isReadOnly) {
        return 0;
    }

    my $gtype = GetGValueTypeName($property->signature->type);
    my $hasGtypeSignature = $gtype eq "boolean" || $gtype eq "float" || $gtype eq "double" ||
                            $gtype eq "int64" || $gtype eq "uint64" ||
                            $gtype eq "long" || $gtype eq "ulong" ||
                            $gtype eq "int" || $gtype eq "uint" ||
                            $gtype eq "short" || $gtype eq "ushort" ||
                            $gtype eq "int8" || $gtype eq "uint8" ||
                            $gtype eq "char" || $gtype eq "uchar" ||
                            $gtype eq "string";
    if (!$hasGtypeSignature) {
        return 0;
    }

    # FIXME: We are not generating setters for 'Replaceable' attributes now, but we should somehow.
    if ($property->signature->extendedAttributes->{"Replaceable"}) {
        return 0;
    }

    if ($property->signature->extendedAttributes->{"CustomSetter"}) {
        return 0;
    }

    return 1;
}

sub GenerateConditionalWarning
{
    my $node = shift;
    my $indentSize = shift;
    if (!$indentSize) {
        $indentSize = 4;
    }

    my $conditional = $node->extendedAttributes->{"Conditional"};
    my @warn;

    if ($conditional) {
        if ($conditional =~ /&/) {
            my @splitConditionals = split(/&/, $conditional);
            foreach $condition (@splitConditionals) {
                push(@warn, "#if !ENABLE($condition)\n");
                push(@warn, ' ' x $indentSize . "WEBKIT_WARN_FEATURE_NOT_PRESENT(\"" . HumanReadableConditional($condition) . "\")\n");
                push(@warn, "#endif\n");
            }
        } elsif ($conditional =~ /\|/) {
            foreach $condition (split(/\|/, $conditional)) {
                push(@warn, ' ' x $indentSize . "WEBKIT_WARN_FEATURE_NOT_PRESENT(\"" . HumanReadableConditional($condition) . "\")\n");
            }
        } else {
            push(@warn, ' ' x $indentSize . "WEBKIT_WARN_FEATURE_NOT_PRESENT(\"" . HumanReadableConditional($conditional) . "\")\n");
        }
    }

    return @warn;
}

sub GenerateProperty {
    my $attribute = shift;
    my $interfaceName = shift;
    my @writeableProperties = @{shift @_};
    my $parentNode = shift;

    my $conditionalString = $codeGenerator->GenerateConditionalString($attribute->signature);
    my @conditionalWarn = GenerateConditionalWarning($attribute->signature, 8);
    my $parentConditionalString = $codeGenerator->GenerateConditionalString($parentNode);
    my @parentConditionalWarn = GenerateConditionalWarning($parentNode, 8);
    my $camelPropName = $attribute->signature->name;
    my $setPropNameFunction = $codeGenerator->WK_ucfirst($camelPropName);
    my $getPropNameFunction = $codeGenerator->WK_lcfirst($camelPropName);
    my $hasGetterException = $attribute->signature->extendedAttributes->{"GetterRaisesException"};
    my $hasSetterException = $attribute->signature->extendedAttributes->{"SetterRaisesException"};

    my $propName = decamelize($camelPropName);
    my $propNameCaps = uc($propName);
    $propName =~ s/_/-/g;
    my ${propEnum} = "PROP_${propNameCaps}";
    push(@cBodyProperties, "    ${propEnum},\n");

    my $propType = $attribute->signature->type;
    my ${propGType} = decamelize($propType);
    my ${ucPropGType} = uc($propGType);

    my $gtype = GetGValueTypeName($propType);
    my $gparamflag = "WEBKIT_PARAM_READABLE";
    my $writeable = IsPropertyWriteable($attribute);

    my $mutableString = "read-only";
    my $hasCustomSetter = $attribute->signature->extendedAttributes->{"CustomSetter"};
    if ($writeable && $hasCustomSetter) {
        $mutableString = "read-only (due to custom functions needed in webkitdom)";
    } elsif ($writeable) {
        $gparamflag = "WEBKIT_PARAM_READWRITE";
        $mutableString = "read-write";
    }

    my $convertFunction = "";
    if ($gtype eq "string") {
        $convertFunction = "WTF::String::fromUTF8";
    }

    my ($getterFunctionName, @getterArguments) = $codeGenerator->GetterExpression(\%implIncludes, $interfaceName, $attribute);
    my ($setterFunctionName, @setterArguments) = $codeGenerator->SetterExpression(\%implIncludes, $interfaceName, $attribute);

    if ($attribute->signature->extendedAttributes->{"ImplementedBy"}) {
        my $implementedBy = $attribute->signature->extendedAttributes->{"ImplementedBy"};
        $implIncludes{"${implementedBy}.h"} = 1;
        push(@setterArguments, "${convertFunction}(g_value_get_$gtype(value))");
        unshift(@getterArguments, "coreSelf");
        unshift(@setterArguments, "coreSelf");
        $getterFunctionName = "WebCore::${implementedBy}::$getterFunctionName";
        $setterFunctionName = "WebCore::${implementedBy}::$setterFunctionName";
    } else {
        push(@setterArguments, "${convertFunction}(g_value_get_$gtype(value))");
        $getterFunctionName = "coreSelf->$getterFunctionName";
        $setterFunctionName = "coreSelf->$setterFunctionName";
    }
    push(@getterArguments, "isNull") if $attribute->signature->isNullable;
    push(@getterArguments, "ec") if $hasGetterException;
    push(@setterArguments, "ec") if $hasSetterException;

    if (grep {$_ eq $attribute} @writeableProperties) {
        push(@txtSetProps, "    case ${propEnum}: {\n");
        push(@txtSetProps, "#if ${parentConditionalString}\n") if $parentConditionalString;
        push(@txtSetProps, "#if ${conditionalString}\n") if $conditionalString;
        push(@txtSetProps, "        WebCore::ExceptionCode ec = 0;\n") if $hasSetterException;
        push(@txtSetProps, "        ${setterFunctionName}(" . join(", ", @setterArguments) . ");\n");
        push(@txtSetProps, "#else\n") if $conditionalString;
        push(@txtSetProps, @conditionalWarn) if scalar(@conditionalWarn);
        push(@txtSetProps, "#endif /* ${conditionalString} */\n") if $conditionalString;
        push(@txtSetProps, "#else\n") if $parentConditionalString;
        push(@txtSetProps, @parentConditionalWarn) if scalar(@parentConditionalWarn);
        push(@txtSetProps, "#endif /* ${parentConditionalString} */\n") if $parentConditionalString;
        push(@txtSetProps, "        break;\n    }\n");
    }

    push(@txtGetProps, "    case ${propEnum}: {\n");
    push(@txtGetProps, "#if ${parentConditionalString}\n") if $parentConditionalString;
    push(@txtGetProps, "#if ${conditionalString}\n") if $conditionalString;
    push(@txtGetProps, "        bool isNull = false;\n") if $attribute->signature->isNullable;
    push(@txtGetProps, "        WebCore::ExceptionCode ec = 0;\n") if $hasGetterException;

    # FIXME: Should we return a default value when isNull == true?

    my $postConvertFunction = "";
    if ($gtype eq "string") {
        push(@txtGetProps, "        g_value_take_string(value, convertToUTF8String(${getterFunctionName}(" . join(", ", @getterArguments) . ")));\n");
    } elsif ($gtype eq "object") {
        push(@txtGetProps, "        RefPtr<WebCore::${propType}> ptr = ${getterFunctionName}(" . join(", ", @getterArguments) . ");\n");
        push(@txtGetProps, "        g_value_set_object(value, WebKit::kit(ptr.get()));\n");
    } else {
        push(@txtGetProps, "        g_value_set_$gtype(value, ${convertFunction}${getterFunctionName}(" . join(", ", @getterArguments) . ")${postConvertFunction});\n");
    }

    push(@txtGetProps, "#else\n") if $conditionalString;
    push(@txtGetProps, @conditionalWarn) if scalar(@conditionalWarn);
    push(@txtGetProps, "#endif /* ${conditionalString} */\n") if $conditionalString;
    push(@txtGetProps, "#else\n") if $parentConditionalString;
    push(@txtGetProps, @parentConditionalWarn) if scalar(@parentConditionalWarn);
    push(@txtGetProps, "#endif /* ${parentConditionalString} */\n") if $parentConditionalString;
    push(@txtGetProps, "        break;\n    }\n");

    my %parameterSpecOptions = ("int" =>     [ "G_MININT", "G_MAXINT", "0" ],
                                "int8" =>    [ "G_MININT8", "G_MAXINT8", "0" ],
                                "boolean" => [ "FALSE" ],
                                "float" =>   [ "-G_MAXFLOAT", "G_MAXFLOAT", "0" ],
                                "double" =>  [ "-G_MAXDOUBLE", "G_MAXDOUBLE", "0" ],
                                "uint64" =>  [ "0", "G_MAXUINT64", "0" ],
                                "long" =>    [ "G_MINLONG", "G_MAXLONG", "0" ],
                                "int64" =>   [ "G_MININT64", "G_MAXINT64", "0" ],
                                "ulong" =>   [ "0", "G_MAXULONG", "0" ],
                                "uint" =>    [ "0", "G_MAXUINT", "0" ],
                                "uint8" =>   [ "0", "G_MAXUINT8", "0" ],
                                "ushort" =>  [ "0", "G_MAXUINT16", "0" ],
                                "uchar" =>   [ "G_MININT8", "G_MAXINT8", "0" ],
                                "char" =>    [ "0", "G_MAXUINT8", "0" ],
                                "string" =>  [ '""', ],
                                "object" =>  [ "WEBKIT_TYPE_DOM_${ucPropGType}" ]);

    my $extraParameters = join(", ", @{$parameterSpecOptions{$gtype}});
    my $glibTypeName = GetGlibTypeName($propType);
    my $txtInstallProp = << "EOF";
    g_object_class_install_property(
        gobjectClass,
        $propEnum,
        g_param_spec_$gtype(
            "$propName",
            "$interfaceName:$propName",
            "$mutableString $glibTypeName $interfaceName:$propName",
            $extraParameters,
            $gparamflag));

EOF
    push(@txtInstallProps, $txtInstallProp);
}

sub GenerateProperties {
    my ($object, $interfaceName, $interface) = @_;

    my $decamelize = decamelize($interfaceName);
    my $clsCaps = uc($decamelize);
    my $lowerCaseIfaceName = "webkit_dom_$decamelize";
    my $parentImplClassName = GetParentImplClassName($interface);

    my $conditionGuardStart = "";
    my $conditionGuardEnd = "";
    my $conditionalString = $codeGenerator->GenerateConditionalString($interface);
    if ($conditionalString) {
        $conditionGuardStart = "#if ${conditionalString}";
        $conditionGuardEnd = "#endif // ${conditionalString}";
    }

    # Properties
    my $implContent = "";
    my @readableProperties = grep { IsPropertyReadable($_) } @{$interface->attributes};
    my @writeableProperties = grep { IsPropertyWriteable($_) } @{$interface->attributes};
    my $numProperties = scalar @readableProperties;

    # Properties
    my $privFunction = GetCoreObject($interfaceName, "coreSelf", "self");
    if ($numProperties > 0) {
        $implContent = << "EOF";
enum {
    PROP_0,
EOF
        push(@cBodyProperties, $implContent);

        my $txtGetProp = << "EOF";
static void ${lowerCaseIfaceName}_get_property(GObject* object, guint propertyId, GValue* value, GParamSpec* pspec)
{
    WebCore::JSMainThreadNullState state;
EOF
        push(@txtGetProps, $txtGetProp);
        $txtGetProp = << "EOF";
$conditionGuardStart
    ${className}* self = WEBKIT_DOM_${clsCaps}(object);
    $privFunction
$conditionGuardEnd
EOF
        push(@txtGetProps, $txtGetProp);

        $txtGetProp = << "EOF";
    switch (propertyId) {
EOF
        push(@txtGetProps, $txtGetProp);

        if (scalar @writeableProperties > 0) {
            my $txtSetProps = << "EOF";
static void ${lowerCaseIfaceName}_set_property(GObject* object, guint propertyId, const GValue* value, GParamSpec* pspec)
{
    WebCore::JSMainThreadNullState state;
EOF
            push(@txtSetProps, $txtSetProps);

            $txtSetProps = << "EOF";
$conditionGuardStart
    ${className}* self = WEBKIT_DOM_${clsCaps}(object);
    $privFunction
$conditionGuardEnd
EOF
            push(@txtSetProps, $txtSetProps);

            $txtSetProps = << "EOF";
    switch (propertyId) {
EOF
            push(@txtSetProps, $txtSetProps);
        }

        foreach my $attribute (@readableProperties) {
            GenerateProperty($attribute, $interfaceName, \@writeableProperties, $interface);
        }

        push(@cBodyProperties, "};\n\n");

        $txtGetProp = << "EOF";
    default:
        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, propertyId, pspec);
        break;
    }
}
EOF
        push(@txtGetProps, $txtGetProp);

        if (scalar @writeableProperties > 0) {
            $txtSetProps = << "EOF";
    default:
        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, propertyId, pspec);
        break;
    }
}
EOF
            push(@txtSetProps, $txtSetProps);
        }
    }

    # Do not insert extra spaces when interpolating array variables
    $" = "";

    if ($parentImplClassName eq "Object") {
        $implContent = << "EOF";
static void ${lowerCaseIfaceName}_finalize(GObject* object)
{
    ${className}Private* priv = WEBKIT_DOM_${clsCaps}_GET_PRIVATE(object);
$conditionGuardStart
    WebKit::DOMObjectCache::forget(priv->coreObject.get());
$conditionGuardEnd
    priv->~${className}Private();
    G_OBJECT_CLASS(${lowerCaseIfaceName}_parent_class)->finalize(object);
}

EOF
        push(@cBodyProperties, $implContent);
    }

    if ($numProperties > 0) {
        if (scalar @writeableProperties > 0) {
            push(@cBodyProperties, @txtSetProps);
            push(@cBodyProperties, "\n");
        }
        push(@cBodyProperties, @txtGetProps);
        push(@cBodyProperties, "\n");
    }

    # Add a constructor implementation only for direct subclasses of Object to make sure
    # that the WebCore wrapped object is added only once to the DOM cache. The DOM garbage
    # collector works because Node is a direct subclass of Object and the version of
    # DOMObjectCache::put() that receives a Node (which is the one setting the frame) is
    # always called for DOM objects derived from Node.
    if ($parentImplClassName eq "Object") {
        $implContent = << "EOF";
static GObject* ${lowerCaseIfaceName}_constructor(GType type, guint constructPropertiesCount, GObjectConstructParam* constructProperties)
{
    GObject* object = G_OBJECT_CLASS(${lowerCaseIfaceName}_parent_class)->constructor(type, constructPropertiesCount, constructProperties);
$conditionGuardStart
    ${className}Private* priv = WEBKIT_DOM_${clsCaps}_GET_PRIVATE(object);
    priv->coreObject = static_cast<WebCore::${interfaceName}*>(WEBKIT_DOM_OBJECT(object)->coreObject);
    WebKit::DOMObjectCache::put(priv->coreObject.get(), object);
$conditionGuardEnd
    return object;
}

EOF
        push(@cBodyProperties, $implContent);
    }

    $implContent = << "EOF";
static void ${lowerCaseIfaceName}_class_init(${className}Class* requestClass)
{
EOF
    push(@cBodyProperties, $implContent);

    if ($parentImplClassName eq "Object" || $numProperties > 0) {
        push(@cBodyProperties, "    GObjectClass* gobjectClass = G_OBJECT_CLASS(requestClass);\n");

        if ($parentImplClassName eq "Object") {
            push(@cBodyProperties, "    g_type_class_add_private(gobjectClass, sizeof(${className}Private));\n");
            push(@cBodyProperties, "    gobjectClass->constructor = ${lowerCaseIfaceName}_constructor;\n");
            push(@cBodyProperties, "    gobjectClass->finalize = ${lowerCaseIfaceName}_finalize;\n");
        }

        if ($numProperties > 0) {
            if (scalar @writeableProperties > 0) {
                push(@cBodyProperties, "    gobjectClass->set_property = ${lowerCaseIfaceName}_set_property;\n");
            }
            push(@cBodyProperties, "    gobjectClass->get_property = ${lowerCaseIfaceName}_get_property;\n");
            push(@cBodyProperties, "\n");
            push(@cBodyProperties, @txtInstallProps);
        }
    }
    $implContent = << "EOF";
}

static void ${lowerCaseIfaceName}_init(${className}* request)
{
EOF
    push(@cBodyProperties, $implContent);

    if ($parentImplClassName eq "Object") {
        $implContent = << "EOF";
    ${className}Private* priv = WEBKIT_DOM_${clsCaps}_GET_PRIVATE(request);
    new (priv) ${className}Private();
EOF
        push(@cBodyProperties, $implContent);
    }
    $implContent = << "EOF";
}

EOF
    push(@cBodyProperties, $implContent);
}

sub GenerateHeader {
    my ($object, $interfaceName, $parentClassName) = @_;

    my $implContent = "";

    # Add the default header template
    @hPrefix = split("\r", $licenceTemplate);
    push(@hPrefix, "\n");

    # Force single header include.
    my $headerCheck = << "EOF";
#if !defined(__WEBKITDOM_H_INSIDE__) && !defined(BUILDING_WEBKIT)
#error "Only <webkitdom/webkitdom.h> can be included directly."
#endif

EOF
    push(@hPrefix, $headerCheck);

    # Header guard
    my $guard = $className . "_h";

    @hPrefixGuard = << "EOF";
#ifndef $guard
#define $guard

EOF

    $implContent = << "EOF";
G_BEGIN_DECLS

EOF

    push(@hBodyPre, $implContent);

    my $decamelize = decamelize($interfaceName);
    my $clsCaps = uc($decamelize);
    my $lowerCaseIfaceName = "webkit_dom_$decamelize";

    $implContent = << "EOF";
#define WEBKIT_TYPE_DOM_${clsCaps}            (${lowerCaseIfaceName}_get_type())
#define WEBKIT_DOM_${clsCaps}(obj)            (G_TYPE_CHECK_INSTANCE_CAST((obj), WEBKIT_TYPE_DOM_${clsCaps}, ${className}))
#define WEBKIT_DOM_${clsCaps}_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST((klass),  WEBKIT_TYPE_DOM_${clsCaps}, ${className}Class)
#define WEBKIT_DOM_IS_${clsCaps}(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), WEBKIT_TYPE_DOM_${clsCaps}))
#define WEBKIT_DOM_IS_${clsCaps}_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass),  WEBKIT_TYPE_DOM_${clsCaps}))
#define WEBKIT_DOM_${clsCaps}_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS((obj),  WEBKIT_TYPE_DOM_${clsCaps}, ${className}Class))

struct _${className} {
    ${parentClassName} parent_instance;
};

struct _${className}Class {
    ${parentClassName}Class parent_class;
};

WEBKIT_API GType
${lowerCaseIfaceName}_get_type (void);

EOF

    push(@hBody, $implContent);
}

sub GetGReturnMacro {
    my ($paramName, $paramIDLType, $returnType, $functionName) = @_;

    my $condition;
    if ($paramIDLType eq "GError") {
        $condition = "!$paramName || !*$paramName";
    } elsif (IsGDOMClassType($paramIDLType)) {
        my $paramTypeCaps = uc(decamelize($paramIDLType));
        $condition = "WEBKIT_DOM_IS_${paramTypeCaps}($paramName)";
        if (ParamCanBeNull($functionName, $paramName)) {
            $condition = "!$paramName || $condition";
        }
    } else {
        if (ParamCanBeNull($functionName, $paramName)) {
            return "";
        }
        $condition = "$paramName";
    }

    my $macro;
    if ($returnType ne "void") {
        $defaultReturn = $returnType eq "gboolean" ? "FALSE" : 0;
        $macro = "    g_return_val_if_fail($condition, $defaultReturn);\n";
    } else {
        $macro = "    g_return_if_fail($condition);\n";
    }

    return $macro;
}

sub ParamCanBeNull {
    my($functionName, $paramName) = @_;

    if (defined($functionName)) {
        return scalar(grep {$_ eq $paramName} @{$canBeNullParams->{$functionName}});
    }
    return 0;
}

sub GetFunctionDeprecationInformation {
    my($function, $parentNode) = @_;

    my $version;
    my $replacement;

    if ($parentNode->extendedAttributes->{"EventTarget"} && $function->signature->name eq "dispatchEvent") {
        # dispatchEvent is implemented already as part fo the WebKitDOMEventTarget interface.
        # Mark it as deprecated for now in favor of the interface method, and skip it once
        # we break the API. All other methods of WebKitDOMEventTarget interface are already
        # skipped because they receive an EventListener as parameter.
        $version = "2.4";
        $replacement = "webkit_dom_event_target_dispatch_event";
    }

    return ($version, $replacement);
}

sub GetTransferTypeForReturnType {
    my $returnType = shift;

    # Node is always transfer none.
    return "none" if $returnType eq "Node";

    # Any base class but Node is transfer full.
    return "full" if IsBaseType($returnType);

    # Any other class not derived from Node is transfer full.
    return "full" if $transferFullTypeHash{$returnType};
    return "none";
}

sub GenerateFunction {
    my ($object, $interfaceName, $function, $prefix, $parentNode) = @_;

    my $decamelize = decamelize($interfaceName);

    if (SkipFunction($object, $function, $parentNode, $decamelize, $prefix)) {
        return;
    }

    my ($deprecationVersion, $deprecationReplacement) = GetFunctionDeprecationInformation($function, $parentNode);
    my $functionSigType = $prefix eq "set_" ? "void" : $function->signature->type;
    my $functionName = "webkit_dom_" . $decamelize . "_" . $prefix . decamelize($function->signature->name);
    my $returnType = GetGlibTypeName($functionSigType);
    my $returnValueIsGDOMType = IsGDOMClassType($functionSigType);
    my $raisesException = $function->signature->extendedAttributes->{"RaisesException"};

    my $conditionalString = $codeGenerator->GenerateConditionalString($function->signature);
    my $parentConditionalString = $codeGenerator->GenerateConditionalString($parentNode);
    my @conditionalWarn = GenerateConditionalWarning($function->signature);
    my @parentConditionalWarn = GenerateConditionalWarning($parentNode);

    my $functionSig = "${className}* self";
    my $symbolSig = "${className}*";

    my @callImplParams;
    foreach my $param (@{$function->parameters}) {
        my $paramIDLType = $param->type;
        my $paramType = GetGlibTypeName($paramIDLType);
        my $const = $paramType eq "gchar*" ? "const " : "";
        my $paramName = $param->name;

        $functionSig .= ", ${const}$paramType $paramName";
        $symbolSig .= ", ${const}$paramType";

        my $paramIsGDOMType = IsGDOMClassType($paramIDLType);
        if ($paramIsGDOMType) {
            if ($paramIDLType ne "any") {
                $implIncludes{"WebKitDOM${paramIDLType}Private.h"} = 1;
            }
        }
        if ($paramIsGDOMType || ($paramIDLType eq "DOMString") || ($paramIDLType eq "CompareHow")) {
            $paramName = "converted" . $codeGenerator->WK_ucfirst($paramName);
        }
        push(@callImplParams, $paramName);
    }

    if ($returnType ne "void" && $returnValueIsGDOMType && $functionSigType ne "any") {
        $implIncludes{"WebKitDOM${functionSigType}Private.h"} = 1;
    }

    $functionSig .= ", GError** error" if $raisesException;
    $symbolSig .= ", GError**" if $raisesException;

    push(@symbols, "$returnType $functionName($symbolSig)\n");

    if ($deprecationVersion) {
        push(@hBody, "#if !defined(WEBKIT_DISABLE_DEPRECATED)\n");
    }

    # Insert introspection annotations
    push(@hBody, "/**\n");
    push(@hBody, " * ${functionName}:\n");
    push(@hBody, " * \@self: A #${className}\n");

    foreach my $param (@{$function->parameters}) {
        my $paramType = GetGlibTypeName($param->type);
        # $paramType can have a trailing * in some cases
        $paramType =~ s/\*$//;
        my $paramName = $param->name;
        my $paramAnnotations = "";
        if (ParamCanBeNull($functionName, $paramName)) {
            $paramAnnotations = " (allow-none):";
        }
        push(@hBody, " * \@${paramName}:${paramAnnotations} A #${paramType}\n");
    }
    push(@hBody, " * \@error: #GError\n") if $raisesException;
    push(@hBody, " *\n");
    my $returnTypeName = $returnType;
    $returnTypeName =~ s/\*$//;
    if ($returnValueIsGDOMType) {
        my $transferType = GetTransferTypeForReturnType($functionSigType);
        push(@hBody, " * Returns: (transfer $transferType): A #${returnTypeName}\n");
    } elsif ($returnType ne "void") {
        push(@hBody, " * Returns: A #${returnTypeName}\n");
    }
    if ($deprecationVersion) {
        push(@hBody, " *\n");
        push(@hBody, " * Deprecated: $deprecationVersion");
        if ($deprecationReplacement) {
            push(@hBody, ": Use $deprecationReplacement() instead.");
        }
        push(@hBody, "\n");
    }
    push(@hBody, "**/\n");

    if ($deprecationVersion && $deprecationReplacement) {
        push(@hBody, "WEBKIT_DEPRECATED_FOR($deprecationReplacement) ");
    } elsif ($deprecationVersion) {
        push(@hBody, "WEBKIT_DEPRECATED ");
    } else {
        push(@hBody, "WEBKIT_API ");
    }
    push(@hBody, "$returnType\n$functionName($functionSig);\n");
    if ($deprecationVersion) {
        push(@hBody, "#endif /* WEBKIT_DISABLE_DEPRECATED */\n");
    }
    push(@hBody, "\n");

    push(@cBody, "$returnType $functionName($functionSig)\n{\n");
    push(@cBody, "#if ${parentConditionalString}\n") if $parentConditionalString;
    push(@cBody, "#if ${conditionalString}\n") if $conditionalString;

    push(@cBody, "    WebCore::JSMainThreadNullState state;\n");

    # g_return macros to check parameters of public methods.
    $gReturnMacro = GetGReturnMacro("self", $interfaceName, $returnType);
    push(@cBody, $gReturnMacro);

    foreach my $param (@{$function->parameters}) {
        my $paramName = $param->name;
        my $paramIDLType = $param->type;
        my $paramTypeIsPointer = !$codeGenerator->IsNonPointerType($paramIDLType);
        if ($paramTypeIsPointer) {
            $gReturnMacro = GetGReturnMacro($paramName, $paramIDLType, $returnType, $functionName);
            push(@cBody, $gReturnMacro);
        }
    }

    if ($raisesException) {
        $gReturnMacro = GetGReturnMacro("error", "GError", $returnType);
        push(@cBody, $gReturnMacro);
    }

    # The WebKit::core implementations check for null already; no need to duplicate effort.
    push(@cBody, "    WebCore::${interfaceName}* item = WebKit::core(self);\n");

    $returnParamName = "";
    foreach my $param (@{$function->parameters}) {
        my $paramIDLType = $param->type;
        my $paramName = $param->name;

        my $paramIsGDOMType = IsGDOMClassType($paramIDLType);
        $convertedParamName = "converted" . $codeGenerator->WK_ucfirst($paramName);
        if ($paramIDLType eq "DOMString") {
            push(@cBody, "    WTF::String ${convertedParamName} = WTF::String::fromUTF8($paramName);\n");
        } elsif ($paramIDLType eq "CompareHow") {
            push(@cBody, "    WebCore::Range::CompareHow ${convertedParamName} = static_cast<WebCore::Range::CompareHow>($paramName);\n");
        } elsif ($paramIsGDOMType) {
            push(@cBody, "    WebCore::${paramIDLType}* ${convertedParamName} = WebKit::core($paramName);\n");
        }
        $returnParamName = $convertedParamName if $param->extendedAttributes->{"CustomReturn"};
    }

    my $assign = "";
    my $assignPre = "";
    my $assignPost = "";

    # We need to special-case these Node methods because their C++
    # signature is different from what we'd expect given their IDL
    # description; see Node.h.
    my $functionHasCustomReturn = $functionName eq "webkit_dom_node_append_child" ||
        $functionName eq "webkit_dom_node_insert_before" ||
        $functionName eq "webkit_dom_node_replace_child" ||
        $functionName eq "webkit_dom_node_remove_child";
         
    if ($returnType ne "void" && !$functionHasCustomReturn) {
        if ($returnValueIsGDOMType) {
            $assign = "RefPtr<WebCore::${functionSigType}> gobjectResult = ";
            $assignPre = "WTF::getPtr(";
            $assignPost = ")";
        } else {
            $assign = "${returnType} result = ";
        }
    }

    # FIXME: Should we return a default value when isNull == true?
    if ($function->signature->isNullable) {
        push(@cBody, "    bool isNull = false;\n");
        push(@callImplParams, "isNull");
    }

    if ($raisesException) {
        push(@cBody, "    WebCore::ExceptionCode ec = 0;\n");
        push(@callImplParams, "ec");
    }

    my $functionImplementationName = $function->signature->extendedAttributes->{"ImplementedAs"} || $function->signature->name;

    if ($functionHasCustomReturn) {
        push(@cBody, "    bool ok = item->${functionImplementationName}(" . join(", ", @callImplParams) . ");\n");
        my $customNodeAppendChild = << "EOF";
    if (ok)
        return WebKit::kit($returnParamName);
EOF
        push(@cBody, $customNodeAppendChild);
    
        if($raisesException) {
            my $exceptionHandling = << "EOF";

    WebCore::ExceptionCodeDescription ecdesc(ec);
    g_set_error_literal(error, g_quark_from_string("WEBKIT_DOM"), ecdesc.code, ecdesc.name);
EOF
            push(@cBody, $exceptionHandling);
        }
        push(@cBody, "    return 0;\n");
        push(@cBody, "}\n\n");
        return;
    } elsif ($functionSigType eq "DOMString") {
        my $getterContentHead;
        if ($prefix) {
            my ($functionName, @arguments) = $codeGenerator->GetterExpression(\%implIncludes, $interfaceName, $function);
            push(@arguments, @callImplParams);
            if ($function->signature->extendedAttributes->{"ImplementedBy"}) {
                my $implementedBy = $function->signature->extendedAttributes->{"ImplementedBy"};
                $implIncludes{"${implementedBy}.h"} = 1;
                unshift(@arguments, "item");
                $functionName = "WebCore::${implementedBy}::${functionName}";
            } else {
                $functionName = "item->${functionName}";
            }
            $getterContentHead = "${assign}convertToUTF8String(${functionName}(" . join(", ", @arguments) . "));\n";
        } else {
            my @arguments = @callImplParams;
            if ($function->signature->extendedAttributes->{"ImplementedBy"}) {
                my $implementedBy = $function->signature->extendedAttributes->{"ImplementedBy"};
                $implIncludes{"${implementedBy}.h"} = 1;
                unshift(@arguments, "item");
                $getterContentHead = "${assign}convertToUTF8String(WebCore::${implementedBy}::${functionImplementationName}(" . join(", ", @arguments) . "));\n";
            } else {
                $getterContentHead = "${assign}convertToUTF8String(item->${functionImplementationName}(" . join(", ", @arguments) . "));\n";
            }
        }
        push(@cBody, "    ${getterContentHead}");
    } else {
        my $contentHead;
        if ($prefix eq "get_") {
            my ($functionName, @arguments) = $codeGenerator->GetterExpression(\%implIncludes, $interfaceName, $function);
            push(@arguments, @callImplParams);
            if ($function->signature->extendedAttributes->{"ImplementedBy"}) {
                my $implementedBy = $function->signature->extendedAttributes->{"ImplementedBy"};
                $implIncludes{"${implementedBy}.h"} = 1;
                unshift(@arguments, "item");
                $functionName = "WebCore::${implementedBy}::${functionName}";
            } else {
                $functionName = "item->${functionName}";
            }
            $contentHead = "${assign}${assignPre}${functionName}(" . join(", ", @arguments) . "${assignPost});\n";
        } elsif ($prefix eq "set_") {
            my ($functionName, @arguments) = $codeGenerator->SetterExpression(\%implIncludes, $interfaceName, $function);
            push(@arguments, @callImplParams);
            if ($function->signature->extendedAttributes->{"ImplementedBy"}) {
                my $implementedBy = $function->signature->extendedAttributes->{"ImplementedBy"};
                $implIncludes{"${implementedBy}.h"} = 1;
                unshift(@arguments, "item");
                $functionName = "WebCore::${implementedBy}::${functionName}";
                $contentHead = "${assign}${assignPre}${functionName}(" . join(", ", @arguments) . "${assignPost});\n";
            } else {
                $functionName = "item->${functionName}";
                $contentHead = "${assign}${assignPre}${functionName}(" . join(", ", @arguments) . "${assignPost});\n";
            }
        } else {
            my @arguments = @callImplParams;
            if ($function->signature->extendedAttributes->{"ImplementedBy"}) {
                my $implementedBy = $function->signature->extendedAttributes->{"ImplementedBy"};
                $implIncludes{"${implementedBy}.h"} = 1;
                unshift(@arguments, "item");
                $contentHead = "${assign}${assignPre}WebCore::${implementedBy}::${functionImplementationName}(" . join(", ", @arguments) . "${assignPost});\n";
            } else {
                $contentHead = "${assign}${assignPre}item->${functionImplementationName}(" . join(", ", @arguments) . "${assignPost});\n";
            }
        }
        push(@cBody, "    ${contentHead}");
        
        if($raisesException) {
            my $exceptionHandling = << "EOF";
    if (ec) {
        WebCore::ExceptionCodeDescription ecdesc(ec);
        g_set_error_literal(error, g_quark_from_string("WEBKIT_DOM"), ecdesc.code, ecdesc.name);
    }
EOF
            push(@cBody, $exceptionHandling);
        }
    }

    if ($returnType ne "void" && !$functionHasCustomReturn) {
        if ($functionSigType ne "any") {
            if ($returnValueIsGDOMType) {
                push(@cBody, "    return WebKit::kit(gobjectResult.get());\n");
            } else {
                push(@cBody, "    return result;\n");
            }
        } else {
            push(@cBody, "    return 0; // TODO: return canvas object\n");
        }
    }

    if ($conditionalString) {
        push(@cBody, "#else\n");
        push(@cBody, @conditionalWarn) if scalar(@conditionalWarn);
        if ($returnType ne "void") {
            if ($codeGenerator->IsNonPointerType($functionSigType)) {
                push(@cBody, "    return static_cast<${returnType}>(0);\n");
            } else {
                push(@cBody, "    return 0;\n");
            }
        }
        push(@cBody, "#endif /* ${conditionalString} */\n");
    }

    if ($parentConditionalString) {
        push(@cBody, "#else\n");
        push(@cBody, @parentConditionalWarn) if scalar(@parentConditionalWarn);
        if ($returnType ne "void") {
            if ($codeGenerator->IsNonPointerType($functionSigType)) {
                push(@cBody, "    return static_cast<${returnType}>(0);\n");
            } else {
                push(@cBody, "    return 0;\n");
            }
        }
        push(@cBody, "#endif /* ${parentConditionalString} */\n");
    }

    push(@cBody, "}\n\n");
}

sub ClassHasFunction {
    my ($class, $name) = @_;

    foreach my $function (@{$class->functions}) {
        if ($function->signature->name eq $name) {
            return 1;
        }
    }

    return 0;
}

sub GenerateFunctions {
    my ($object, $interfaceName, $interface) = @_;

    foreach my $function (@{$interface->functions}) {
        $object->GenerateFunction($interfaceName, $function, "", $interface);
    }

    TOP:
    foreach my $attribute (@{$interface->attributes}) {
        if (SkipAttribute($attribute)) {
            next TOP;
        }

        if ($attribute->signature->name eq "type") {
            # This will conflict with the get_type() function we define to return a GType
            # according to GObject conventions.  Skip this for now.
            next TOP;
        }
            
        my $attrNameUpper = $codeGenerator->WK_ucfirst($attribute->signature->name);
        my $getname = "get${attrNameUpper}";
        my $setname = "set${attrNameUpper}";
        if (ClassHasFunction($interface, $getname) || ClassHasFunction($interface, $setname)) {
            # Very occasionally an IDL file defines getter/setter functions for one of its
            # attributes; in this case we don't need to autogenerate the getter/setter.
            next TOP;
        }
        
        # Generate an attribute getter.  For an attribute "foo", this is a function named
        # "get_foo" which calls a DOM class method named foo().
        my $function = new domFunction();
        $function->signature($attribute->signature);
        $function->signature->extendedAttributes({%{$attribute->signature->extendedAttributes}});
        if ($attribute->signature->extendedAttributes->{"GetterRaisesException"}) {
            $function->signature->extendedAttributes->{"RaisesException"} = "VALUE_IS_MISSING";
        }
        $object->GenerateFunction($interfaceName, $function, "get_", $interface);

        # FIXME: We are not generating setters for 'Replaceable'
        # attributes now, but we should somehow.
        my $custom = $attribute->signature->extendedAttributes->{"CustomSetter"};
        if ($attribute->isReadOnly || $attribute->signature->extendedAttributes->{"Replaceable"} || $custom) {
            next TOP;
        }
        
        # Generate an attribute setter.  For an attribute, "foo", this is a function named
        # "set_foo" which calls a DOM class method named setFoo().
        $function = new domFunction();
        
        $function->signature(new domSignature());
        $function->signature->name($attribute->signature->name);
        $function->signature->type($attribute->signature->type);
        $function->signature->extendedAttributes({%{$attribute->signature->extendedAttributes}});
        
        my $param = new domSignature();
        $param->name("value");
        $param->type($attribute->signature->type);
        my %attributes = ();
        $param->extendedAttributes(\%attributes);
        my $arrayRef = $function->parameters;
        push(@$arrayRef, $param);
        
        if ($attribute->signature->extendedAttributes->{"SetterRaisesException"}) {
            $function->signature->extendedAttributes->{"RaisesException"} = "VALUE_IS_MISSING";
        } else {
            delete $function->signature->extendedAttributes->{"RaisesException"};
        }
        
        $object->GenerateFunction($interfaceName, $function, "set_", $interface);
    }
}

sub GenerateCFile {
    my ($object, $interfaceName, $parentClassName, $parentGObjType, $interface) = @_;

    if ($interface->extendedAttributes->{"EventTarget"}) {
        $object->GenerateEventTargetIface($interface);
    }

    my $implContent = "";

    my $decamelize = decamelize($interfaceName);
    my $clsCaps = uc($decamelize);
    my $lowerCaseIfaceName = "webkit_dom_$decamelize";
    my $parentImplClassName = GetParentImplClassName($interface);
    my $baseClassName = GetBaseClass($parentImplClassName, $interface);

    # Add a private struct only for direct subclasses of Object so that we can use RefPtr
    # for the WebCore wrapped object and make sure we only increment the reference counter once.
    if ($parentImplClassName eq "Object") {
        my $conditionalString = $codeGenerator->GenerateConditionalString($interface);
        push(@cStructPriv, "#define WEBKIT_DOM_${clsCaps}_GET_PRIVATE(obj) G_TYPE_INSTANCE_GET_PRIVATE(obj, WEBKIT_TYPE_DOM_${clsCaps}, ${className}Private)\n\n");
        push(@cStructPriv, "typedef struct _${className}Private {\n");
        push(@cStructPriv, "#if ${conditionalString}\n") if $conditionalString;
        push(@cStructPriv, "    RefPtr<WebCore::${interfaceName}> coreObject;\n");
        push(@cStructPriv, "#endif // ${conditionalString}\n") if $conditionalString;
        push(@cStructPriv, "} ${className}Private;\n\n");
    }

    $implContent = << "EOF";
${defineTypeMacro}(${className}, ${lowerCaseIfaceName}, ${parentGObjType}${defineTypeInterfaceImplementation}

EOF
    push(@cBodyProperties, $implContent);

    if ($parentImplClassName eq "Object") {
        push(@cBodyPriv, "${className}* kit(WebCore::$interfaceName* obj)\n");
        push(@cBodyPriv, "{\n");
        push(@cBodyPriv, "    if (!obj)\n");
        push(@cBodyPriv, "        return 0;\n\n");
        push(@cBodyPriv, "    if (gpointer ret = DOMObjectCache::get(obj))\n");
        push(@cBodyPriv, "        return WEBKIT_DOM_${clsCaps}(ret);\n\n");
        if (IsPolymorphic($interfaceName)) {
            push(@cBodyPriv, "    return wrap(obj);\n");
        } else {
            push(@cBodyPriv, "    return wrap${interfaceName}(obj);\n");
        }
        push(@cBodyPriv, "}\n\n");
    } else {
        push(@cBodyPriv, "${className}* kit(WebCore::$interfaceName* obj)\n");
        push(@cBodyPriv, "{\n");
        if (!IsPolymorphic($baseClassName)) {
            push(@cBodyPriv, "    if (!obj)\n");
            push(@cBodyPriv, "        return 0;\n\n");
            push(@cBodyPriv, "    if (gpointer ret = DOMObjectCache::get(obj))\n");
            push(@cBodyPriv, "        return WEBKIT_DOM_${clsCaps}(ret);\n\n");
            push(@cBodyPriv, "    return wrap${interfaceName}(obj);\n");
        } else {
            push(@cBodyPriv, "    return WEBKIT_DOM_${clsCaps}(kit(static_cast<WebCore::$baseClassName*>(obj)));\n");
        }
        push(@cBodyPriv, "}\n\n");
    }

    $implContent = << "EOF";
WebCore::${interfaceName}* core(${className}* request)
{
    return request ? static_cast<WebCore::${interfaceName}*>(WEBKIT_DOM_OBJECT(request)->coreObject) : 0;
}

${className}* wrap${interfaceName}(WebCore::${interfaceName}* coreObject)
{
    ASSERT(coreObject);
    return WEBKIT_DOM_${clsCaps}(g_object_new(WEBKIT_TYPE_DOM_${clsCaps}, "core-object", coreObject, NULL));
}

EOF
    push(@cBodyPriv, $implContent);

    $object->GenerateProperties($interfaceName, $interface);
    $object->GenerateFunctions($interfaceName, $interface);
}

sub GenerateEndHeader {
    my ($object) = @_;

    #Header guard
    my $guard = $className . "_h";

    push(@hBody, "G_END_DECLS\n\n");
    push(@hPrefixGuardEnd, "#endif /* $guard */\n");
}

sub IsPolymorphic {
    my $type = shift;

    return scalar(grep {$_ eq $type} qw(Blob Event HTMLCollection Node StyleSheet));
}

sub GenerateEventTargetIface {
    my $object = shift;
    my $interface = shift;

    my $interfaceName = $interface->name;
    my $decamelize = decamelize($interfaceName);
    my $conditionalString = $codeGenerator->GenerateConditionalString($interface);
    my @conditionalWarn = GenerateConditionalWarning($interface);

    $implIncludes{"GObjectEventListener.h"} = 1;
    $implIncludes{"WebKitDOMEventTarget.h"} = 1;
    $implIncludes{"WebKitDOMEventPrivate.h"} = 1;

    push(@cBodyProperties, "static gboolean webkit_dom_${decamelize}_dispatch_event(WebKitDOMEventTarget* target, WebKitDOMEvent* event, GError** error)\n{\n");
    push(@cBodyProperties, "#if ${conditionalString}\n") if $conditionalString;
    push(@cBodyProperties, "    WebCore::Event* coreEvent = WebKit::core(event);\n");
    push(@cBodyProperties, "    WebCore::${interfaceName}* coreTarget = static_cast<WebCore::${interfaceName}*>(WEBKIT_DOM_OBJECT(target)->coreObject);\n\n");
    push(@cBodyProperties, "    WebCore::ExceptionCode ec = 0;\n");
    push(@cBodyProperties, "    gboolean result = coreTarget->dispatchEvent(coreEvent, ec);\n");
    push(@cBodyProperties, "    if (ec) {\n        WebCore::ExceptionCodeDescription description(ec);\n");
    push(@cBodyProperties, "        g_set_error_literal(error, g_quark_from_string(\"WEBKIT_DOM\"), description.code, description.name);\n    }\n");
    push(@cBodyProperties, "    return result;\n");
    push(@cBodyProperties, "#else\n") if $conditionalString;
    push(@cBodyProperties, @conditionalWarn) if scalar(@conditionalWarn);
    push(@cBodyProperties, "    return false;\n#endif // ${conditionalString}\n") if $conditionalString;
    push(@cBodyProperties, "}\n\n");

    push(@cBodyProperties, "static gboolean webkit_dom_${decamelize}_add_event_listener(WebKitDOMEventTarget* target, const char* eventName, GClosure* handler, gboolean useCapture)\n{\n");
    push(@cBodyProperties, "#if ${conditionalString}\n") if $conditionalString;
    push(@cBodyProperties, "    WebCore::${interfaceName}* coreTarget = static_cast<WebCore::${interfaceName}*>(WEBKIT_DOM_OBJECT(target)->coreObject);\n");
    push(@cBodyProperties, "    return WebCore::GObjectEventListener::addEventListener(G_OBJECT(target), coreTarget, eventName, handler, useCapture);\n");
    push(@cBodyProperties, "#else\n") if $conditionalString;
    push(@cBodyProperties, @conditionalWarn) if scalar(@conditionalWarn);
    push(@cBodyProperties, "    return false;\n#endif // ${conditionalString}\n") if $conditionalString;
    push(@cBodyProperties, "}\n\n");

    push(@cBodyProperties, "static gboolean webkit_dom_${decamelize}_remove_event_listener(WebKitDOMEventTarget* target, const char* eventName, GClosure* handler, gboolean useCapture)\n{\n");
    push(@cBodyProperties, "#if ${conditionalString}\n") if $conditionalString;
    push(@cBodyProperties, "    WebCore::${interfaceName}* coreTarget = static_cast<WebCore::${interfaceName}*>(WEBKIT_DOM_OBJECT(target)->coreObject);\n");
    push(@cBodyProperties, "    return WebCore::GObjectEventListener::removeEventListener(G_OBJECT(target), coreTarget, eventName, handler, useCapture);\n");
    push(@cBodyProperties, "#else\n") if $conditionalString;
    push(@cBodyProperties, @conditionalWarn) if scalar(@conditionalWarn);
    push(@cBodyProperties, "    return false;\n#endif // ${conditionalString}\n") if $conditionalString;
    push(@cBodyProperties, "}\n\n");

    push(@cBodyProperties, "static void webkit_dom_event_target_init(WebKitDOMEventTargetIface* iface)\n{\n");
    push(@cBodyProperties, "    iface->dispatch_event = webkit_dom_${decamelize}_dispatch_event;\n");
    push(@cBodyProperties, "    iface->add_event_listener = webkit_dom_${decamelize}_add_event_listener;\n");
    push(@cBodyProperties, "    iface->remove_event_listener = webkit_dom_${decamelize}_remove_event_listener;\n}\n\n");

    $defineTypeMacro = "G_DEFINE_TYPE_WITH_CODE";
    $defineTypeInterfaceImplementation = ", G_IMPLEMENT_INTERFACE(WEBKIT_TYPE_DOM_EVENT_TARGET, webkit_dom_event_target_init))";
}

sub Generate {
    my ($object, $interface) = @_;

    my $parentClassName = GetParentClassName($interface);
    my $parentGObjType = GetParentGObjType($interface);
    my $interfaceName = $interface->name;
    my $parentImplClassName = GetParentImplClassName($interface);
    my $baseClassName = GetBaseClass($parentImplClassName, $interface);

    # Add the default impl header template
    @cPrefix = split("\r", $licenceTemplate);
    push(@cPrefix, "\n");

    $implIncludes{"DOMObjectCache.h"} = 1;
    $implIncludes{"WebKitDOMPrivate.h"} = 1;
    $implIncludes{"gobject/ConvertToUTF8String.h"} = 1;
    $implIncludes{"${className}Private.h"} = 1;
    $implIncludes{"Document.h"} = 1;
    $implIncludes{"JSMainThreadExecState.h"} = 1;
    $implIncludes{"ExceptionCode.h"} = 1;
    $implIncludes{"CSSImportRule.h"} = 1;
    if ($parentImplClassName ne "Object" and IsPolymorphic($baseClassName)) {
        $implIncludes{"WebKitDOM${baseClassName}Private.h"} = 1;
    }

    $hdrIncludes{"webkitdom/${parentClassName}.h"} = 1;

    $object->GenerateHeader($interfaceName, $parentClassName);
    $object->GenerateCFile($interfaceName, $parentClassName, $parentGObjType, $interface);
    $object->GenerateEndHeader();
}

sub WriteData {
    my $object = shift;
    my $interface = shift;
    my $outputDir = shift;
    mkdir $outputDir;

    # Write a private header.
    my $interfaceName = $interface->name;
    my $filename = "$outputDir/" . $className . "Private.h";
    my $guard = "${className}Private_h";

    # Add the guard if the 'Conditional' extended attribute exists
    my $conditionalString = $codeGenerator->GenerateConditionalString($interface);

    open(PRIVHEADER, ">$filename") or die "Couldn't open file $filename for writing";

    print PRIVHEADER split("\r", $licenceTemplate);
    print PRIVHEADER "\n";

    my $text = << "EOF";
#ifndef $guard
#define $guard

#include "${interfaceName}.h"
#include <webkitdom/${className}.h>
EOF

    print PRIVHEADER $text;
    print PRIVHEADER "#if ${conditionalString}\n" if $conditionalString;
    print PRIVHEADER map { "#include \"$_\"\n" } sort keys(%hdrPropIncludes);
    print PRIVHEADER "\n";
    $text = << "EOF";
namespace WebKit {
${className}* wrap${interfaceName}(WebCore::${interfaceName}*);
${className}* kit(WebCore::${interfaceName}*);
WebCore::${interfaceName}* core(${className}*);
EOF

    print PRIVHEADER $text;

    $text = << "EOF";
} // namespace WebKit

EOF

    print PRIVHEADER $text;
    print PRIVHEADER "#endif /* ${conditionalString} */\n\n" if $conditionalString;
    print PRIVHEADER "#endif /* ${guard} */\n";

    close(PRIVHEADER);

    my $basename = FileNamePrefix . $interfaceName;
    $basename =~ s/_//g;

    # Write public header.
    my $fullHeaderFilename = "$outputDir/" . $basename . ".h";
    my $installedHeaderFilename = "${basename}.h";
    open(HEADER, ">$fullHeaderFilename") or die "Couldn't open file $fullHeaderFilename";

    print HEADER @hPrefix;
    print HEADER @hPrefixGuard;
    print HEADER "#include <glib-object.h>\n";
    print HEADER map { "#include <$_>\n" } sort keys(%hdrIncludes);
    print HEADER "#include <webkitdom/webkitdomdefines.h>\n\n";
    print HEADER @hBodyPre;
    print HEADER @hBody;
    print HEADER @hPrefixGuardEnd;

    close(HEADER);

    # Write the implementation sources
    my $implFileName = "$outputDir/" . $basename . ".cpp";
    open(IMPL, ">$implFileName") or die "Couldn't open file $implFileName";

    print IMPL @cPrefix;
    print IMPL "#include \"config.h\"\n";
    print IMPL "#include \"$installedHeaderFilename\"\n\n";

    # Remove the implementation header from the list of included files.
    %includesCopy = %implIncludes;
    print IMPL map { "#include \"$_\"\n" } sort keys(%includesCopy);

    print IMPL "#include <wtf/GetPtr.h>\n";
    print IMPL "#include <wtf/RefPtr.h>\n\n";
    print IMPL @cStructPriv;
    print IMPL "#if ${conditionalString}\n\n" if $conditionalString;

    print IMPL "namespace WebKit {\n\n";
    print IMPL @cBodyPriv;
    print IMPL "} // namespace WebKit\n\n";
    print IMPL "#endif // ${conditionalString}\n\n" if $conditionalString;

    print IMPL @cBodyProperties;
    print IMPL @cBody;

    close(IMPL);

    # Write a symbols file.
    my $symbolsFileName = "$outputDir/" . $basename . ".symbols";
    open(SYM, ">$symbolsFileName") or die "Couldn't open file $symbolsFileName";
    print SYM @symbols;
    close(SYM);

    %implIncludes = ();
    %hdrIncludes = ();
    @hPrefix = ();
    @hBody = ();

    @cPrefix = ();
    @cBody = ();
    @cBodyPriv = ();
    @cBodyProperties = ();
    @cStructPriv = ();

    @symbols = ();
}

sub GenerateInterface {
    my ($object, $interface, $defines) = @_;

    # Set up some global variables
    $className = GetClassName($interface->name);

    $object->Generate($interface);
}

1;
