/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
 *
 * 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):
 * Norris Boyd
 * Roger Lawrence
 * Mike McCabe
 *
 * 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.
 */

package org.mozilla.javascript;

/**
 * This class implements the root of the intermediate representation.
 *
 * @author Norris Boyd
 * @author Mike McCabe
 */

public class Node
{
    public static final int
        FUNCTION_PROP      =  1,
        LOCAL_PROP         =  2,
        LOCAL_BLOCK_PROP   =  3,
        REGEXP_PROP        =  4,
        CASEARRAY_PROP     =  5,
    /*
        the following properties are defined and manipulated by the
        optimizer -
        TARGETBLOCK_PROP - the block referenced by a branch node
        VARIABLE_PROP - the variable referenced by a BIND or NAME node
        ISNUMBER_PROP - this node generates code on Number children and
                        delivers a Number result (as opposed to Objects)
        DIRECTCALL_PROP - this call node should emit code to test the function
                          object against the known class and call diret if it
                          matches.
    */

        TARGETBLOCK_PROP   =  6,
        VARIABLE_PROP      =  7,
        ISNUMBER_PROP      =  8,
        DIRECTCALL_PROP    =  9,
        SPECIALCALL_PROP   = 10,
        SKIP_INDEXES_PROP  = 11, // array of skipped indexes of array literal
        OBJECT_IDS_PROP    = 12, // array of properties for object literal
        INCRDECR_PROP      = 13, // pre or post type of increment/decerement
        CATCH_SCOPE_PROP   = 14, // index of catch scope block in catch
        LABEL_ID_PROP      = 15, // label id: code generation uses it
        MEMBER_TYPE_PROP   = 16, // type of element access operation
        NAME_PROP          = 17, // property name
        LAST_PROP          = 17;

    // values of ISNUMBER_PROP to specify
    // which of the children are Number types
    public static final int
        BOTH = 0,
        LEFT = 1,
        RIGHT = 2;

    public static final int    // values for SPECIALCALL_PROP
        NON_SPECIALCALL  = 0,
        SPECIALCALL_EVAL = 1,
        SPECIALCALL_WITH = 2;

    public static final int   // flags for INCRDECR_PROP
        DECR_FLAG = 0x1,
        POST_FLAG = 0x2;

    public static final int   // flags for MEMBER_TYPE_PROP
        PROPERTY_FLAG    = 0x1, // property access: element is valid name
        ATTRIBUTE_FLAG   = 0x2, // x.@y or x..@y
        DESCENDANTS_FLAG = 0x4; // x..y or x..@i

    private static class NumberNode extends Node
    {
        NumberNode(double number)
        {
            super(Token.NUMBER);
            this.number = number;
        }

        double number;
    }

    private static class StringNode extends Node
    {
        StringNode(int type, String str) {
            super(type);
            this.str = str;
        }

        String str;
    }

    public static class Jump extends Node
    {
        public Jump(int type)
        {
            super(type);
        }

        Jump(int type, int lineno)
        {
            super(type, lineno);
        }

        Jump(int type, Node child)
        {
            super(type, child);
        }

        Jump(int type, Node child, int lineno)
        {
            super(type, child, lineno);
        }

        public final Jump getJumpStatement()
        {
            if (!(type == Token.BREAK || type == Token.CONTINUE)) Kit.codeBug();
            return jumpNode;
        }

        public final void setJumpStatement(Jump jumpStatement)
        {
            if (!(type == Token.BREAK || type == Token.CONTINUE)) Kit.codeBug();
            if (jumpStatement == null) Kit.codeBug();
            if (this.jumpNode != null) Kit.codeBug(); //only once
            this.jumpNode = jumpStatement;
        }

        public final Node getDefault()
        {
            if (!(type == Token.SWITCH)) Kit.codeBug();
            return target2;
        }

        public final void setDefault(Node defaultTarget)
        {
            if (!(type == Token.SWITCH)) Kit.codeBug();
            if (defaultTarget.type != Token.TARGET) Kit.codeBug();
            if (target2 != null) Kit.codeBug(); //only once
            target2 = defaultTarget;
        }

        public final Node getFinally()
        {
            if (!(type == Token.TRY)) Kit.codeBug();
            return target2;
        }

        public final void setFinally(Node finallyTarget)
        {
            if (!(type == Token.TRY)) Kit.codeBug();
            if (finallyTarget.type != Token.TARGET) Kit.codeBug();
            if (target2 != null) Kit.codeBug(); //only once
            target2 = finallyTarget;
        }

        public final Jump getLoop()
        {
            if (!(type == Token.LABEL)) Kit.codeBug();
            return jumpNode;
        }

        public final void setLoop(Jump loop)
        {
            if (!(type == Token.LABEL)) Kit.codeBug();
            if (loop == null) Kit.codeBug();
            if (jumpNode != null) Kit.codeBug(); //only once
            jumpNode = loop;
        }

        public final Node getContinue()
        {
            if (type != Token.LOOP) Kit.codeBug();
            return target2;
        }

        public final void setContinue(Node continueTarget)
        {
            if (type != Token.LOOP) Kit.codeBug();
            if (continueTarget.type != Token.TARGET) Kit.codeBug();
            if (target2 != null) Kit.codeBug(); //only once
            target2 = continueTarget;
        }

        public Node target;
        private Node target2;
        private Jump jumpNode;
    }

    private static class PropListItem
    {
        PropListItem next;
        int type;
        int intValue;
        Object objectValue;
    }


    public Node(int nodeType) {
        type = nodeType;
    }

    public Node(int nodeType, Node child) {
        type = nodeType;
        first = last = child;
        child.next = null;
    }

    public Node(int nodeType, Node left, Node right) {
        type = nodeType;
        first = left;
        last = right;
        left.next = right;
        right.next = null;
    }

    public Node(int nodeType, Node left, Node mid, Node right) {
        type = nodeType;
        first = left;
        last = right;
        left.next = mid;
        mid.next = right;
        right.next = null;
    }

    public Node(int nodeType, int line) {
        type = nodeType;
        lineno = line;
    }

    public Node(int nodeType, Node child, int line) {
        this(nodeType, child);
        lineno = line;
    }

    public Node(int nodeType, Node left, Node right, int line) {
        this(nodeType, left, right);
        lineno = line;
    }

    public Node(int nodeType, Node left, Node mid, Node right, int line) {
        this(nodeType, left, mid, right);
        lineno = line;
    }

    public static Node newNumber(double number) {
        return new NumberNode(number);
    }

    public static Node newString(String str) {
        return new StringNode(Token.STRING, str);
    }

    public static Node newString(int type, String str) {
        return new StringNode(type, str);
    }

    public int getType() {
        return type;
    }

    public void setType(int type) {
        this.type = type;
    }

    public boolean hasChildren() {
        return first != null;
    }

    public Node getFirstChild() {
        return first;
    }

    public Node getLastChild() {
        return last;
    }

    public Node getNext() {
        return next;
    }

    public Node getChildBefore(Node child) {
        if (child == first)
            return null;
        Node n = first;
        while (n.next != child) {
            n = n.next;
            if (n == null)
                throw new RuntimeException("node is not a child");
        }
        return n;
    }

    public Node getLastSibling() {
        Node n = this;
        while (n.next != null) {
            n = n.next;
        }
        return n;
    }

    public void addChildToFront(Node child) {
        child.next = first;
        first = child;
        if (last == null) {
            last = child;
        }
    }

    public void addChildToBack(Node child) {
        child.next = null;
        if (last == null) {
            first = last = child;
            return;
        }
        last.next = child;
        last = child;
    }

    public void addChildrenToFront(Node children) {
        Node lastSib = children.getLastSibling();
        lastSib.next = first;
        first = children;
        if (last == null) {
            last = lastSib;
        }
    }

    public void addChildrenToBack(Node children) {
        if (last != null) {
            last.next = children;
        }
        last = children.getLastSibling();
        if (first == null) {
            first = children;
        }
    }

    /**
     * Add 'child' before 'node'.
     */
    public void addChildBefore(Node newChild, Node node) {
        if (newChild.next != null)
            throw new RuntimeException(
                      "newChild had siblings in addChildBefore");
        if (first == node) {
            newChild.next = first;
            first = newChild;
            return;
        }
        Node prev = getChildBefore(node);
        addChildAfter(newChild, prev);
    }

    /**
     * Add 'child' after 'node'.
     */
    public void addChildAfter(Node newChild, Node node) {
        if (newChild.next != null)
            throw new RuntimeException(
                      "newChild had siblings in addChildAfter");
        newChild.next = node.next;
        node.next = newChild;
        if (last == node)
            last = newChild;
    }

    public void removeChild(Node child) {
        Node prev = getChildBefore(child);
        if (prev == null)
            first = first.next;
        else
            prev.next = child.next;
        if (child == last) last = prev;
        child.next = null;
    }

    public void replaceChild(Node child, Node newChild) {
        newChild.next = child.next;
        if (child == first) {
            first = newChild;
        } else {
            Node prev = getChildBefore(child);
            prev.next = newChild;
        }
        if (child == last)
            last = newChild;
        child.next = null;
    }

    public void replaceChildAfter(Node prevChild, Node newChild) {
        Node child = prevChild.next;
        newChild.next = child.next;
        prevChild.next = newChild;
        if (child == last)
            last = newChild;
        child.next = null;
    }

    private static final String propToString(int propType)
    {
        if (Token.printTrees) {
            // If Context.printTrees is false, the compiler
            // can remove all these strings.
            switch (propType) {
                case FUNCTION_PROP:      return "function";
                case LOCAL_PROP:         return "local";
                case LOCAL_BLOCK_PROP:   return "local_block";
                case REGEXP_PROP:        return "regexp";
                case CASEARRAY_PROP:     return "casearray";

                case TARGETBLOCK_PROP:   return "targetblock";
                case VARIABLE_PROP:      return "variable";
                case ISNUMBER_PROP:      return "isnumber";
                case DIRECTCALL_PROP:    return "directcall";

                case SPECIALCALL_PROP:   return "specialcall";
                case SKIP_INDEXES_PROP:  return "skip_indexes";
                case OBJECT_IDS_PROP:    return "object_ids_prop";
                case INCRDECR_PROP:      return "incrdecr_prop";
                case CATCH_SCOPE_PROP:   return "catch_scope_prop";
                case LABEL_ID_PROP:      return "label_id_prop";
                case MEMBER_TYPE_PROP:   return "member_type_prop";
                case NAME_PROP:          return "name_prop";

                default: Kit.codeBug();
            }
        }
        return null;
    }

    private PropListItem lookupProperty(int propType)
    {
        PropListItem x = propListHead;
        while (x != null && propType != x.type) {
            x = x.next;
        }
        return x;
    }

    private PropListItem ensureProperty(int propType)
    {
        PropListItem item = lookupProperty(propType);
        if (item == null) {
            item = new PropListItem();
            item.type = propType;
            item.next = propListHead;
            propListHead = item;
        }
        return item;
    }

    public void removeProp(int propType)
    {
        PropListItem x = propListHead;
        if (x != null) {
            PropListItem prev = null;
            while (x.type != propType) {
                prev = x;
                x = x.next;
                if (x == null) { return; }
            }
            if (prev == null) {
                propListHead = x.next;
            } else {
                prev.next = x.next;
            }
        }
    }

    public Object getProp(int propType)
    {
        PropListItem item = lookupProperty(propType);
        if (item == null) { return null; }
        return item.objectValue;
    }

    public int getIntProp(int propType, int defaultValue)
    {
        PropListItem item = lookupProperty(propType);
        if (item == null) { return defaultValue; }
        return item.intValue;
    }

    public int getExistingIntProp(int propType)
    {
        PropListItem item = lookupProperty(propType);
        if (item == null) { Kit.codeBug(); }
        return item.intValue;
    }

    public void putProp(int propType, Object prop)
    {
        if (prop == null) {
            removeProp(propType);
        } else {
            PropListItem item = ensureProperty(propType);
            item.objectValue = prop;
        }
    }

    public void putIntProp(int propType, int prop)
    {
        PropListItem item = ensureProperty(propType);
        item.intValue = prop;
    }

    public int getLineno() {
        return lineno;
    }

    /** Can only be called when <tt>getType() == Token.NUMBER</tt> */
    public final double getDouble() {
        return ((NumberNode)this).number;
    }

    public final void setDouble(double number) {
        ((NumberNode)this).number = number;
    }

    /** Can only be called when node has String context. */
    public final String getString() {
        return ((StringNode)this).str;
    }

    /** Can only be called when node has String context. */
    public final void setString(String s) {
        if (s == null) Kit.codeBug();
        ((StringNode)this).str = s;
    }

    public static Node newTarget()
    {
        return new Node(Token.TARGET);
    }

    public final int labelId()
    {
        if (type != Token.TARGET) Kit.codeBug();
        return getIntProp(LABEL_ID_PROP, -1);
    }

    public void labelId(int labelId)
    {
        if (type != Token.TARGET) Kit.codeBug();
        putIntProp(LABEL_ID_PROP, labelId);
    }

    public String toString()
    {
        if (Token.printTrees) {
            StringBuffer sb = new StringBuffer();
            toString(new ObjToIntMap(), sb);
            return sb.toString();
        }
        return String.valueOf(type);
    }

    private void toString(ObjToIntMap printIds, StringBuffer sb)
    {
        if (Token.printTrees) {
            sb.append(Token.name(type));
            if (this instanceof StringNode) {
                sb.append(' ');
                sb.append(getString());
            } else if (this instanceof ScriptOrFnNode) {
                ScriptOrFnNode sof = (ScriptOrFnNode)this;
                if (this instanceof FunctionNode) {
                    FunctionNode fn = (FunctionNode)this;
                    sb.append(' ');
                    sb.append(fn.getFunctionName());
                }
                sb.append(" [source name: ");
                sb.append(sof.getSourceName());
                sb.append("] [encoded source length: ");
                sb.append(sof.getEncodedSourceEnd()
                          - sof.getEncodedSourceStart());
                sb.append("] [base line: ");
                sb.append(sof.getBaseLineno());
                sb.append("] [end line: ");
                sb.append(sof.getEndLineno());
                sb.append(']');
            } else if (this instanceof Jump) {
                Jump jump = (Jump)this;
                if (type == Token.BREAK || type == Token.CONTINUE) {
                    sb.append(" [label: ");
                    appendPrintId(jump.getJumpStatement(), printIds, sb);
                    sb.append(']');
                } else if (type == Token.TRY) {
                    Node catchNode = jump.target;
                    Node finallyTarget = jump.getFinally();
                    if (catchNode != null) {
                        sb.append(" [catch: ");
                        appendPrintId(catchNode, printIds, sb);
                        sb.append(']');
                    }
                    if (finallyTarget != null) {
                        sb.append(" [finally: ");
                        appendPrintId(finallyTarget, printIds, sb);
                        sb.append(']');
                    }
                } else if (type == Token.LABEL || type == Token.LOOP
                           || type == Token.SWITCH)
                {
                    sb.append(" [break: ");
                    appendPrintId(jump.target, printIds, sb);
                    sb.append(']');
                    if (type == Token.LOOP) {
                        sb.append(" [continue: ");
                        appendPrintId(jump.getContinue(), printIds, sb);
                        sb.append(']');
                    }
                } else {
                    sb.append(" [target: ");
                    appendPrintId(jump.target, printIds, sb);
                    sb.append(']');
                }
            } else if (type == Token.NUMBER) {
                sb.append(' ');
                sb.append(getDouble());
            } else if (type == Token.TARGET) {
                sb.append(' ');
                appendPrintId(this, printIds, sb);
            }
            if (lineno != -1) {
                sb.append(' ');
                sb.append(lineno);
            }

            for (PropListItem x = propListHead; x != null; x = x.next) {
                int type = x.type;
                sb.append(" [");
                sb.append(propToString(type));
                sb.append(": ");
                String value;
                switch (type) {
                  case TARGETBLOCK_PROP : // can't add this as it recurses
                    value = "target block property";
                    break;
                  case LOCAL_BLOCK_PROP :     // can't add this as it is dull
                    value = "last local block";
                    break;
                  case ISNUMBER_PROP:
                    switch (x.intValue) {
                      case BOTH:
                        value = "both";
                        break;
                      case RIGHT:
                        value = "right";
                        break;
                      case LEFT:
                        value = "left";
                        break;
                      default:
                        throw Kit.codeBug();
                    }
                    break;
                  case SPECIALCALL_PROP:
                    switch (x.intValue) {
                      case SPECIALCALL_EVAL:
                        value = "eval";
                        break;
                      case SPECIALCALL_WITH:
                        value = "with";
                        break;
                      default:
                        // NON_SPECIALCALL should not be stored
                        throw Kit.codeBug();
                    }
                    break;
                  default :
                    Object obj = x.objectValue;
                    if (obj != null) {
                        value = obj.toString();
                    } else {
                        value = String.valueOf(x.intValue);
                    }
                    break;
                }
                sb.append(value);
                sb.append(']');
            }
        }
    }

    public String toStringTree(ScriptOrFnNode treeTop) {
        if (Token.printTrees) {
            StringBuffer sb = new StringBuffer();
            toStringTreeHelper(treeTop, this, null, 0, sb);
            return sb.toString();
        }
        return null;
    }

    private static void toStringTreeHelper(ScriptOrFnNode treeTop, Node n,
                                           ObjToIntMap printIds,
                                           int level, StringBuffer sb)
    {
        if (Token.printTrees) {
            if (printIds == null) {
                printIds = new ObjToIntMap();
                generatePrintIds(treeTop, printIds);
            }
            for (int i = 0; i != level; ++i) {
                sb.append("    ");
            }
            n.toString(printIds, sb);
            sb.append('\n');
            for (Node cursor = n.getFirstChild(); cursor != null;
                 cursor = cursor.getNext())
            {
                if (cursor.getType() == Token.FUNCTION) {
                    int fnIndex = cursor.getExistingIntProp(Node.FUNCTION_PROP);
                    FunctionNode fn = treeTop.getFunctionNode(fnIndex);
                    toStringTreeHelper(fn, fn, null, level + 1, sb);
                } else {
                    toStringTreeHelper(treeTop, cursor, printIds, level + 1, sb);
                }
            }
        }
    }

    private static void generatePrintIds(Node n, ObjToIntMap map)
    {
        if (Token.printTrees) {
            map.put(n, map.size());
            for (Node cursor = n.getFirstChild(); cursor != null;
                 cursor = cursor.getNext())
            {
                generatePrintIds(cursor, map);
            }
        }
    }

    private static void appendPrintId(Node n, ObjToIntMap printIds,
                                      StringBuffer sb)
    {
        if (Token.printTrees) {
            if (n != null) {
                int id = printIds.get(n, -1);
                sb.append('#');
                if (id != -1) {
                    sb.append(id + 1);
                } else {
                    sb.append("<not_available>");
                }
            }
        }
    }

    int type;              // type of the node; Token.NAME for example
    Node next;             // next sibling
    private Node first;    // first element of a linked list of children
    private Node last;     // last element of a linked list of children
    private int lineno = -1;    // encapsulated int data; depends on type

    /**
     * Linked list of properties. Since vast majority of nodes would have
     * no more then 2 properties, linked list saves memory and provides
     * fast lookup. If this does not holds, propListHead can be replaced
     * by UintMap.
     */
    private PropListItem propListHead;
}

