/*
 * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package jdk.nashorn.internal.codegen;

import static jdk.nashorn.internal.lookup.Lookup.MH;

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import jdk.internal.org.objectweb.asm.MethodVisitor;
import jdk.internal.org.objectweb.asm.Opcodes;
import jdk.nashorn.internal.codegen.types.Type;
import jdk.nashorn.internal.runtime.ScriptFunction;
import jdk.nashorn.internal.runtime.ScriptObject;
import jdk.nashorn.internal.runtime.Source;

/**
 * This class represents constant names of variables, methods and fields in
 * the compiler
 */

public enum CompilerConstants {
    /** the __FILE__ variable */
    __FILE__,

    /** the __DIR__ variable */
    __DIR__,

    /** the __LINE__ variable */
    __LINE__,

    /** constructor name */
    INIT("<init>"),

    /** static initializer name */
    CLINIT("<clinit>"),

    /** eval name */
    EVAL("eval"),

    /** source name and class */
    SOURCE("source", Source.class),

    /** constants name and class */
    CONSTANTS("constants", Object[].class),

    /** strict mode field name and type */
    STRICT_MODE("strictMode", boolean.class),

    /** default script name */
    DEFAULT_SCRIPT_NAME("Script"),

    /** function prefix for anonymous functions */
    ANON_FUNCTION_PREFIX("L:"),

    /** method name for Java method that is the program entry point */
    PROGRAM(":program"),

    /** method name for Java method that creates the script function for the program */
    CREATE_PROGRAM_FUNCTION(":createProgramFunction"),

    /**
     * "this" name symbol for a parameter representing ECMAScript "this" in static methods that are compiled
     * representations of ECMAScript functions. It is not assigned a slot, as its position in the method signature is
     * dependent on other factors (most notably, callee can precede it).
     */
    THIS("this", Object.class),

    /** this debugger symbol */
    THIS_DEBUGGER(":this"),

    /** scope name, type and slot */
    SCOPE(":scope", ScriptObject.class, 2),

    /** the return value variable name were intermediate results are stored for scripts */
    RETURN(":return"),

    /** the callee value variable when necessary */
    CALLEE(":callee", ScriptFunction.class),

    /** the varargs variable when necessary */
    VARARGS(":varargs", Object[].class),

    /** the arguments variable (visible to function body). Initially set to ARGUMENTS, but can be reassigned by code in
     * the function body.*/
    ARGUMENTS_VAR("arguments", Object.class),

    /** the internal arguments object, when necessary (not visible to scripts, can't be reassigned). */
    ARGUMENTS(":arguments", ScriptObject.class),

    /** prefix for apply-to-call exploded arguments */
    EXPLODED_ARGUMENT_PREFIX(":xarg"),

    /** prefix for iterators for for (x in ...) */
    ITERATOR_PREFIX(":i", Iterator.class),

    /** prefix for tag variable used for switch evaluation */
    SWITCH_TAG_PREFIX(":s"),

    /** prefix for JVM exceptions */
    EXCEPTION_PREFIX(":e", Throwable.class),

    /** prefix for quick slots generated in Store */
    QUICK_PREFIX(":q"),

    /** prefix for temporary variables */
    TEMP_PREFIX(":t"),

    /** prefix for literals */
    LITERAL_PREFIX(":l"),

    /** prefix for regexps */
    REGEX_PREFIX(":r"),

    /** "this" used in non-static Java methods; always in slot 0 */
    JAVA_THIS(null, 0),

    /** Map parameter in scope object constructors; always in slot 1 */
    INIT_MAP(null, 1),

    /** Parent scope parameter in scope object constructors; always in slot 2 */
    INIT_SCOPE(null, 2),

    /** Arguments parameter in scope object constructors; in slot 3 when present */
    INIT_ARGUMENTS(null, 3),

    /** prefix for all ScriptObject subclasses with dual object/primitive fields, see {@link ObjectClassGenerator} */
    JS_OBJECT_DUAL_FIELD_PREFIX("JD"),

    /** prefix for all ScriptObject subclasses with object fields only, see {@link ObjectClassGenerator} */
    JS_OBJECT_SINGLE_FIELD_PREFIX("JO"),

    /** name for allocate method in JO objects */
    ALLOCATE("allocate"),

    /** prefix for split methods, @see Splitter */
    SPLIT_PREFIX(":split"),

    /** prefix for split array method and slot */
    SPLIT_ARRAY_ARG(":split_array", 3),

    /** get string from constant pool */
    GET_STRING(":getString"),

    /** get map */
    GET_MAP(":getMap"),

    /** set map */
    SET_MAP(":setMap"),

    /** get array prefix */
    GET_ARRAY_PREFIX(":get"),

    /** get array suffix */
    GET_ARRAY_SUFFIX("$array");

    /** To save memory - intern the compiler constant symbol names, as they are frequently reused */
    static {
        for (final CompilerConstants c : values()) {
            final String symbolName = c.symbolName();
            if (symbolName != null) {
                symbolName.intern();
            }
        }
    }

    private static Set<String> symbolNames;

    /**
     * Prefix used for internal methods generated in script clases.
     */
    private static final String INTERNAL_METHOD_PREFIX = ":";

    private final String symbolName;
    private final Class<?> type;
    private final int slot;

    private CompilerConstants() {
        this.symbolName = name();
        this.type = null;
        this.slot = -1;
    }

    private CompilerConstants(final String symbolName) {
        this(symbolName, -1);
    }

    private CompilerConstants(final String symbolName, final int slot) {
        this(symbolName, null, slot);
    }

    private CompilerConstants(final String symbolName, final Class<?> type) {
        this(symbolName, type, -1);
    }

    private CompilerConstants(final String symbolName, final Class<?> type, final int slot) {
        this.symbolName = symbolName;
        this.type       = type;
        this.slot       = slot;
    }

    /**
     * Check whether a name is that of a reserved compiler constnat
     * @param name name
     * @return true if compiler constant name
     */
    public static boolean isCompilerConstant(final String name) {
        ensureSymbolNames();
        return symbolNames.contains(name);
    }

    private static void ensureSymbolNames() {
        if(symbolNames == null) {
            symbolNames = new HashSet<>();
            for(final CompilerConstants cc: CompilerConstants.values()) {
                symbolNames.add(cc.symbolName);
            }
        }
    }

    /**
     * Return the tag for this compile constant. Deliberately avoiding "name" here
     * not to conflate with enum implementation. This is the master string for the
     * constant - every constant has one.
     *
     * @return the tag
     */
    public final String symbolName() {
        return symbolName;
    }

    /**
     * Return the type for this compile constant
     *
     * @return type for this constant's instances, or null if N/A
     */
    public final Class<?> type() {
        return type;
    }

    /**
     * Return the slot for this compile constant
     *
     * @return byte code slot where constant is stored or -1 if N/A
     */
    public final int slot() {
        return slot;
    }

    /**
     * Return a descriptor for this compile constant. Only relevant if it has
     * a type
     *
     * @return descriptor the descriptor
     */
    public final String descriptor() {
        assert type != null : " asking for descriptor of typeless constant";
        return typeDescriptor(type);
    }

    /**
     * Get the internal class name for a type
     *
     * @param type a type
     * @return  the internal name for this type
     */
    public static String className(final Class<?> type) {
        return Type.getInternalName(type);
    }

    /**
     * Get the method descriptor for a given method type collection
     *
     * @param rtype  return type
     * @param ptypes parameter types
     *
     * @return internal descriptor for this method
     */
    public static String methodDescriptor(final Class<?> rtype, final Class<?>... ptypes) {
        return Type.getMethodDescriptor(rtype, ptypes);
    }

    /**
     * Get the type descriptor for a type
     *
     * @param clazz a type
     *
     * @return the internal descriptor for this type
     */
    public static String typeDescriptor(final Class<?> clazz) {
        return Type.typeFor(clazz).getDescriptor();
    }

    /**
     * Create a call representing a void constructor for a given type. Don't
     * attempt to look this up at compile time
     *
     * @param clazz the class
     *
     * @return Call representing void constructor for type
     */
    public static Call constructorNoLookup(final Class<?> clazz) {
        return specialCallNoLookup(clazz, INIT.symbolName(), void.class);
    }

    /**
     * Create a call representing a constructor for a given type. Don't
     * attempt to look this up at compile time
     *
     * @param className the type class name
     * @param ptypes    the parameter types for the constructor
     *
     * @return Call representing constructor for type
     */
    public static Call constructorNoLookup(final String className, final Class<?>... ptypes) {
        return specialCallNoLookup(className, INIT.symbolName(), methodDescriptor(void.class, ptypes));
    }

    /**
     * Create a call representing a constructor for a given type. Don't
     * attempt to look this up at compile time
     *
     * @param clazz  the class name
     * @param ptypes the parameter types for the constructor
     *
     * @return Call representing constructor for type
     */
    public static Call constructorNoLookup(final Class<?> clazz, final Class<?>... ptypes) {
        return specialCallNoLookup(clazz, INIT.symbolName(), void.class, ptypes);
    }

    /**
     * Create a call representing an invokespecial to a given method. Don't
     * attempt to look this up at compile time
     *
     * @param className the class name
     * @param name      the method name
     * @param desc      the descriptor
     *
     * @return Call representing specified invokespecial call
     */
    public static Call specialCallNoLookup(final String className, final String name, final String desc) {
        return new Call(null, className, name, desc) {
            @Override
            MethodEmitter invoke(final MethodEmitter method) {
                return method.invokespecial(className, name, descriptor);
            }

            @Override
            public void invoke(final MethodVisitor mv) {
                mv.visitMethodInsn(Opcodes.INVOKESPECIAL, className, name, desc, false);
            }
        };
    }

    /**
     * Create a call representing an invokespecial to a given method. Don't
     * attempt to look this up at compile time
     *
     * @param clazz  the class
     * @param name   the method name
     * @param rtype  the return type
     * @param ptypes the parameter types
     *
     * @return Call representing specified invokespecial call
     */
    public static Call specialCallNoLookup(final Class<?> clazz, final String name, final Class<?> rtype, final Class<?>... ptypes) {
        return specialCallNoLookup(className(clazz), name, methodDescriptor(rtype, ptypes));
    }

    /**
     * Create a call representing an invokestatic to a given method. Don't
     * attempt to look this up at compile time
     *
     * @param className the class name
     * @param name      the method name
     * @param desc      the descriptor
     *
     * @return Call representing specified invokestatic call
     */
    public static Call staticCallNoLookup(final String className, final String name, final String desc) {
        return new Call(null, className, name, desc) {
            @Override
            MethodEmitter invoke(final MethodEmitter method) {
                return method.invokestatic(className, name, descriptor);
            }

            @Override
            public void invoke(final MethodVisitor mv) {
                mv.visitMethodInsn(Opcodes.INVOKESTATIC, className, name, desc, false);
            }
        };
    }

    /**
     * Create a call representing an invokestatic to a given method. Don't
     * attempt to look this up at compile time
     *
     * @param clazz  the class
     * @param name   the method name
     * @param rtype  the return type
     * @param ptypes the parameter types
     *
     * @return Call representing specified invokestatic call
     */
    public static Call staticCallNoLookup(final Class<?> clazz, final String name, final Class<?> rtype, final Class<?>... ptypes) {
        return staticCallNoLookup(className(clazz), name, methodDescriptor(rtype, ptypes));
    }

    /**
     * Create a call representing an invokevirtual to a given method. Don't
     * attempt to look this up at compile time
     *
     * @param clazz  the class
     * @param name   the method name
     * @param rtype  the return type
     * @param ptypes the parameter types
     *
     * @return Call representing specified invokevirtual call
     */
    public static Call virtualCallNoLookup(final Class<?> clazz, final String name, final Class<?> rtype, final Class<?>... ptypes) {
        return new Call(null, className(clazz), name, methodDescriptor(rtype, ptypes)) {
            @Override
            MethodEmitter invoke(final MethodEmitter method) {
                return method.invokevirtual(className, name, descriptor);
            }

            @Override
            public void invoke(final MethodVisitor mv) {
                mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, className, name, descriptor, false);
            }
        };
    }

    /**
     * Create a call representing an invokeinterface to a given method. Don't
     * attempt to look this up at compile time
     *
     * @param clazz  the class
     * @param name   the method name
     * @param rtype  the return type
     * @param ptypes the parameter types
     *
     * @return Call representing specified invokeinterface call
     */
    public static Call interfaceCallNoLookup(final Class<?> clazz, final String name, final Class<?> rtype, final Class<?>... ptypes) {
        return new Call(null, className(clazz), name, methodDescriptor(rtype, ptypes)) {
            @Override
            MethodEmitter invoke(final MethodEmitter method) {
                return method.invokeinterface(className, name, descriptor);
            }

            @Override
            public void invoke(final MethodVisitor mv) {
                mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, className, name, descriptor, true);
            }
        };
    }

    /**
     * Create a FieldAccess representing a virtual field, that can be subject to put
     * or get operations
     *
     * @param className name of the class where the field is a member
     * @param name      name of the field
     * @param desc      type descriptor of the field
     *
     * @return a field access object giving access code generation method for the virtual field
     */
    public static FieldAccess virtualField(final String className, final String name, final String desc) {
        return new FieldAccess(className, name, desc) {
            @Override
            public MethodEmitter get(final MethodEmitter method) {
                return method.getField(className, name, descriptor);
            }

            @Override
            public void put(final MethodEmitter method) {
                method.putField(className, name, descriptor);
            }
        };
    }

    /**
     * Create a FieldAccess representing a virtual field, that can be subject to put
     * or get operations
     *
     * @param clazz class where the field is a member
     * @param name  name of the field
     * @param type  type of the field
     *
     * @return a field access object giving access code generation method for the virtual field
     */
    public static FieldAccess virtualField(final Class<?> clazz, final String name, final Class<?> type) {
        return virtualField(className(clazz), name, typeDescriptor(type));
    }

    /**
     * Create a FieldAccess representing a static field, that can be subject to put
     * or get operations
     *
     * @param className name of the class where the field is a member
     * @param name      name of the field
     * @param desc      type descriptor of the field
     *
     * @return a field access object giving access code generation method for the static field
     */
    public static FieldAccess staticField(final String className, final String name, final String desc) {
        return new FieldAccess(className, name, desc) {
            @Override
            public MethodEmitter get(final MethodEmitter method) {
                return method.getStatic(className, name, descriptor);
            }

            @Override
            public void put(final MethodEmitter method) {
                method.putStatic(className, name, descriptor);
            }
        };
    }

    /**
     * Create a FieldAccess representing a static field, that can be subject to put
     * or get operations
     *
     * @param clazz class where the field is a member
     * @param name  name of the field
     * @param type  type of the field
     *
     * @return a field access object giving access code generation method for the virtual field
     */
    public static FieldAccess staticField(final Class<?> clazz, final String name, final Class<?> type) {
        return staticField(className(clazz), name, typeDescriptor(type));
    }

    /**
     * Create a static call, given an explicit lookup, looking up the method handle for it at the same time
     *
     * @param lookup the lookup
     * @param clazz  the class
     * @param name   the name of the method
     * @param rtype  the return type
     * @param ptypes the parameter types
     *
     * @return the call object representing the static call
     */
    public static Call staticCall(final MethodHandles.Lookup lookup, final Class<?> clazz, final String name, final Class<?> rtype, final Class<?>... ptypes) {
        return new Call(MH.findStatic(lookup, clazz, name, MH.type(rtype, ptypes)), className(clazz), name, methodDescriptor(rtype, ptypes)) {
            @Override
            MethodEmitter invoke(final MethodEmitter method) {
                return method.invokestatic(className, name, descriptor);
            }

            @Override
            public void invoke(final MethodVisitor mv) {
                mv.visitMethodInsn(Opcodes.INVOKESTATIC, className, name, descriptor, false);
            }
        };
    }

    /**
     * Create a virtual call, given an explicit lookup, looking up the method handle for it at the same time
     *
     * @param lookup the lookup
     * @param clazz  the class
     * @param name   the name of the method
     * @param rtype  the return type
     * @param ptypes the parameter types
     *
     * @return the call object representing the virtual call
     */
    public static Call virtualCall(final MethodHandles.Lookup lookup, final Class<?> clazz, final String name, final Class<?> rtype, final Class<?>... ptypes) {
        return new Call(MH.findVirtual(lookup, clazz, name, MH.type(rtype, ptypes)), className(clazz), name, methodDescriptor(rtype, ptypes)) {
            @Override
            MethodEmitter invoke(final MethodEmitter method) {
                return method.invokevirtual(className, name, descriptor);
            }

            @Override
            public void invoke(final MethodVisitor mv) {
                mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, className, name, descriptor, false);
            }
        };
    }

    /**
     * Create a special call, given an explicit lookup, looking up the method handle for it at the same time.
     * clazz is used as this class
     *
     * @param lookup    the lookup
     * @param clazz     the class
     * @param name      the name of the method
     * @param rtype     the return type
     * @param ptypes    the parameter types
     *
     * @return the call object representing the virtual call
     */
    public static Call specialCall(final MethodHandles.Lookup lookup, final Class<?> clazz, final String name, final Class<?> rtype, final Class<?>... ptypes) {
        return new Call(MH.findSpecial(lookup, clazz, name, MH.type(rtype, ptypes), clazz), className(clazz), name, methodDescriptor(rtype, ptypes)) {
            @Override
            MethodEmitter invoke(final MethodEmitter method) {
                return method.invokespecial(className, name, descriptor);
            }

            @Override
            public void invoke(final MethodVisitor mv) {
                mv.visitMethodInsn(Opcodes.INVOKESPECIAL, className, name, descriptor, false);
            }
        };
    }

    /**
     * Returns true if the passed string looks like a method name of an internally generated Nashorn method. Basically,
     * if it starts with a colon character {@code :} but is not the name of the program method {@code :program}.
     * Program function is not considered internal as we want it to show up in exception stack traces.
     * @param methodName the name of a method
     * @return true if it looks like an internal Nashorn method name.
     * @throws NullPointerException if passed null
     */
    public static boolean isInternalMethodName(final String methodName) {
        return methodName.startsWith(INTERNAL_METHOD_PREFIX) && !methodName.equals(PROGRAM.symbolName);
     }

    /**
     * Private class representing an access. This can generate code into a method code or
     * a field access.
     */
    private abstract static class Access {
        protected final MethodHandle methodHandle;
        protected final String       className;
        protected final String       name;
        protected final String       descriptor;

        /**
         * Constructor
         *
         * @param methodHandle methodHandle or null if none
         * @param className    class name for access
         * @param name         field or method name for access
         * @param descriptor   descriptor for access field or method
         */
        protected Access(final MethodHandle methodHandle, final String className, final String name, final String descriptor) {
            this.methodHandle = methodHandle;
            this.className    = className;
            this.name         = name;
            this.descriptor   = descriptor;
        }

        /**
         * Get the method handle, or null if access hasn't been looked up
         *
         * @return method handle
         */
        public MethodHandle methodHandle() {
            return methodHandle;
        }

        /**
         * Get the class name of the access
         *
         * @return the class name
         */
        public String className() {
            return className;
        }

        /**
         * Get the field name or method name of the access
         *
         * @return the name
         */
        public String name() {
            return name;
        }

        /**
         * Get the descriptor of the method or field of the access
         *
         * @return the descriptor
         */
        public String descriptor() {
            return descriptor;
        }
    }

    /**
     * Field access - this can be used for generating code for static or
     * virtual field accesses
     */
    public abstract static class FieldAccess extends Access {
        /**
         * Constructor
         *
         * @param className  name of the class where the field is
         * @param name       name of the field
         * @param descriptor descriptor of the field
         */
        protected FieldAccess(final String className, final String name, final String descriptor) {
            super(null, className, name, descriptor);
        }

        /**
         * Generate get code for the field
         *
         * @param emitter a method emitter
         *
         * @return the method emitter
         */
        protected abstract MethodEmitter get(final MethodEmitter emitter);

        /**
         * Generate put code for the field
         *
         * @param emitter a method emitter
         */
        protected abstract void put(final MethodEmitter emitter);
    }

    /**
     * Call - this can be used for generating code for different types of calls
     */
    public abstract static class Call extends Access {

        /**
         * Constructor
         *
         * @param className  class name for the method of the call
         * @param name       method name
         * @param descriptor method descriptor
         */
        protected Call(final String className, final String name, final String descriptor) {
            super(null, className, name, descriptor);
        }

        /**
         * Constructor
         *
         * @param methodHandle method handle for the call if resolved
         * @param className    class name for the method of the call
         * @param name         method name
         * @param descriptor   method descriptor
         */
        protected Call(final MethodHandle methodHandle, final String className, final String name, final String descriptor) {
            super(methodHandle, className, name, descriptor);
        }

        /**
         * Generate invocation code for the method
         *
         * @param emitter a method emitter
         *
         * @return the method emitter
         */
        abstract MethodEmitter invoke(final MethodEmitter emitter);

        /**
         * Generate invocation code for the method
         *
         * @param mv a method visitor
         */
        public abstract void invoke(final MethodVisitor mv);
    }

}
