/*
 * The contents of this file are subject to the Netscape Public
 * License Version 1.1 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.mozilla.org/NPL/

 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.

 * The Original Code is Rhino code, released
 * May 6, 1999.

 * The Initial Developer of the Original Code is Netscape
 * Communications Corporation.  Portions created by Netscape are
 * Copyright (C) 1997-1999 Netscape Communications Corporation. All
 * Rights Reserved.

 * Contributor(s): 

 * Alternatively, the contents of this file may be used under the
 * terms of the GNU Public License (the "GPL"), in which case the
 * provisions of the GPL are applicable instead of those above.
 * If you wish to allow use of your version of this file only
 * under the terms of the GPL and not to allow others to use your
 * version of this file under the NPL, indicate your decision by
 * deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL.  If you do not delete
 * the provisions above, a recipient may use your version of this
 * file under either the NPL or the GPL.
 */
/* Header file */
#include "js-stringbuffer.h"
#include "js-tokenstream.h"
#include "js-nodep.h"

/* Class data */
const jsString_t JSNode::className = "Node";

/* Procedures */
jsString_t JSNode::propToString(int propType)
{
    static const jsString_t *propNames = NULL;

    if (JSContext::printTrees && propNames == NULL) {
        static const jsString_t a[] = {
            "target",
            "break",
            "continue",
            "enum",
            "function",
            "temp",
            "local",
            "codeoffset",
            "fixups",
            "vars",
            "uses",
            "regexp",
            "cases",
            "default",
            "casearray",
            "sourcename",
            "source",
            "type",
            "special_prop",
            "label",
            "finally",
            "localcount",
            "targetblock",
            "variable",
            "lastuse",
            "isnumber",
            "directcall",
            "base_lineno",
            "end_lineno",
            "specialcall",
            "function_list",
        };
        propNames = a;
    }
    if (propNames != NULL) {
        return propNames[propType - 1];
    }
    return "";
}

jsString_t JSNode::toStringTreeHelper(JSContext * cx, int level)
{
    if (JSContext::printTrees) {
        JSStringBuffer s(cx);

        for (int i = 0; i < level; i++) {
            s.append("    ");
        }
        jsString_t string = toString(cx);

        s.append(string);
        s.append('\n');
        JS_DeleteZoneObj(cx, string);

        JSShallowNodeIterator iterator = getChildIterator();

        while (iterator.hasMoreElements()) {
            JSNode *n = iterator.nextElement();

            if (n->getType() == JSTokenStream::FUNCTION) {
                JSNode *p;
                jsBool_t ok = n->getProp(JSNode::FUNCTION_PROP, &p);

                if (ok) {
                    n = p;
                }
            }
            jsString_t string = n->toStringTreeHelper(cx, level + 1);

            s.append(string);
            JS_DeleteZoneObj(cx, string);
        }

        return s.offString();
    }
    return JS_NewZoneString(cx, "");
}

#ifndef FASTER
int JSNode::getType()
{
    return type;
}
void JSNode::setType(int type)
{
    this->type = type;
}
#endif
jsBool_t JSNode::hasChildren()
{
    return lstCount(&children) != 0;
}

JSNode *JSNode::getFirstChild()
{
    return (JSNode *) lstFirst(&children);
}

JSNode *JSNode::getLastChild()
{
    return (JSNode *) lstLast(&children);
}

JSNode *JSNode::getNextSibling()
{
    return (JSNode *) lstNext(this);
}

JSNode *JSNode::getChildBefore(JSNode * child)
{
    return (JSNode *) lstPrevious(child);
}

JSNode *JSNode::getLastSibling()
{
    NODE *n = this;
    NODE *next;

    while ((next = lstNext(n)) != NULL) {
        n = next;
    }
    return (JSNode *) n;
}

JSShallowNodeIterator JSNode::getChildIterator()
{
    return JSShallowNodeIterator((JSNode *) lstFirst(&children));
}

JSPreorderNodeIterator JSNode::getPreorderIterator(JSContext * cx)
{
    return JSPreorderNodeIterator(cx, this);
}

void JSNode::addChildToFront(JSNode * child)
{
    lstInsert(&children, NULL, child);
}

void JSNode::addChildToBack(JSNode * child)
{
    lstAdd(&children, child);
}

void JSNode::addChildrenToFront(JSNode * children)
{
    for (NODE * n = children; n != NULL;) {
        NODE *next = lstNext(n);

        lstInsert(&this->children, NULL, n);
        n = next;
    }
}
void JSNode::addChildrenToBack(JSNode * children)
{
    for (NODE * n = children; n != NULL;) {
        NODE *next = lstNext(n);

        lstAdd(&this->children, n);
        n = next;
    }
}
void JSNode::addChildBefore(JSNode * newChild, JSNode * node)
{
    lstInsert(&children, node->previous, newChild);
}

void JSNode::addChildAfter(JSNode * newChild, JSNode * node)
{
    lstInsert(&children, node, newChild);
}

void JSNode::removeChild(JSNode * child)
{
    lstDelete(&children, child);
}

void JSNode::replaceChild(JSNode * child, JSNode * newChild)
{
    NODE *previous = lstPrevious(child);

    lstDelete(&children, child);
    lstInsert(&children, previous, newChild);
    child->previous = child->next = NULL;
}

void JSNode::removeChildren()
{
    lstInit(&children);
}

jsBool_t JSNode::getProp(int propType, int *prop)
{
    if (props == NULL) {
        return FALSE;
    }
    Datum *datum = props->get(propType);

    if (datum != NULL) {
        if (datum->getType() == Datum::int_datum) {
            *prop = ((JSIntDatum *) datum)->datum;
            return TRUE;
        }
    }
    return FALSE;
}

jsBool_t JSNode::getProp(int propType, double *prop)
{
    if (props == NULL) {
        return FALSE;
    }
    Datum *datum = props->get(propType);

    if (datum != NULL) {
        switch (datum->getType()) {
        case Datum::int_datum:
            *prop = (double)((JSIntDatum *) datum)->datum;
            return TRUE;
        case Datum::double_datum:
            *prop = ((JSDoubleDatum *) datum)->datum;
            return TRUE;
        }
    }
    return FALSE;
}

jsBool_t JSNode::getProp(int propType, jsString_t * prop)
{
    if (props == NULL) {
        prop = NULL;
        return FALSE;
    }
    Datum *datum = props->get(propType);

    if (datum != NULL) {
        if (datum->getType() == Datum::string_datum) {
            *prop = ((JSStringDatum *) datum)->datum;
            return TRUE;
        }
    }
    prop = NULL;
    return FALSE;
}

jsBool_t JSNode::getProp(int propType, JSNode ** prop)
{
    if (props == NULL) {
        prop = NULL;
        return FALSE;
    }
    Datum *datum = props->get(propType);

    if (datum != NULL) {
        if (datum->getType() == Datum::node_datum) {
            *prop = ((JSNodeDatum *) datum)->datum;
            return TRUE;
        }
    }
    prop = NULL;
    return FALSE;
}

jsBool_t JSNode::getProp(int propType, Vector ** prop)
{
    if (props == NULL) {
        prop = NULL;
        return FALSE;
    }
    Datum *datum = props->get(propType);

    if (datum != NULL) {
        if (datum->getType() == Datum::vector_datum) {
            *prop = ((JSVectorDatum *) datum)->datum;
            return TRUE;
        }
    }
    prop = NULL;
    return FALSE;
}

jsBool_t JSNode::getProp(int propType, JSVariableTable ** prop)
{
    if (props == NULL) {
        prop = NULL;
        return FALSE;
    }
    Datum *datum = props->get(propType);

    if (datum != NULL) {
        if (datum->getType() == Datum::variabletable_datum) {
            *prop = ((JSVariableTableDatum *) datum)->datum;
            return TRUE;
        }
    }
    prop = NULL;
    return FALSE;
}

jsBool_t JSNode::getProp(int propType, jsString_t * prop, int *propLength)
{
    if (props == NULL) {
        *prop = NULL;
        propLength = 0;
        return FALSE;
    }
    Datum *datum = props->get(propType);

    if (datum != NULL) {
        if (datum->getType() == Datum::source_datum) {
            *prop = ((JSSourceDatum *) datum)->datum;
            *propLength = ((JSSourceDatum *) datum)->datumLength;
            return TRUE;
        }
    }
    *prop = NULL;
    propLength = 0;
    return FALSE;
}

JSNode::Datum * JSNode::getProp(int propType)
{
    if (props == NULL) {
        return NULL;
    }
    return props->get(propType);
}
void JSNode::putProp(JSContext * cx, int propType, int prop)
{
    if (props == NULL) {
        props = new(cx) Props(cx, 2);
    }
    Datum *value = props->put(propType, new(cx) JSIntDatum(prop));

    if (value != NULL) {
        value->release(cx);
    }
}
void JSNode::putProp(JSContext * cx, int propType, jsString_t prop)
{
    if (props == NULL) {
        props = new(cx) Props(cx, 2);
    }
    Datum *value = props->put(propType, new(cx) JSStringDatum(cx, prop));

    if (value != NULL) {
        value->release(cx);
    }
}
void JSNode::putProp(JSContext * cx, int propType, JSNode * prop)
{
    prop->addRef();
    if (props == NULL) {
        props = new(cx) Props(cx, 2);
    }
    Datum *value = props->put(propType, new(cx) JSNodeDatum(prop));

    if (value != NULL) {
        value->release(cx);
    }
}
void JSNode::putProp(JSContext * cx, int propType, Vector * prop)
{
    if (props == NULL) {
        props = new(cx) Props(cx, 2);
    }
    Datum *value = props->put(propType, new(cx) JSVectorDatum(prop));

    if (value != NULL) {
        value->release(cx);
    }
}
void JSNode::putProp(JSContext * cx, int propType, JSVariableTable * prop)
{
    if (props == NULL) {
        props = new(cx) Props(cx, 2);
    }
    Datum *value = props->put(propType, new(cx) JSVariableTableDatum(prop));

    if (value != NULL) {
        value->release(cx);
    }
}
void JSNode::putProp(JSContext * cx, int propType, jsString_t prop, int propLegth)
{
    if (props == NULL) {
        props = new(cx) Props(cx, 2);
    }
    Datum *value = props->put(propType,
            new(cx) JSSourceDatum(prop, propLegth));

    if (value != NULL) {
        value->release(cx);
    }
}
void JSNode::putProp(JSContext * cx, int propType, Datum * prop)
{
    if (props == NULL) {
        props = new(cx) Props(cx, 2);
    }
    prop->addRef();

    Datum *value = props->put(propType, prop);

    if (value != NULL) {
        value->release(cx);
    }
}
jsBool_t JSNode::getDatum(int *datum)
{
    if (this->datum != NULL) {
        switch (this->datum->getType()) {
        case Datum::int_datum:
            *datum = ((JSIntDatum *) this->datum)->datum;
            return TRUE;
        }
    }
    return FALSE;
}

jsBool_t JSNode::getDatum(double *datum)
{
    if (this->datum != NULL) {
        switch (this->datum->getType()) {
        case Datum::int_datum:
            *datum = (double)((JSIntDatum *) this->datum)->datum;
            return TRUE;
        case Datum::double_datum:
            *datum = ((JSDoubleDatum *) this->datum)->datum;
            return TRUE;
        }
    }
    return FALSE;
}

jsBool_t JSNode::getDatum(jsString_t * datum)
{
    if (this->datum != NULL) {
        switch (this->datum->getType()) {
        case Datum::string_datum:
            *datum = ((JSStringDatum *) this->datum)->datum;
            return TRUE;
        }
    }
    datum = NULL;
    return FALSE;
}

JSNode::Datum * JSNode::getDatum()
{
    return this->datum;
}
void JSNode::setDatum(JSContext * cx, int datum)
{
    if (this->datum != NULL) {
        this->datum->release(cx);
    }
    this->datum = new(cx) JSIntDatum(datum);
}

int JSNode::getInt()
{
    switch (datum->getType()) {
    case Datum::int_datum:
        return ((JSIntDatum *) datum)->datum;
    case Datum::double_datum:
        return (int)((JSDoubleDatum *) datum)->datum;
    }
    return 0;   // bug
}

double JSNode::getDouble()
{
    switch (datum->getType()) {
    case Datum::int_datum:
        return (double)((JSIntDatum *) datum)->datum;
    case Datum::double_datum:
        return ((JSDoubleDatum *) datum)->datum;
    }
    return 0.0; // bug
}

int64_t JSNode::getLong()
{
    switch (datum->getType()) {
    case Datum::int_datum:
        return (int64_t) ((JSIntDatum *) datum)->datum;
    case Datum::double_datum:
        return (int64_t) ((JSDoubleDatum *) datum)->datum;
    }
    return 0;   // bug
}

jsString_t JSNode::getString()
{
    switch (datum->getType()) {
    case Datum::string_datum:
        return ((JSStringDatum *) datum)->datum;
    }
    return "";  // bug 
}

JSNode *JSNode::cloneNode(JSContext * cx)
{
    JSNode *node = new(cx) JSNode(*this);

    for (NODE * cnode = lstFirst(&children); cnode != NULL;) {
        JSNode *child = (JSNode *) cnode;
        NODE *next = lstNext(cnode);

        child->addRef();
        cnode = next;
    }
    if (props != NULL) {
        node->props = props->clone(cx);
    }
    if (datum != NULL) {
        datum->addRef();
    }
    return node;
}

jsString_t JSNode::toString(JSContext * cx)
{
    if (JSContext::printTrees) {
        JSStringBuffer sb(cx, JSTokenStream::tokenToName(type));

        if (type == JSTokenStream::TARGET) {
            sb.append(" ");
            sb.append("JSTokenStream::TARGET");
            // sb.append((jsChar_t) hashCode());
        }
        if (datum != NULL) {
            jsString_t s = datum->toString(cx);

            sb.append(' ');
            sb.append(s);
            JS_DeleteZoneObj(cx, s);
        }
        if (props == NULL) {
            return sb.offString();
        }

        Props::HashtableKeyEnumerator keys = props->keys();
        Props::HashtableValEnumerator elems = props->values();

        while (keys.hasMoreElements()) {
            int key = keys.nextElement();
            Datum *elem = elems.nextElement();

            sb.append(" [");
            sb.append(propToString(key));
            sb.append(": ");
            switch (key) {
            case FIXUPS_PROP:  // can't add this as it recurses
                sb.append("fixups property");
                break;
            case SOURCE_PROP:  // can't add this as it has unprintables
                sb.append("source property");
                break;
            case TARGETBLOCK_PROP: // can't add this as it recurses
                sb.append("target block property");
                break;
            case LASTUSE_PROP: // can't add this as it is dull
                sb.append("last use property");
                break;
            default:
                jsString_t s = elem->toString(cx);

                sb.append(s);
                JS_DeleteZoneObj(cx, s);
                break;
            }
            sb.append("]");
        }
        return sb.offString();
    }
    return NULL;
}

jsString_t JSNode::toStringTree(JSContext * cx)
{
    return toStringTreeHelper(cx, 0);
}

JSNode *JSNode::getFirst()
{
    return (JSNode *) lstFirst(&children);
}

JSNode *JSNode::getNext()
{
    return (JSNode *) lstNext(this);
}

jsBool_t JSNode::isInstanceOf(const jsString_t className)
{
    return className == JSNode::className;
}

void JSNode::addRef()
{
    refCount += 1;
}

void JSNode::release(JSContext * cx)
{
    refCount -= 1;
    if (refCount <= 0) {
        destroy(cx);
    }
}
void JSNode::initialize(int nodeType)
{
    next = previous = NULL;
    type = nodeType;
    lstInit(&this->children);
    props = NULL;
    datum = NULL;
    refCount = 1;
}

void JSNode::destroy(JSContext * cx)
{
    if (datum != NULL) {
        datum->release(cx);
    }
    if (props != NULL) {
        Props::HashtableValEnumerator enumerator = props->values();

        while (enumerator.hasMoreElements()) {
            Datum *value;

            value = enumerator.nextElement();
            if (value != NULL) {
                value->release(cx);
            }
        }
        props->destroy(cx);
    }

    for (NODE * node = lstFirst(&children); node != NULL;) {
        JSNode *child = (JSNode *) node;
        NODE *next = lstNext(node);

        child->release(cx);
        node = next;
    }
    JSZoneNew::destroy(cx);
}

JSNode::JSNode(int nodeType)
{
    initialize(nodeType);
}
JSNode::JSNode(int nodeType, JSNode * child)
{
    initialize(nodeType);
    lstAdd(&children, child);
}
JSNode::JSNode(int nodeType, JSNode * left, JSNode * right)
{
    initialize(nodeType);
    lstAdd(&children, left);
    lstAdd(&children, right);
}
JSNode::JSNode(int nodeType, JSNode * left, JSNode * mid, JSNode * right)
{
    initialize(nodeType);
    lstAdd(&children, left);
    lstAdd(&children, mid);
    lstAdd(&children, right);
}
JSNode::JSNode(JSContext * cx, int nodeType, int datum)
{
    initialize(nodeType);
    this->datum = new(cx) JSIntDatum(datum);
}
JSNode::JSNode(JSContext * cx, int nodeType, double datum)
{
    initialize(nodeType);
    this->datum = new(cx) JSDoubleDatum(datum);
}
JSNode::JSNode(JSContext * cx, int nodeType, jsString_t datum)
{
    initialize(nodeType);
    this->datum = new(cx) JSStringDatum(cx, datum);
}
JSNode::JSNode(JSContext * cx, int nodeType, JSNode * child, int datum)
{
    initialize(nodeType);
    lstAdd(&children, child);
    this->datum = new(cx) JSIntDatum(datum);
}
JSNode::JSNode(JSContext * cx, int nodeType, JSNode * child, double datum)
{
    initialize(nodeType);
    lstAdd(&children, child);
    this->datum = new(cx) JSDoubleDatum(datum);
}
JSNode::JSNode(JSContext * cx, int nodeType, JSNode * child, jsString_t datum)
{
    initialize(nodeType);
    lstAdd(&children, child);
    this->datum = new(cx) JSStringDatum(cx, datum);
}
JSNode::JSNode(JSContext * cx, int nodeType, JSNode * child, Datum * datum)
{
    initialize(nodeType);
    lstAdd(&children, child);
    datum->addRef();
    this->datum = datum;
}
JSNode::JSNode(JSContext * cx, int nodeType, JSNode * left, JSNode * right, int datum)
{
    initialize(nodeType);
    lstAdd(&children, left);
    lstAdd(&children, right);
    this->datum = new(cx) JSIntDatum(datum);
}
JSNode::JSNode(JSContext * cx, int nodeType, JSNode * left, JSNode * right, double datum)
{
    initialize(nodeType);
    lstAdd(&children, left);
    lstAdd(&children, right);
    this->datum = new(cx) JSDoubleDatum(datum);
}
JSNode::JSNode(JSContext * cx, int nodeType, JSNode * left, JSNode * right, jsString_t datum)
{
    initialize(nodeType);
    lstAdd(&children, left);
    lstAdd(&children, right);
    this->datum = new(cx) JSStringDatum(cx, datum);
}
