/*
 *
 *  Licensed to the Apache Software Foundation (ASF) under one or more
 *  contributor license agreements.  See the NOTICE file distributed with
 *  this work for additional information regarding copyright ownership.
 *  The ASF licenses this file to You under the Apache License, Version 2.0
 *  (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.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 */

package org.apache.royale.compiler.internal.parsing.as;

import java.lang.reflect.Field;

import org.apache.royale.compiler.internal.parsing.TokenBase;
import org.apache.royale.compiler.parsing.IASToken;
import org.apache.royale.compiler.parsing.ICMToken;

import com.google.common.collect.ImmutableMap;

/**
 * ActionScript token (output unit of ASTokenizer/ASTokenFixer, input unit of
 * ASTreeAssembler).
 */
public class ASToken extends TokenBase implements IASToken, ASTokenTypes
{
    /**
     * Builds a new {@link ASToken}
     * 
     * @param tokenType type of token
     * @param start start of token
     * @param end end of token
     * @param line line of token
     * @param tokenText token type from ASTokenTypes
     */
    public ASToken(final int tokenType, int start, int end, int line, int column, CharSequence tokenText)
    {
        super(tokenType, start, end, line, column, tokenText);
    }

    /**
     * Copy constructor
     * 
     * @param other the ASToken to copy
     */
    public ASToken(ASToken other)
    {
        super(other);
    }

    @Override
    public ASToken clone()
    {
        return new ASToken(this);
    }

    @Override
    public ICMToken changeType(int type)
    {
        return new ASToken(type, getStart(), getEnd(), getLine(), getColumn(), getText());
    }

    /**
     * Is this an open token (e.g. "(", "{", "[")
     * 
     * @param tokenType type of the token
     * @return true iff this is an open token
     */
    public static final boolean isOpenToken(final int tokenType)
    {
        switch (tokenType)
        {
            case TOKEN_PAREN_OPEN:
            case TOKEN_BLOCK_OPEN:
            case TOKEN_SQUARE_OPEN:
            case TOKEN_E4X_BINDING_OPEN:
                return true;
            default:
                return false;
        }
    }

    /**
     * Unknown open block token type.
     * 
     * @see #getOpenBlockTokenType()
     */
    public static final int UNKNOWN_OPEN_BLOCK_TOKEN_TYPE = -1;

    public static final int getOpenBlockTokenType(int tokenType)
    {

        switch (tokenType)
        {
            case TOKEN_KEYWORD_CATCH:
            case TOKEN_KEYWORD_DO:
            case TOKEN_KEYWORD_WHILE:
            case TOKEN_KEYWORD_FOR:
            case TOKEN_RESERVED_WORD_EACH:
            case TOKEN_KEYWORD_WITH:
            case TOKEN_KEYWORD_ELSE:
            case TOKEN_KEYWORD_IF:
            case TOKEN_KEYWORD_SWITCH:
            case TOKEN_KEYWORD_CASE:
            case TOKEN_KEYWORD_DEFAULT:
            case TOKEN_KEYWORD_TRY:
            case TOKEN_KEYWORD_FINALLY:
                return TOKEN_BLOCK_OPEN;
            case TOKEN_KEYWORD_CLASS:
                return TOKEN_BLOCK_OPEN;
            case TOKEN_KEYWORD_FUNCTION:
                return TOKEN_BLOCK_OPEN;
            case TOKEN_KEYWORD_INTERFACE:
                return TOKEN_BLOCK_OPEN;
            case TOKEN_KEYWORD_PACKAGE:
                return TOKEN_BLOCK_OPEN;
        }

        return UNKNOWN_OPEN_BLOCK_TOKEN_TYPE;
    }

    public final int getOpenBlockTokenType()
    {
        return getOpenBlockTokenType(type);
    }

    /**
     * Is this an open block token
     * 
     * @return true if this is an open block token
     */
    public final boolean isOpenBlockToken()
    {
        return ASToken.isOpenBlockToken(getType());
    }

    public static final boolean isOpenBlockToken(final int tokenType)
    {
        switch (tokenType)
        {
            case TOKEN_BLOCK_OPEN:
            case TOKEN_E4X_BINDING_OPEN:
                return true;
            default:
                return false;
        }
    }

    /**
     * Is this an open token (e.g. "(", "{", "[")
     * 
     * @return true iff this is an open token
     */
    public final boolean isOpenToken()
    {
        return ASToken.isOpenToken(getType());
    }

    /**
     * Is this a close token (e.g. ")", "}", "]")
     * 
     * @param tokenType type of the token
     * @return true if this is an close token
     */
    public static final boolean isCloseToken(final int tokenType)
    {
        switch (tokenType)
        {
            case TOKEN_PAREN_CLOSE:
            case TOKEN_BLOCK_CLOSE:
            case TOKEN_SQUARE_CLOSE:
            case TOKEN_E4X_BINDING_CLOSE:
                return true;
            default:
                return false;
        }
    }

    /**
     * Is this a close block token
     * 
     * @return true if this is a close block token
     */
    public final boolean isCloseBlockToken()
    {
        return ASToken.isCloseBlockToken(getType());
    }

    /**
     * Is this a close token
     * 
     * @param tokenType type of the token
     * @return true if this is an close token
     */
    public static final boolean isCloseBlockToken(final int tokenType)
    {
        switch (tokenType)
        {
            case TOKEN_BLOCK_CLOSE:
            case TOKEN_E4X_BINDING_CLOSE:
                return true;
            default:
                return false;
        }
    }

    /**
     * Is this a close token (e.g. ")", "}", "]")
     * 
     * @return true iff this is an close token
     */
    public final boolean isCloseToken()
    {
        return ASToken.isCloseToken(getType());
    }

    /**
     * If this is an open token, get the matching close token. If this is a
     * close token, get the matching open token
     * 
     * @param tokenType type of the token
     * @return balancing token (or 0 if this isn't an open/close token)
     */
    public static final int getBalancingToken(final int tokenType)
    {
        switch (tokenType)
        {
            case TOKEN_PAREN_OPEN:
                return TOKEN_PAREN_CLOSE;
            case TOKEN_PAREN_CLOSE:
                return TOKEN_PAREN_OPEN;
            case TOKEN_BLOCK_OPEN:
                return TOKEN_BLOCK_CLOSE;
            case TOKEN_BLOCK_CLOSE:
                return TOKEN_BLOCK_OPEN;
            case TOKEN_TYPED_COLLECTION_OPEN:
                return TOKEN_TYPED_COLLECTION_CLOSE;
            case TOKEN_TYPED_LITERAL_OPEN:
                return TOKEN_TYPED_LITERAL_CLOSE;
            case TOKEN_TYPED_LITERAL_CLOSE:
                return TOKEN_TYPED_LITERAL_OPEN;
            case TOKEN_SQUARE_OPEN:
                return TOKEN_SQUARE_CLOSE;
            case TOKEN_SQUARE_CLOSE:
                return TOKEN_SQUARE_OPEN;
            case TOKEN_TYPED_COLLECTION_CLOSE:
                return TOKEN_TYPED_COLLECTION_OPEN;
            case TOKEN_E4X_BINDING_OPEN:
                return TOKEN_E4X_BINDING_CLOSE;
            case TOKEN_E4X_BINDING_CLOSE:
                return TOKEN_E4X_BINDING_OPEN;
            default:
                return 0;
        }
    }

    /**
     * If this is an open token, get the matching close token. If this is a
     * close token, get the matching open token
     * 
     * @return balancing token (or 0 if this isn't an open/close token)
     */
    public final int getBalancingToken()
    {
        return ASToken.getBalancingToken(getType());
    }

    /**
     * Determine whether or not this token is capable of causing a containment
     * problem. If a token can cause canContain to return false when it's passed
     * in as containedType, then it must return true. This is used to improve
     * the performance of ASTokenFixer.locateBottommostContainerProblem.
     * 
     * @param tokenType type of the token
     * @return true if the token can cause canContain to return false.
     */
    private static final boolean canCauseContainmentProblems(final int tokenType)
    {
        switch (tokenType)
        {
            case TOKEN_KEYWORD_PACKAGE:
            case TOKEN_KEYWORD_CLASS:
            case TOKEN_KEYWORD_INTERFACE:
            case TOKEN_KEYWORD_FUNCTION:
            case TOKEN_KEYWORD_VAR:
            case TOKEN_BLOCK_OPEN:
            case TOKEN_BLOCK_CLOSE:
                return true;
            default:
                return false;
        }
    }

    /**
     * Determine whether or not this token is capable of causing a containment
     * problem. If a token can cause canContain to return false when it's passed
     * in as containedType, then it must return true. This is used to improve
     * the performance of ASTokenFixer.locateBottommostContainerProblem.
     * 
     * @return true iff the token can cause canContain to return false.
     */
    public final boolean canCauseContainmentProblems()
    {
        return ASToken.canCauseContainmentProblems(getType());
    }

    public final boolean isOperator()
    {
        return ASToken.isOperator(this);
    }

    private static final boolean isOperator(ASToken token)
    {
        switch (token.getType())
        {
            case TOKEN_OPERATOR_ASSIGNMENT:
            case TOKEN_OPERATOR_BITWISE_NOT:
            case TOKEN_OPERATOR_LOGICAL_NOT:
            case TOKEN_OPERATOR_MINUS:
            case TOKEN_OPERATOR_PLUS:
            case TOKEN_OPERATOR_MEMBER_ACCESS:
            case TOKEN_OPERATOR_NS_QUALIFIER:
            case TOKEN_OPERATOR_STAR:
            case TOKEN_OPERATOR_DIVISION:
            case TOKEN_OPERATOR_MODULO:
            case TOKEN_OPERATOR_BITWISE_LEFT_SHIFT:
            case TOKEN_OPERATOR_BITWISE_RIGHT_SHIFT:
            case TOKEN_OPERATOR_BITWISE_UNSIGNED_RIGHT_SHIFT:
            case TOKEN_OPERATOR_LESS_THAN:
            case TOKEN_OPERATOR_GREATER_THAN:
            case TOKEN_OPERATOR_LESS_THAN_EQUALS:
            case TOKEN_OPERATOR_GREATER_THAN_EQUALS:
            case TOKEN_OPERATOR_EQUAL:
            case TOKEN_OPERATOR_NOT_EQUAL:
            case TOKEN_OPERATOR_STRICT_EQUAL:
            case TOKEN_OPERATOR_STRICT_NOT_EQUAL:
            case TOKEN_OPERATOR_BITWISE_AND:
            case TOKEN_OPERATOR_BITWISE_XOR:
            case TOKEN_OPERATOR_BITWISE_OR:
            case TOKEN_OPERATOR_LOGICAL_AND:
            case TOKEN_OPERATOR_LOGICAL_OR:
            case TOKEN_OPERATOR_PLUS_ASSIGNMENT:
            case TOKEN_OPERATOR_MINUS_ASSIGNMENT:
            case TOKEN_OPERATOR_MULTIPLICATION_ASSIGNMENT:
            case TOKEN_OPERATOR_DIVISION_ASSIGNMENT:
            case TOKEN_OPERATOR_MODULO_ASSIGNMENT:
            case TOKEN_OPERATOR_BITWISE_AND_ASSIGNMENT:
            case TOKEN_OPERATOR_BITWISE_OR_ASSIGNMENT:
            case TOKEN_OPERATOR_BITWISE_XOR_ASSIGNMENT:
            case TOKEN_OPERATOR_BITWISE_LEFT_SHIFT_ASSIGNMENT:
            case TOKEN_OPERATOR_BITWISE_RIGHT_SHIFT_ASSIGNMENT:
            case TOKEN_OPERATOR_BITWISE_UNSIGNED_RIGHT_SHIFT_ASSIGNMENT:
            case TOKEN_OPERATOR_LOGICAL_AND_ASSIGNMENT:
            case TOKEN_OPERATOR_LOGICAL_OR_ASSIGNMENT:
            case TOKEN_KEYWORD_IN:
            case TOKEN_OPERATOR_TERNARY:
            case TOKEN_OPERATOR_DECREMENT:
            case TOKEN_OPERATOR_INCREMENT:
            case TOKEN_KEYWORD_AS:
            case TOKEN_KEYWORD_IS:
            case TOKEN_KEYWORD_DELETE:
            case TOKEN_KEYWORD_TYPEOF:
            case TOKEN_KEYWORD_INSTANCEOF:
            case TOKEN_OPERATOR_ATSIGN:
            case TOKEN_OPERATOR_DESCENDANT_ACCESS:
                return true;
            default:
                return false;
        }
    }

    public final boolean isE4X()
    {
        return ASToken.isE4X(type);
    }

    public static final boolean isE4X(final int type)
    {
        switch (type)
        {
            case TOKEN_E4X_CDATA:
            case TOKEN_E4X_COMMENT:
            case TOKEN_E4X_OPEN_TAG_START:
            case TOKEN_E4X_CLOSE_TAG_START:
            case TOKEN_E4X_TAG_END:
            case TOKEN_E4X_EMPTY_TAG_END:
            case TOKEN_E4X_NAME:
            case TOKEN_E4X_WHITESPACE:
            case TOKEN_E4X_DOTTED_NAME_PART:
            case TOKEN_E4X_NAME_DOT:
            case TOKEN_E4X_EQUALS:
            case HIDDEN_TOKEN_E4X:
            case TOKEN_E4X_PROCESSING_INSTRUCTION:
            case TOKEN_E4X_STRING:
            case TOKEN_E4X_XMLNS:
            case TOKEN_E4X_ENTITY:
            case TOKEN_E4X_DECIMAL_ENTITY:
            case TOKEN_E4X_HEX_ENTITY:
            case TOKEN_E4X_TEXT:
            case TOKEN_LITERAL_XMLLIST:
            case TOKEN_E4X_XMLLIST_CLOSE:
                return true;
        }
        return false;
    }

    public final boolean isLiteral()
    {
        return ASToken.isLiteral(type);
    }

    private static final boolean isLiteral(final int type)
    {
        switch (type)
        {
            case TOKEN_KEYWORD_TRUE:
            case TOKEN_KEYWORD_FALSE:
            case TOKEN_LITERAL_NUMBER:
            case TOKEN_VOID_0:
            case TOKEN_KEYWORD_NULL:
            case TOKEN_LITERAL_HEX_NUMBER:
            case TOKEN_LITERAL_REGEXP:
            case TOKEN_LITERAL_STRING:
            case TOKEN_LITERAL_XMLLIST:
            case TOKEN_E4X_XMLLIST_CLOSE:
                return true;
            default:
                return false;
        }
    }

    public static final boolean isDefinitionKeyword(final int type)
    {
        switch (type)
        {
            case TOKEN_KEYWORD_CLASS:
            case TOKEN_KEYWORD_FUNCTION:
            case TOKEN_KEYWORD_INTERFACE:
            case TOKEN_RESERVED_WORD_NAMESPACE:
            case TOKEN_KEYWORD_VAR:
            case TOKEN_KEYWORD_CONST:
                return true;
        }
        return false;
    }

    public static boolean isModifier(final int type)
    {
        switch (type)
        {
            case TOKEN_MODIFIER_DYNAMIC:
            case TOKEN_MODIFIER_FINAL:
            case TOKEN_MODIFIER_NATIVE:
            case TOKEN_MODIFIER_OVERRIDE:
            case TOKEN_MODIFIER_STATIC:
                return true;
        }
        return false;
    }

    public final boolean isModifier()
    {
        return isModifier(type);
    }

    public static final boolean isStatementKeyword(final int type)
    {
        switch (type)
        {
            case TOKEN_KEYWORD_FOR:
            case TOKEN_KEYWORD_WHILE:
            case TOKEN_KEYWORD_WITH:
            case TOKEN_KEYWORD_FINALLY:
            case TOKEN_KEYWORD_CASE:
            case TOKEN_KEYWORD_SWITCH:
            case TOKEN_KEYWORD_DO:
            case TOKEN_KEYWORD_DEFAULT:
            case TOKEN_KEYWORD_BREAK:
            case TOKEN_KEYWORD_CONTINUE:
            case TOKEN_KEYWORD_IF:
            case TOKEN_KEYWORD_ELSE:
                return true;
        }
        return false;
    }

    public final boolean isStatementKeyword()
    {
        return ASToken.isStatementKeyword(type);
    }

    /**
     * Check if the token is a contextual reserved word.
     * 
     * @return True if the token is a contextual reserved word.
     */
    public final boolean isContextualReservedWord()
    {
        switch (type)
        {
            case TOKEN_MODIFIER_DYNAMIC:
            case TOKEN_MODIFIER_FINAL:
            case TOKEN_MODIFIER_NATIVE:
            case TOKEN_MODIFIER_OVERRIDE:
            case TOKEN_MODIFIER_STATIC:
            case TOKEN_MODIFIER_VIRTUAL:
            case TOKEN_RESERVED_WORD_GET:
            case TOKEN_RESERVED_WORD_SET:
            case TOKEN_RESERVED_WORD_NAMESPACE:
            case TOKEN_RESERVED_WORD_CONFIG:
            case TOKEN_RESERVED_WORD_EXTENDS:
            case TOKEN_RESERVED_WORD_EACH:
            case TOKEN_RESERVED_WORD_IMPLEMENTS:
            case TOKEN_RESERVED_WORD_GOTO:
                return true;
            default:
                return false;
        }
    }

    /**
     * Returns whether this token represents an ActionScript keyword.
     * <p>
     * For missing "private", "protected", "public", "internal", see
     * https://bugs.adobe.com/jira/browse/ASLSPEC-8
     * 
     * @return True if the token is a reserved keyword.
     */
    public final boolean isKeyword()
    {
        switch (type)
        {
            case TOKEN_KEYWORD_AS:
            case TOKEN_KEYWORD_BREAK:
            case TOKEN_KEYWORD_CASE:
            case TOKEN_KEYWORD_CATCH:
            case TOKEN_KEYWORD_CLASS:
            case TOKEN_KEYWORD_CONST:
            case TOKEN_KEYWORD_CONTINUE:
            case TOKEN_KEYWORD_DEFAULT:
            case TOKEN_KEYWORD_DELETE:
            case TOKEN_KEYWORD_DO:
            case TOKEN_KEYWORD_ELSE:
            case TOKEN_KEYWORD_FALSE:
            case TOKEN_KEYWORD_FINALLY:
            case TOKEN_KEYWORD_FOR:
            case TOKEN_KEYWORD_FUNCTION:
            case TOKEN_KEYWORD_IF:
            case TOKEN_KEYWORD_IMPORT:
            case TOKEN_KEYWORD_IN:
            case TOKEN_KEYWORD_INCLUDE:
            case TOKEN_KEYWORD_INSTANCEOF:
            case TOKEN_KEYWORD_INTERFACE:
            case TOKEN_KEYWORD_IS:
            case TOKEN_KEYWORD_NEW:
            case TOKEN_KEYWORD_NULL:
            case TOKEN_KEYWORD_PACKAGE:
            case TOKEN_KEYWORD_RETURN:
            case TOKEN_KEYWORD_SUPER:
            case TOKEN_KEYWORD_SWITCH:
            case TOKEN_KEYWORD_THIS:
            case TOKEN_KEYWORD_THROW:
            case TOKEN_KEYWORD_TRUE:
            case TOKEN_KEYWORD_TRY:
            case TOKEN_KEYWORD_TYPEOF:
            case TOKEN_KEYWORD_USE:
            case TOKEN_KEYWORD_VAR:
            case TOKEN_KEYWORD_VOID:
            case TOKEN_KEYWORD_WHILE:
            case TOKEN_KEYWORD_WITH:
                return true;
            default:
                return false;
        }
    }

    /**
     * Check if the token is either a "keyword" or a "contextual reserved word".
     * 
     * @return True if the token is either a "keyword" or a
     * "contextual reserved word".
     */
    public final boolean isKeywordOrContextualReservedWord()
    {
        return isKeyword() || isContextualReservedWord();
    }

    public static final boolean canExistInMetadata(final int tokenType)
    {
        switch (tokenType)
        {
            //this is the valid set of tokens contained within metadata
            case TOKEN_PAREN_OPEN:
            case TOKEN_PAREN_CLOSE:
            case TOKEN_IDENTIFIER:
            case TOKEN_COMMA:
            case TOKEN_SQUARE_CLOSE:
            case TOKEN_OPERATOR_ASSIGNMENT:
            case TOKEN_KEYWORD_INCLUDE:
            case TOKEN_KEYWORD_DEFAULT:
            case TOKEN_KEYWORD_TRUE:
            case TOKEN_KEYWORD_FALSE:
            case TOKEN_LITERAL_HEX_NUMBER:
            case TOKEN_KEYWORD_NULL:
            case TOKEN_LITERAL_NUMBER:
            case TOKEN_LITERAL_STRING:
                return true;
        }
        return false;
    }

    public final boolean canExistInMetadata()
    {
        return canExistInMetadata(type);
    }

    public static final boolean canFollowMetadata(final int tokenType)
    {
        switch (tokenType)
        {
            case TOKEN_IDENTIFIER:
            case TOKEN_ASDOC_COMMENT:
            case HIDDEN_TOKEN_MULTI_LINE_COMMENT:
            case HIDDEN_TOKEN_SINGLE_LINE_COMMENT:
            case TOKEN_SQUARE_OPEN:
                return true; //we're a valid following token for metadata
        }
        return false;
    }

    public final boolean canFollowMetadata()
    {
        return canFollowMetadata(type);
    }

    public static final boolean canFollowUserNamespaceAnnotation(final int tokenType)
    {
        switch (tokenType)
        {
            case TOKEN_KEYWORD_FUNCTION:
            case TOKEN_KEYWORD_VAR:
            case TOKEN_KEYWORD_CONST:
            case TOKEN_MODIFIER_DYNAMIC:
            case TOKEN_MODIFIER_FINAL:
            case TOKEN_MODIFIER_NATIVE:
            case TOKEN_MODIFIER_OVERRIDE:
            case TOKEN_MODIFIER_STATIC:
                return true;
        }
        return false;
    }

    public final boolean canFollowUserNamespace()
    {
        return canFollowUserNamespaceAnnotation(type);
    }

    /**
     * @return True if a "function" keyword after this token should be
     * considered an anonymous function (closure).
     */
    public final boolean canPreceedAnonymousFunction()
    {
        switch (type)
        {
            case TOKEN_OPERATOR_ASSIGNMENT: // x = function () {...};
            case TOKEN_COMMA: // foo(x, function() {...});
            case TOKEN_PAREN_OPEN: // foo(function() {...}, ...);
            case TOKEN_SQUARE_OPEN: // x = [ function() {...}, ...];
            case TOKEN_KEYWORD_NEW: // x = new function() {...};
            case TOKEN_KEYWORD_RETURN: // return new function() {...};
            case TOKEN_COLON: // obj = { fn: function() {...} };
                return true;
            default:
                return false;
        }
    }

    /**
     * Returns true if the token type can come before the operators
     * <code>+</code> or <code>-</code>
     * 
     * @param tokenType the token type to check
     * @return true if able to come before
     */
    public static final boolean canPreceedSignedOperator(final int tokenType)
    {
        if (isLiteral(tokenType))
            return true;

        switch (tokenType)
        {
            case TOKEN_PAREN_CLOSE:
            case TOKEN_SQUARE_CLOSE:
            case TOKEN_IDENTIFIER:
            case TOKEN_OPERATOR_DECREMENT:
            case TOKEN_OPERATOR_INCREMENT:
            case TOKEN_KEYWORD_THIS:
            case TOKEN_KEYWORD_SUPER:
                return true;
        }
        return false;
    }

    /**
     * Returns true if the token can come before the operators <code>+</code> or
     * <code>-</code>
     * 
     * @return true if able to come before
     */
    public final boolean canPreceedSignedOperator()
    {
        return canPreceedSignedOperator(type);
    }

    public final boolean canPreceedE4X()
    {
        return canPreceedE4X(type);
    }

    public static final boolean canPreceedE4X(final int tokenType)
    {
        switch (tokenType)
        {
            case TOKEN_SEMICOLON:
            case TOKEN_PAREN_OPEN:
            case TOKEN_COMMA:
            case TOKEN_OPERATOR_ASSIGNMENT:
            case TOKEN_KEYWORD_RETURN:
            case TOKEN_KEYWORD_THROW:
            case TOKEN_SQUARE_OPEN: //array/vector initializer open 

                // case TOKEN_OPERATOR_BINARY:
            case TOKEN_OPERATOR_DIVISION:
            case TOKEN_OPERATOR_MODULO:
            case TOKEN_OPERATOR_BITWISE_LEFT_SHIFT:
            case TOKEN_OPERATOR_BITWISE_RIGHT_SHIFT:
            case TOKEN_OPERATOR_BITWISE_UNSIGNED_RIGHT_SHIFT:
            case TOKEN_OPERATOR_LESS_THAN:
            case TOKEN_OPERATOR_GREATER_THAN:
            case TOKEN_OPERATOR_LESS_THAN_EQUALS:
            case TOKEN_OPERATOR_GREATER_THAN_EQUALS:
            case TOKEN_OPERATOR_EQUAL:
            case TOKEN_OPERATOR_NOT_EQUAL:
            case TOKEN_OPERATOR_STRICT_EQUAL:
            case TOKEN_OPERATOR_STRICT_NOT_EQUAL:
            case TOKEN_OPERATOR_BITWISE_AND:
            case TOKEN_OPERATOR_BITWISE_XOR:
            case TOKEN_OPERATOR_BITWISE_OR:
            case TOKEN_OPERATOR_LOGICAL_AND:
            case TOKEN_OPERATOR_LOGICAL_OR:

            case TOKEN_OPERATOR_PLUS:
            case TOKEN_OPERATOR_MINUS:

            case TOKEN_OPERATOR_BITWISE_NOT:
            case TOKEN_OPERATOR_LOGICAL_NOT:
            case TOKEN_OPERATOR_PLUS_ASSIGNMENT:
            case TOKEN_OPERATOR_MINUS_ASSIGNMENT:
            case TOKEN_OPERATOR_MULTIPLICATION_ASSIGNMENT:
            case TOKEN_OPERATOR_DIVISION_ASSIGNMENT:
            case TOKEN_OPERATOR_MODULO_ASSIGNMENT:
            case TOKEN_OPERATOR_BITWISE_AND_ASSIGNMENT:
            case TOKEN_OPERATOR_BITWISE_OR_ASSIGNMENT:
            case TOKEN_OPERATOR_BITWISE_XOR_ASSIGNMENT:
            case TOKEN_OPERATOR_BITWISE_LEFT_SHIFT_ASSIGNMENT:
            case TOKEN_OPERATOR_BITWISE_RIGHT_SHIFT_ASSIGNMENT:
            case TOKEN_OPERATOR_BITWISE_UNSIGNED_RIGHT_SHIFT_ASSIGNMENT:

                // Ternary("?") and Colon(":") are for XML literals inside ternary expressions:
                // For example:  boolValue ? <True></True> : <False></False>
            case TOKEN_OPERATOR_TERNARY:
            case TOKEN_COLON:

            case -1: //nothing
                return true;
        }
        return false;
    }

    /**
     * Check if regular-expression literal can follow a token of given type.
     * 
     * @param tokenType Token type
     * @return True if {@link ASTokenTypes#TOKEN_LITERAL_REGEXP} can follow the
     * given token type.
     */
    public static final boolean canPreceedRegex(final int tokenType)
    {
        switch (tokenType)
        {
            case TOKEN_PAREN_OPEN:
            case TOKEN_SQUARE_OPEN: // i.e. var a:Array = [ /foo/, /bar/ ] ;
            case TOKEN_COMMA:
            case TOKEN_COLON:
            case TOKEN_OPERATOR_ASSIGNMENT:
            case TOKEN_KEYWORD_RETURN:
            case TOKEN_KEYWORD_THROW:
            case TOKEN_SEMICOLON:
            case TOKEN_BLOCK_OPEN:
            case TOKEN_BLOCK_CLOSE:
            case TOKEN_OPERATOR_DIVISION:
            case TOKEN_OPERATOR_MODULO:
            case TOKEN_OPERATOR_BITWISE_LEFT_SHIFT:
            case TOKEN_OPERATOR_BITWISE_RIGHT_SHIFT:
            case TOKEN_OPERATOR_BITWISE_UNSIGNED_RIGHT_SHIFT:
            case TOKEN_OPERATOR_LESS_THAN:
            case TOKEN_OPERATOR_GREATER_THAN:
            case TOKEN_OPERATOR_LESS_THAN_EQUALS:
            case TOKEN_OPERATOR_GREATER_THAN_EQUALS:
            case TOKEN_OPERATOR_EQUAL:
            case TOKEN_OPERATOR_NOT_EQUAL:
            case TOKEN_OPERATOR_STRICT_EQUAL:
            case TOKEN_OPERATOR_STRICT_NOT_EQUAL:
            case TOKEN_OPERATOR_BITWISE_AND:
            case TOKEN_OPERATOR_BITWISE_XOR:
            case TOKEN_OPERATOR_BITWISE_OR:
            case TOKEN_OPERATOR_LOGICAL_AND:
            case TOKEN_OPERATOR_LOGICAL_OR:
            case TOKEN_OPERATOR_PLUS:
            case TOKEN_OPERATOR_MINUS:
            case TOKEN_OPERATOR_BITWISE_NOT:
            case TOKEN_OPERATOR_LOGICAL_NOT:
            case TOKEN_OPERATOR_PLUS_ASSIGNMENT:
            case TOKEN_OPERATOR_MINUS_ASSIGNMENT:
            case TOKEN_OPERATOR_MULTIPLICATION_ASSIGNMENT:
            case TOKEN_OPERATOR_DIVISION_ASSIGNMENT:
            case TOKEN_OPERATOR_MODULO_ASSIGNMENT:
            case TOKEN_OPERATOR_BITWISE_AND_ASSIGNMENT:
            case TOKEN_OPERATOR_BITWISE_OR_ASSIGNMENT:
            case TOKEN_OPERATOR_BITWISE_XOR_ASSIGNMENT:
            case TOKEN_OPERATOR_BITWISE_LEFT_SHIFT_ASSIGNMENT:
            case TOKEN_OPERATOR_BITWISE_RIGHT_SHIFT_ASSIGNMENT:
            case TOKEN_OPERATOR_BITWISE_UNSIGNED_RIGHT_SHIFT_ASSIGNMENT:
                // The following ones are RegEx after a unary operator.
            case TOKEN_KEYWORD_VOID:
            case TOKEN_KEYWORD_TYPEOF:
            case TOKEN_KEYWORD_DELETE:
            case TOKEN_OPERATOR_INCREMENT:
            case TOKEN_OPERATOR_DECREMENT:
            case -1: //no previous token
                return true;
        }
        return false;
    }

    private static ImmutableMap<Integer, String> tokenNames;

    /**
     * Create a lookup map for token names using reflections.
     */
    private static synchronized void initializeTokenNames()
    {
        if (tokenNames != null)
            return;

        final ImmutableMap.Builder<Integer, String> builder = new ImmutableMap.Builder<Integer, String>();
        final ASToken token = new ASToken(UNKNOWN, UNKNOWN, UNKNOWN, UNKNOWN, UNKNOWN, "");
        for (final Field field : ASTokenTypes.class.getFields())
        {
            if (field.getType().equals(Integer.TYPE))
            {
                int tokenType;
                try
                {
                    tokenType = field.getInt(token);
                }
                catch (Exception e)
                {
                    throw new RuntimeException(e);
                }
                builder.put(tokenType, field.getName());
            }
        }
        tokenNames = builder.build();
    }

    /**
     * Get the display string for the token type
     * 
     * @return display string for the token type
     */
    @Override
    protected String getTypeString()
    {
        initializeTokenNames();
        return tokenNames.get(getType());
    }

    @Override
    public ASTokenKind getTokenKind()
    {
        switch (getType())
        {
            case EOF:
                return ASTokenKind.EOF;
            case HIDDEN_TOKEN_COMMENT:
            case HIDDEN_TOKEN_MULTI_LINE_COMMENT:
            case HIDDEN_TOKEN_SINGLE_LINE_COMMENT:
            case TOKEN_ASDOC_COMMENT:
                return ASTokenKind.COMMENT;
            case TOKEN_MODIFIER_DYNAMIC:
            case TOKEN_MODIFIER_FINAL:
            case TOKEN_MODIFIER_NATIVE:
            case TOKEN_MODIFIER_OVERRIDE:
            case TOKEN_MODIFIER_STATIC:
                return ASTokenKind.MODIFIER;
            case HIDDEN_TOKEN_BUILTIN_NS:
            case TOKEN_NAMESPACE_ANNOTATION:
                return ASTokenKind.NAMESPACE;
            case TOKEN_DIRECTIVE_DEFAULT_XML:
                return ASTokenKind.DEFAULT_XML_STATEMENT;
            case TOKEN_BLOCK_CLOSE:
                return ASTokenKind.SCOPE_CLOSE;
            case TOKEN_BLOCK_OPEN:
                return ASTokenKind.SCOPE_OPEN;
            case TOKEN_LITERAL_STRING:
                return ASTokenKind.STRING_LITERAL;
            case TOKEN_LITERAL_NUMBER:
            case TOKEN_LITERAL_HEX_NUMBER:
                return ASTokenKind.NUMBER_LITERAL;
            case TOKEN_KEYWORD_TRUE:
            case TOKEN_KEYWORD_FALSE:
                return ASTokenKind.BOOLEAN_LITERAL;
            case TOKEN_VOID_0:
            case TOKEN_KEYWORD_NULL:
                return ASTokenKind.OBJECT_LITERAL;
            case TOKEN_LITERAL_REGEXP:
                return ASTokenKind.REGEX_LITERAL;
            case TOKEN_LITERAL_XMLLIST:
            case TOKEN_E4X_XMLLIST_CLOSE:
                return ASTokenKind.XMLLIST_LITERAL;
            case TOKEN_PAREN_OPEN:
                return ASTokenKind.PAREN_OPEN;
            case TOKEN_PAREN_CLOSE:
                return ASTokenKind.PAREN_CLOSE;
            case TOKEN_SQUARE_OPEN:
                return ASTokenKind.BRACKET_OPEN;
            case TOKEN_SQUARE_CLOSE:
                return ASTokenKind.BRACKET_CLOSE;
            case TOKEN_ATTRIBUTE:
                return ASTokenKind.METADATA;
            case TOKEN_SEMICOLON:
                return ASTokenKind.SEMICOLON;
            case TOKEN_COLON:
                return ASTokenKind.COLON;
            case TOKEN_COMMA:
                return ASTokenKind.OPERATOR;
            case TOKEN_E4X_BINDING_CLOSE:
                return ASTokenKind.E4X_BINDING_CLOSE;
            case TOKEN_E4X_BINDING_OPEN:
                return ASTokenKind.E4X_BINDING_OPEN;
            case TOKEN_TYPED_COLLECTION_CLOSE:
            case TOKEN_TYPED_LITERAL_CLOSE:
                return ASTokenKind.TYPED_COLLECTION_CLOSE;
            case TOKEN_TYPED_COLLECTION_OPEN:
            case TOKEN_TYPED_LITERAL_OPEN:
                return ASTokenKind.TYPED_COLLECTION_OPEN;
            case TOKEN_ELLIPSIS:
                return ASTokenKind.OPERATOR;
            case TOKEN_KEYWORD_INCLUDE:
                return ASTokenKind.INCLUDE;
            default:
                if (isKeywordOrContextualReservedWord())
                    return ASTokenKind.KEYWORD;
                if (isOperator())
                    return ASTokenKind.OPERATOR;
                if (getType() == TOKEN_IDENTIFIER) //handle this after keywords
                    return ASTokenKind.IDENTIFIER;
                if (isE4X())
                    return ASTokenKind.XML_LITERAL;
        }
        return ASTokenKind.UNKNOWN;
    }

    /**
     * Get {@code IASToken.ASTokenKind} from a given token type.
     * 
     * @param type AS token type.
     * @return Token kind.
     */
    public static synchronized ASTokenKind typeToKind(final int type)
    {
        TOKEN_TYPE_TO_KIND_CONVERTER.setType(type);
        return TOKEN_TYPE_TO_KIND_CONVERTER.getTokenKind();
    }

    /**
     * Surrogate token used by {@link #typeToKind(int)} so that we don't have to
     * create a new {@code ASToken} object every time we convert.
     */
    private static final ASToken TOKEN_TYPE_TO_KIND_CONVERTER =
            new ASToken(ASTokenTypes.EOF, UNKNOWN, UNKNOWN, UNKNOWN, UNKNOWN, "");

    @Override
    public final boolean isMultiLineComment()
    {
        return getType() == HIDDEN_TOKEN_MULTI_LINE_COMMENT || getType() == TOKEN_ASDOC_COMMENT;
    }

}
