#!/usr/bin/perl -w

# Copyright (C) 2005, 2006, 2007, 2009 Apple Inc. All rights reserved.
# Copyright (C) 2009, Julien Chaffraix <jchaffraix@webkit.org>
# Copyright (C) 2009 Torch Mobile Inc. All rights reserved. (http://www.torchmobile.com/)
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# 1.  Redistributions of source code must retain the above copyright
#     notice, this list of conditions and the following disclaimer. 
# 2.  Redistributions in binary form must reproduce the above copyright
#     notice, this list of conditions and the following disclaimer in the
#     documentation and/or other materials provided with the distribution. 
# 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
#     its contributors may be used to endorse or promote products derived
#     from this software without specific prior written permission. 
#
# THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

use strict;

use Config;
use Getopt::Long;
use File::Path;
use IO::File;
use InFilesParser;

sub readTags($$);
sub readAttrs($$);

my $printFactory = 0; 
my $printWrapperFactory = 0; 
my $printWrapperFactoryV8 = 0; 
my $tagsFile = "";
my $attrsFile = "";
my $outputDir = ".";
my %parsedTags = ();
my %parsedAttrs = ();
my %enabledTags = ();
my %enabledAttrs = ();
my %allTags = ();
my %allAttrs = ();
my %parameters = ();
my $extraDefines = 0;
my %extensionAttrs = ();

require Config;

my $gccLocation = "";
if ($ENV{CC}) {
    $gccLocation = $ENV{CC};
} elsif (($Config::Config{'osname'}) =~ /solaris/i) {
    $gccLocation = "/usr/sfw/bin/gcc";
} else {
    $gccLocation = "/usr/bin/gcc";
}
my $preprocessor = $gccLocation . " -E -P -x c++";

GetOptions(
    'tags=s' => \$tagsFile, 
    'attrs=s' => \$attrsFile,
    'factory' => \$printFactory,
    'outputDir=s' => \$outputDir,
    'extraDefines=s' => \$extraDefines,
    'preprocessor=s' => \$preprocessor,
    'wrapperFactory' => \$printWrapperFactory,
    'wrapperFactoryV8' => \$printWrapperFactoryV8
);

die "You must specify at least one of --tags <file> or --attrs <file>" unless (length($tagsFile) || length($attrsFile));

if (length($tagsFile)) {
    %allTags = %{readTags($tagsFile, 0)};
    %enabledTags = %{readTags($tagsFile, 1)};
}

if (length($attrsFile)) {
    %allAttrs = %{readAttrs($attrsFile, 0)};
    %enabledAttrs = %{readAttrs($attrsFile, 1)};
}

die "You must specify a namespace (e.g. SVG) for <namespace>Names.h" unless $parameters{namespace};
die "You must specify a namespaceURI (e.g. http://www.w3.org/2000/svg)" unless $parameters{namespaceURI};

$parameters{namespacePrefix} = $parameters{namespace} unless $parameters{namespacePrefix};

mkpath($outputDir);
my $namesBasePath = "$outputDir/$parameters{namespace}Names";
my $factoryBasePath = "$outputDir/$parameters{namespace}ElementFactory";
my $wrapperFactoryFileName = "$parameters{namespace}ElementWrapperFactory";

printNamesHeaderFile("$namesBasePath.h");
printNamesCppFile("$namesBasePath.cpp");

if ($printFactory) {
    printFactoryCppFile("$factoryBasePath.cpp");
    printFactoryHeaderFile("$factoryBasePath.h");
}

die "You cannot specify both --wrapperFactory and --wrapperFactoryV8" if $printWrapperFactory && $printWrapperFactoryV8;
my $wrapperFactoryType = "";
if ($printWrapperFactory) {
    $wrapperFactoryType = "JS";
} elsif ($printWrapperFactoryV8) {
    $wrapperFactoryType = "V8";
}

if ($wrapperFactoryType) {
    printWrapperFactoryCppFile($outputDir, $wrapperFactoryType, $wrapperFactoryFileName);
    printWrapperFactoryHeaderFile($outputDir, $wrapperFactoryType, $wrapperFactoryFileName);
}

### Hash initialization

sub defaultTagPropertyHash
{
    return (
        'constructorNeedsCreatedByParser' => 0,
        'constructorNeedsFormElement' => 0,
        'interfaceName' => defaultInterfaceName($_[0]),
        # By default, the JSInterfaceName is the same as the interfaceName.
        'JSInterfaceName' => defaultInterfaceName($_[0]),
        'mapToTagName' => '',
        'wrapperOnlyIfMediaIsAvailable' => 0,
        'conditional' => 0
    );
}

sub defaultParametersHash
{
    return (
        'namespace' => '',
        'namespacePrefix' => '',
        'namespaceURI' => '',
        'guardFactoryWith' => '',
        'tagsNullNamespace' => 0,
        'attrsNullNamespace' => 0
    );
}

sub defaultInterfaceName
{
    die "No namespace found" if !$parameters{namespace};
    return $parameters{namespace} . upperCaseName($_[0]) . "Element"
}

### Parsing handlers

sub tagsHandler
{
    my ($tag, $property, $value) = @_;

    $tag =~ s/-/_/g;

    # Initialize default property values.
    $parsedTags{$tag} = { defaultTagPropertyHash($tag) } if !defined($parsedTags{$tag});

    if ($property) {
        die "Unknown property $property for tag $tag\n" if !defined($parsedTags{$tag}{$property});

        # The code relies on JSInterfaceName deriving from interfaceName to check for custom JSInterfaceName.
        # So override JSInterfaceName if it was not already set.
        $parsedTags{$tag}{JSInterfaceName} = $value if $property eq "interfaceName" && $parsedTags{$tag}{JSInterfaceName} eq $parsedTags{$tag}{interfaceName};

        $parsedTags{$tag}{$property} = $value;
    }
}

sub attrsHandler
{
    my ($attr, $property, $value) = @_;
    # Translate HTML5 extension attributes of the form 'x-webkit-feature' to 'webkitfeature'.
    # We don't just check for the 'x-' prefix because there are attributes such as x-height
    # which should follow the default path below.
    if ($attr =~ m/^x-webkit-(.*)/) {
        my $newAttr = "webkit$1";
        $extensionAttrs{$newAttr} = $attr;
        $attr = $newAttr;
    }
    $attr =~ s/-/_/g;

    # Initialize default properties' values.
    $parsedAttrs{$attr} = {} if !defined($parsedAttrs{$attr});

    if ($property) {
        die "Unknown property $property for attribute $attr\n" if !defined($parsedAttrs{$attr}{$property});
        $parsedAttrs{$attr}{$property} = $value;
    }
}

sub parametersHandler
{
    my ($parameter, $value) = @_;

    # Initialize default properties' values.
    %parameters = defaultParametersHash() if !(keys %parameters);

    die "Unknown parameter $parameter for tags/attrs\n" if !defined($parameters{$parameter});
    $parameters{$parameter} = $value;
}

## Support routines

sub preprocessorCommand()
{
    return $preprocessor if $extraDefines eq 0;
    return $preprocessor . " -D" . join(" -D", split(" ", $extraDefines));
}

sub readNames($$$$)
{
    my ($namesFile, $hashToFillRef, $handler, $usePreprocessor) = @_;

    my $names = new IO::File;
    if ($usePreprocessor) {
        open($names, preprocessorCommand() . " " . $namesFile . "|") or die "Failed to open file: $namesFile";
    } else {
        open($names, $namesFile) or die "Failed to open file: $namesFile";
    }

    my $InParser = InFilesParser->new();
    $InParser->parse($names, \&parametersHandler, $handler);

    close($names);
    die "Failed to read names from file: $namesFile" if (keys %{$hashToFillRef} == 0);
    return $hashToFillRef;
}

sub readAttrs($$)
{
    my ($namesFile, $usePreprocessor) = @_;
    %parsedAttrs = ();
    return readNames($namesFile, \%parsedAttrs, \&attrsHandler, $usePreprocessor);
}

sub readTags($$)
{
    my ($namesFile, $usePreprocessor) = @_;
    %parsedTags = ();
    return readNames($namesFile, \%parsedTags, \&tagsHandler, $usePreprocessor);
}

sub printMacros
{
    my ($F, $macro, $suffix, $namesRef) = @_;
    my %names = %$namesRef;

    for my $name (sort keys %$namesRef) {
        print F "$macro $name","$suffix;\n";
    }
}

sub usesDefaultWrapper
{
    my $tagName = shift;
    return $tagName eq $parameters{namespace} . "Element";
}

# Build a direct mapping from the tags to the Element to create, excluding
# Element that have not constructor.
sub buildConstructorMap
{
    my %tagConstructorMap = ();
    for my $tagName (keys %enabledTags) {
        my $interfaceName = $enabledTags{$tagName}{interfaceName};
        next if (usesDefaultWrapper($interfaceName));

        if ($enabledTags{$tagName}{mapToTagName}) {
            die "Cannot handle multiple mapToTagName for $tagName\n" if $enabledTags{$enabledTags{$tagName}{mapToTagName}}{mapToTagName};
            $interfaceName = $enabledTags{ $enabledTags{$tagName}{mapToTagName} }{interfaceName};
        }

        # Chop the string to keep the interesting part.
        $interfaceName =~ s/$parameters{namespace}(.*)Element/$1/;
        $tagConstructorMap{$tagName} = lc($interfaceName);
    }

    return %tagConstructorMap;
}

# Helper method that print the constructor's signature avoiding
# unneeded arguments.
sub printConstructorSignature
{
    my ($F, $tagName, $constructorName, $constructorTagName) = @_;

    print F "static PassRefPtr<$parameters{namespace}Element> ${constructorName}Constructor(const QualifiedName& $constructorTagName, Document* document";
    if ($parameters{namespace} eq "HTML") {
        print F ", HTMLFormElement*";
        print F " formElement" if $enabledTags{$tagName}{constructorNeedsFormElement};
    }
    print F ", bool";
    print F " createdByParser" if $enabledTags{$tagName}{constructorNeedsCreatedByParser};
    print F ")\n{\n";
}

# Helper method to dump the constructor interior and call the 
# Element constructor with the right arguments.
# The variable names should be kept in sync with the previous method.
sub printConstructorInterior
{
    my ($F, $tagName, $interfaceName, $constructorTagName) = @_;

    # Handle media elements.
    if ($enabledTags{$tagName}{wrapperOnlyIfMediaIsAvailable}) {
        print F <<END
    Settings* settings = document->settings();
    if (!MediaPlayer::isAvailable() || (settings && !settings->isMediaEnabled()))
        return HTMLElement::create($constructorTagName, document);
    
END
;
    }

    # Call the constructor with the right parameters.
    print F "    return ${interfaceName}::create($constructorTagName, document";
    print F ", formElement" if $enabledTags{$tagName}{constructorNeedsFormElement};
    print F ", createdByParser" if $enabledTags{$tagName}{constructorNeedsCreatedByParser};
    print F ");\n}\n\n";
}

sub printConstructors
{
    my ($F, $tagConstructorMapRef) = @_;
    my %tagConstructorMap = %$tagConstructorMapRef;

    # This is to avoid generating the same constructor several times.
    my %uniqueTags = ();
    for my $tagName (sort keys %tagConstructorMap) {
        my $interfaceName = $enabledTags{$tagName}{interfaceName};

        # Ignore the mapped tag
        # FIXME: It could be moved inside this loop but was split for readibility.
        next if (defined($uniqueTags{$interfaceName}) || $enabledTags{$tagName}{mapToTagName});

        $uniqueTags{$interfaceName} = '1';

        my $conditional = $enabledTags{$tagName}{conditional};
        if ($conditional) {
            my $conditionalString = "ENABLE(" . join(") && ENABLE(", split(/&/, $conditional)) . ")";
            print F "#if ${conditionalString}\n\n";
        }

        printConstructorSignature($F, $tagName, $tagConstructorMap{$tagName}, "tagName");
        printConstructorInterior($F, $tagName, $interfaceName, "tagName");

        if ($conditional) {
            print F "#endif\n";
        }
    }

    # Mapped tag name uses a special wrapper to keep their prefix and namespaceURI while using the mapped localname.
    for my $tagName (sort keys %tagConstructorMap) {
        if ($enabledTags{$tagName}{mapToTagName}) {
            my $mappedName = $enabledTags{$tagName}{mapToTagName};
            printConstructorSignature($F, $mappedName, $mappedName . "To" . $tagName, "tagName");
            printConstructorInterior($F, $mappedName, $enabledTags{$mappedName}{interfaceName}, "QualifiedName(tagName.prefix(), ${mappedName}Tag.localName(), tagName.namespaceURI())");
        }
    }
}

sub printFunctionInits
{
    my ($F, $tagConstructorMap) = @_;
    my %tagConstructorMap = %$tagConstructorMap;

    for my $tagName (sort keys %tagConstructorMap) {

        my $conditional = $enabledTags{$tagName}{conditional};
        if ($conditional) {
            my $conditionalString = "ENABLE(" . join(") && ENABLE(", split(/&/, $conditional)) . ")";
            print F "#if ${conditionalString}\n";
        }

        if ($enabledTags{$tagName}{mapToTagName}) {
            print F "    addTag(${tagName}Tag, $enabledTags{$tagName}{mapToTagName}To${tagName}Constructor);\n";
        } else {
            print F "    addTag(${tagName}Tag, $tagConstructorMap{$tagName}Constructor);\n";
        }

        if ($conditional) {
            print F "#endif\n\n";
        }
    }
}

sub svgCapitalizationHacks
{
    my $name = shift;

    $name = "FE" . ucfirst $1 if $name =~ /^fe(.+)$/;

    return $name;
}

sub upperCaseName
{
    my $name = shift;
    
    $name = svgCapitalizationHacks($name) if ($parameters{namespace} eq "SVG");

    while ($name =~ /^(.*?)_(.*)/) {
        $name = $1 . ucfirst $2;
    }
    
    return ucfirst $name;
}

sub printLicenseHeader
{
    my $F = shift;
    print F "/*
 * THIS FILE WAS AUTOMATICALLY GENERATED, DO NOT EDIT.
 *
 * This file was generated by the dom/make_names.pl script.
 *
 * Copyright (C) 2005, 2006, 2007, 2008, 2009 Apple Inc.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

";
}

sub printNamesHeaderFile
{
    my ($headerPath) = shift;
    my $F;
    open F, ">$headerPath";

    printLicenseHeader($F);
    print F "#ifndef DOM_$parameters{namespace}NAMES_H\n";
    print F "#define DOM_$parameters{namespace}NAMES_H\n\n";
    print F "#include \"QualifiedName.h\"\n\n";

    print F "namespace WebCore {\n\n namespace $parameters{namespace}Names {\n\n";

    my $lowerNamespace = lc($parameters{namespacePrefix});
    print F "#ifndef DOM_$parameters{namespace}NAMES_HIDE_GLOBALS\n";
    print F "// Namespace\n";
    print F "extern const WTF::AtomicString ${lowerNamespace}NamespaceURI;\n\n";

    if (keys %allTags) {
        print F "// Tags\n";
        printMacros($F, "extern const WebCore::QualifiedName", "Tag", \%allTags);
    }
    
    if (keys %allAttrs) {
        print F "// Attributes\n";
        printMacros($F, "extern const WebCore::QualifiedName", "Attr", \%allAttrs);
    }
    print F "#endif\n\n";

    if (keys %allTags) {
        print F "WebCore::QualifiedName** get$parameters{namespace}Tags(size_t* size);\n";
    }

    if (keys %allAttrs) {
        print F "WebCore::QualifiedName** get$parameters{namespace}Attrs(size_t* size);\n";
    }

    print F "\nvoid init();\n\n";
    print F "} }\n\n";
    print F "#endif\n\n";

    close F;
}

sub printNamesCppFile
{
    my $cppPath = shift;
    my $F;
    open F, ">$cppPath";
    
    printLicenseHeader($F);
    
    my $lowerNamespace = lc($parameters{namespacePrefix});

print F "#include \"config.h\"\n";

print F "#ifdef SKIP_STATIC_CONSTRUCTORS_ON_GCC\n";
print F "#define DOM_$parameters{namespace}NAMES_HIDE_GLOBALS 1\n";
print F "#else\n";
print F "#define QNAME_DEFAULT_CONSTRUCTOR 1\n";
print F "#endif\n\n";


print F "#include \"$parameters{namespace}Names.h\"\n\n";
print F "#include <wtf/StaticConstructors.h>\n";

print F "namespace WebCore {\n\n namespace $parameters{namespace}Names {

using namespace WebCore;

DEFINE_GLOBAL(AtomicString, ${lowerNamespace}NamespaceURI, \"$parameters{namespaceURI}\")
";

    if (keys %allTags) {
        print F "// Tags\n";
        for my $name (sort keys %allTags) {
            print F "DEFINE_GLOBAL(QualifiedName, ", $name, "Tag, nullAtom, \"$name\", ${lowerNamespace}NamespaceURI);\n";
        }
        
        print F "\n\nWebCore::QualifiedName** get$parameters{namespace}Tags(size_t* size)\n";
        print F "{\n    static WebCore::QualifiedName* $parameters{namespace}Tags[] = {\n";
        for my $name (sort keys %allTags) {
            print F "        (WebCore::QualifiedName*)&${name}Tag,\n";
        }
        print F "    };\n";
        print F "    *size = ", scalar(keys %allTags), ";\n";
        print F "    return $parameters{namespace}Tags;\n";
        print F "}\n";
    }

    if (keys %allAttrs) {
        print F "\n// Attributes\n";
        for my $name (sort keys %allAttrs) {
            print F "DEFINE_GLOBAL(QualifiedName, ", $name, "Attr, nullAtom, \"$name\", ${lowerNamespace}NamespaceURI);\n";
        }
        print F "\n\nWebCore::QualifiedName** get$parameters{namespace}Attrs(size_t* size)\n";
        print F "{\n    static WebCore::QualifiedName* $parameters{namespace}Attr[] = {\n";
        for my $name (sort keys %allAttrs) {
            print F "        (WebCore::QualifiedName*)&${name}Attr,\n";
        }
        print F "    };\n";
        print F "    *size = ", scalar(keys %allAttrs), ";\n";
        print F "    return $parameters{namespace}Attr;\n";
        print F "}\n";
    }

print F "\nvoid init()
{
    static bool initialized = false;
    if (initialized)
        return;
    initialized = true;
    
    // Use placement new to initialize the globals.
    
    AtomicString::init();
";
    
    print(F "    AtomicString ${lowerNamespace}NS(\"$parameters{namespaceURI}\");\n\n");

    print(F "    // Namespace\n");
    print(F "    new ((void*)&${lowerNamespace}NamespaceURI) AtomicString(${lowerNamespace}NS);\n\n");
    if (keys %allTags) {
        my $tagsNamespace = $parameters{tagsNullNamespace} ? "nullAtom" : "${lowerNamespace}NS";
        printDefinitions($F, \%allTags, "tags", $tagsNamespace);
    }
    if (keys %allAttrs) {
        my $attrsNamespace = $parameters{attrsNullNamespace} ? "nullAtom" : "${lowerNamespace}NS";
        printDefinitions($F, \%allAttrs, "attributes", $attrsNamespace);
    }

    print F "}\n\n} }\n\n";
    close F;
}

sub printJSElementIncludes
{
    my $F = shift;
    my $wrapperFactoryType = shift;

    my %tagsSeen;
    for my $tagName (sort keys %enabledTags) {
        my $JSInterfaceName = $enabledTags{$tagName}{JSInterfaceName};
        next if defined($tagsSeen{$JSInterfaceName}) || usesDefaultJSWrapper($tagName);
        $tagsSeen{$JSInterfaceName} = 1;

        print F "#include \"${wrapperFactoryType}${JSInterfaceName}.h\"\n";
    }
}

sub printElementIncludes
{
    my $F = shift;

    my %tagsSeen;
    for my $tagName (sort keys %enabledTags) {
        my $interfaceName = $enabledTags{$tagName}{interfaceName};
        next if defined($tagsSeen{$interfaceName});
        $tagsSeen{$interfaceName} = 1;

        print F "#include \"${interfaceName}.h\"\n";
    }
}

sub printDefinitions
{
    my ($F, $namesRef, $type, $namespaceURI) = @_;
    my $singularType = substr($type, 0, -1);
    my $shortType = substr($singularType, 0, 4);
    my $shortCamelType = ucfirst($shortType);
    my $shortUpperType = uc($shortType);
    
    print F "    // " . ucfirst($type) . "\n";

    for my $name (sort keys %$namesRef) {
        my $realName = $extensionAttrs{$name};
        if (!$realName) {
            $realName = $name;
            $realName =~ s/_/-/g;
        }
        print F "    new ((void*)&$name","${shortCamelType}) QualifiedName(nullAtom, \"$realName\", $namespaceURI);\n";
    }
}

## ElementFactory routines

sub printFactoryCppFile
{
    my $cppPath = shift;
    my $F;
    open F, ">$cppPath";

printLicenseHeader($F);

print F <<END
#include "config.h"
END
;

print F "\n#if $parameters{guardFactoryWith}\n\n" if $parameters{guardFactoryWith};

print F <<END
#include "$parameters{namespace}ElementFactory.h"
#include "$parameters{namespace}Names.h"
END
;

printElementIncludes($F);

print F <<END
#include <wtf/HashMap.h>

#if ENABLE(DASHBOARD_SUPPORT) || ENABLE(VIDEO)
#include "Document.h"
#include "Settings.h"
#endif

namespace WebCore {

using namespace $parameters{namespace}Names;

END
;

print F "typedef PassRefPtr<$parameters{namespace}Element> (*ConstructorFunction)(const QualifiedName&, Document*";
print F ", HTMLFormElement*" if $parameters{namespace} eq "HTML";
print F ", bool createdByParser);\n";
print F <<END
typedef HashMap<AtomicStringImpl*, ConstructorFunction> FunctionMap;

static FunctionMap* gFunctionMap = 0;

END
;

my %tagConstructorMap = buildConstructorMap();

printConstructors($F, \%tagConstructorMap);

print F <<END
static void addTag(const QualifiedName& tag, ConstructorFunction func)
{
    gFunctionMap->set(tag.localName().impl(), func);
}

static void createFunctionMap()
{
    ASSERT(!gFunctionMap);

    // Create the table.
    gFunctionMap = new FunctionMap;
    
    // Populate it with constructor functions.
END
;

printFunctionInits($F, \%tagConstructorMap);

print F "}\n";

print F "\nPassRefPtr<$parameters{namespace}Element> $parameters{namespace}ElementFactory::create$parameters{namespace}Element(const QualifiedName& qName, Document* document";
print F ", HTMLFormElement* formElement" if $parameters{namespace} eq "HTML";
print F ", bool createdByParser)\n{\n";

print F <<END
    if (!document)
        return 0;

END
;

if ($parameters{namespace} ne "HTML") {
print F <<END
#if ENABLE(DASHBOARD_SUPPORT)
    Settings* settings = document->settings();
    if (settings && settings->usesDashboardBackwardCompatibilityMode())
        return 0;
#endif
END
;

}

print F <<END
    if (!gFunctionMap)
        createFunctionMap();
    if (ConstructorFunction function = gFunctionMap->get(qName.localName().impl()))
END
;

if ($parameters{namespace} eq "HTML") {
    print F "        return function(qName, document, formElement, createdByParser);\n";
} else {
    print F "        return function(qName, document, createdByParser);\n";
}

print F "    return $parameters{namespace}Element::create(qName, document);\n";

print F <<END
}

} // namespace WebCore

END
;

    print F "#endif\n" if $parameters{guardFactoryWith};

    close F;
}

sub printFactoryHeaderFile
{
    my $headerPath = shift;
    my $F;
    open F, ">$headerPath";

    printLicenseHeader($F);

    print F<<END
#ifndef $parameters{namespace}ElementFactory_h
#define $parameters{namespace}ElementFactory_h

#include <wtf/Forward.h>
#include <wtf/PassRefPtr.h>

namespace WebCore {
    class Element;
    class Document;
    class QualifiedName;
}

namespace WebCore {

    class $parameters{namespace}Element;
END
;

print F "     class HTMLFormElement;\n" if $parameters{namespace} eq "HTML";

print F<<END
    // The idea behind this class is that there will eventually be a mapping from namespace URIs to ElementFactories that can dispense
    // elements. In a compound document world, the generic createElement function (will end up being virtual) will be called.
    class $parameters{namespace}ElementFactory {
    public:
        PassRefPtr<Element> createElement(const WebCore::QualifiedName&, WebCore::Document*, bool createdByParser = true);
END
;
print F "        static PassRefPtr<$parameters{namespace}Element> create$parameters{namespace}Element(const WebCore::QualifiedName&, WebCore::Document*";
print F ", HTMLFormElement* = 0" if $parameters{namespace} eq "HTML";
print F ", bool createdByParser = true);\n";

printf F<<END
    };
}

#endif // $parameters{namespace}ElementFactory_h

END
;

    close F;
}

## Wrapper Factory routines

sub usesDefaultJSWrapper
{
    my $name = shift;

    # A tag reuses the default wrapper if its JSInterfaceName matches the default namespace Element.
    return $enabledTags{$name}{JSInterfaceName} eq $parameters{namespace} . "Element" || $enabledTags{$name}{JSInterfaceName} eq "HTMLNoScriptElement";
}

sub printWrapperFunctions
{
    my $F = shift;
    my $wrapperFactoryType = shift;

    my %tagsSeen;
    for my $tagName (sort keys %enabledTags) {
        # Avoid defining the same wrapper method twice.
        my $JSInterfaceName = $enabledTags{$tagName}{JSInterfaceName};
        next if defined($tagsSeen{$JSInterfaceName}) || usesDefaultJSWrapper($tagName);
        $tagsSeen{$JSInterfaceName} = 1;

        my $conditional = $enabledTags{$tagName}{conditional};
        if ($conditional) {
            my $conditionalString = "ENABLE(" . join(") && ENABLE(", split(/&/, $conditional)) . ")";
            print F "#if ${conditionalString}\n\n";
        }

        if ($wrapperFactoryType eq "JS") {
            # Hack for the media tags
            # FIXME: This should have been done via a CustomWrapper attribute and a separate *Custom file.
            if ($enabledTags{$tagName}{wrapperOnlyIfMediaIsAvailable}) {
                print F <<END
static JSNode* create${JSInterfaceName}Wrapper(ExecState* exec, JSDOMGlobalObject* globalObject, PassRefPtr<$parameters{namespace}Element> element)
{
    Settings* settings = element->document()->settings();
    if (!MediaPlayer::isAvailable() || (settings && !settings->isMediaEnabled()))
        return CREATE_DOM_NODE_WRAPPER(exec, globalObject, $parameters{namespace}Element, element.get());
    return CREATE_DOM_NODE_WRAPPER(exec, globalObject, ${JSInterfaceName}, element.get());
}

END
;
            } else {
                print F <<END
static JSNode* create${JSInterfaceName}Wrapper(ExecState* exec, JSDOMGlobalObject* globalObject, PassRefPtr<$parameters{namespace}Element> element)
{
    return CREATE_DOM_NODE_WRAPPER(exec, globalObject, ${JSInterfaceName}, element.get());
}

END
;
            }
        } elsif ($wrapperFactoryType eq "V8") {
            if ($enabledTags{$tagName}{wrapperOnlyIfMediaIsAvailable}) {
                print F <<END
static v8::Handle<v8::Value> create${JSInterfaceName}Wrapper($parameters{namespace}Element* element)
{
    Settings* settings = element->document()->settings();
    if (!MediaPlayer::isAvailable() || (settings && !settings->isMediaEnabled()))
        return V8$parameters{namespace}Element::wrap(element);
    return toV8(static_cast<${JSInterfaceName}*>(element));
}

END
;
            } else {
            print F <<END
static v8::Handle<v8::Value> create${JSInterfaceName}Wrapper($parameters{namespace}Element* element)
{
    return toV8(static_cast<${JSInterfaceName}*>(element));
}


END
;
            }
        }

        if ($conditional) {
            print F "#endif\n\n";
        }
    }
}

sub printWrapperFactoryCppFile
{
    my $outputDir = shift;
    my $wrapperFactoryType = shift;
    my $wrapperFactoryFileName = shift;
    my $F;
    open F, ">" . $outputDir . "/" . $wrapperFactoryType . $wrapperFactoryFileName . ".cpp";

    printLicenseHeader($F);

    print F "#include \"config.h\"\n";
    print F "#include \"$wrapperFactoryType$parameters{namespace}ElementWrapperFactory.h\"\n";

    print F "\n#if $parameters{guardFactoryWith}\n\n" if $parameters{guardFactoryWith};

    printJSElementIncludes($F, $wrapperFactoryType);

    print F "\n#include \"$parameters{namespace}Names.h\"\n\n";

    printElementIncludes($F);

    print F <<END
#include <wtf/StdLibExtras.h>

#if ENABLE(VIDEO)
#include "Document.h"
#include "Settings.h"
#endif

END
;

    if ($wrapperFactoryType eq "JS") {    
        print F <<END
using namespace JSC;
END
;
    } elsif ($wrapperFactoryType eq "V8") {
        print F <<END
#include "V8$parameters{namespace}Element.h"
        
#include <v8.h>
END
;
    }

    print F <<END

namespace WebCore {

using namespace $parameters{namespace}Names;

END
;
    if ($wrapperFactoryType eq "JS") {
        print F <<END
typedef JSNode* (*Create$parameters{namespace}ElementWrapperFunction)(ExecState*, JSDOMGlobalObject*, PassRefPtr<$parameters{namespace}Element>);

END
;
    } elsif ($wrapperFactoryType eq "V8") {
        print F <<END
typedef v8::Handle<v8::Value> (*Create$parameters{namespace}ElementWrapperFunction)($parameters{namespace}Element*);

END
;
    }

    printWrapperFunctions($F, $wrapperFactoryType);

    if ($wrapperFactoryType eq "JS") {
        print F <<END
JSNode* createJS$parameters{namespace}Wrapper(ExecState* exec, JSDOMGlobalObject* globalObject, PassRefPtr<$parameters{namespace}Element> element)
{
    typedef HashMap<WTF::AtomicStringImpl*, Create$parameters{namespace}ElementWrapperFunction> FunctionMap;
    DEFINE_STATIC_LOCAL(FunctionMap, map, ());
    if (map.isEmpty()) {
END
;
    } elsif ($wrapperFactoryType eq "V8") {
        print F <<END
v8::Handle<v8::Value> createV8$parameters{namespace}Wrapper($parameters{namespace}Element* element, bool forceNewObject)
{
    typedef HashMap<WTF::AtomicStringImpl*, Create$parameters{namespace}ElementWrapperFunction> FunctionMap;
    DEFINE_STATIC_LOCAL(FunctionMap, map, ());
    if (map.isEmpty()) {
END
;
    }

    for my $tag (sort keys %enabledTags) {
        # Do not add the name to the map if it does not have a JS wrapper constructor or uses the default wrapper.
        next if usesDefaultJSWrapper($tag, \%enabledTags);

        my $conditional = $enabledTags{$tag}{conditional};
        if ($conditional) {
            my $conditionalString = "ENABLE(" . join(") && ENABLE(", split(/&/, $conditional)) . ")";
            print F "#if ${conditionalString}\n";
        }

        my $ucTag = $enabledTags{$tag}{JSInterfaceName};
        print F "       map.set(${tag}Tag.localName().impl(), create${ucTag}Wrapper);\n";

        if ($conditional) {
            print F "#endif\n";
        }
    }

    print F <<END
    }
    Create$parameters{namespace}ElementWrapperFunction createWrapperFunction = map.get(element->localName().impl());
    if (createWrapperFunction)
END
;
    if ($wrapperFactoryType eq "JS") {
        print F <<END
        return createWrapperFunction(exec, globalObject, element);
    return CREATE_DOM_NODE_WRAPPER(exec, globalObject, $parameters{namespace}Element, element.get());
END
;
    } elsif ($wrapperFactoryType eq "V8") {
        print F <<END
        return createWrapperFunction(element);
    return V8$parameters{namespace}Element::wrap(element, forceNewObject);
END
;
    }
    print F <<END
}

}

END
;

    print F "#endif\n" if $parameters{guardFactoryWith};

    close F;
}

sub printWrapperFactoryHeaderFile
{
    my $outputDir = shift;
    my $wrapperFactoryType = shift;
    my $wrapperFactoryFileName = shift;
    my $F;
    open F, ">" . $outputDir . "/" . $wrapperFactoryType . $wrapperFactoryFileName . ".h";

    printLicenseHeader($F);

    print F "#ifndef $wrapperFactoryType$parameters{namespace}ElementWrapperFactory_h\n";
    print F "#define $wrapperFactoryType$parameters{namespace}ElementWrapperFactory_h\n\n";

    print F "#if $parameters{guardFactoryWith}\n" if $parameters{guardFactoryWith};

    if ($wrapperFactoryType eq "JS") {
        print F <<END
#include <wtf/Forward.h>

namespace JSC {
    class ExecState;
}                                            
                                             
namespace WebCore {

    class JSNode;
    class JSDOMGlobalObject;
    class $parameters{namespace}Element;

    JSNode* createJS$parameters{namespace}Wrapper(JSC::ExecState*, JSDOMGlobalObject*, PassRefPtr<$parameters{namespace}Element>);

}
 
END
;
    } elsif ($wrapperFactoryType eq "V8") {
        print F <<END
#include <v8.h>

namespace WebCore {

    class $parameters{namespace}Element;

    v8::Handle<v8::Value> createV8$parameters{namespace}Wrapper($parameters{namespace}Element*, bool);
}
END
;
    }

    print F "#endif // $parameters{guardFactoryWith}\n\n" if $parameters{guardFactoryWith};

    print F "#endif // $wrapperFactoryType$parameters{namespace}ElementWrapperFactory_h\n";

    close F;
}
