/*
 * 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 java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.BitSet;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import jdk.nashorn.internal.codegen.types.Type;

/**
 * Abstraction for labels, separating a label from the underlying
 * byte code emitter. Also augmenting label with e.g. a name
 * for easier debugging and reading code
 *
 * see -Dnashorn.codegen.debug, --log=codegen
 */
public final class Label implements Serializable {
    private static final long serialVersionUID = 1L;

    //byte code generation evaluation type stack for consistency check
    //and correct opcode selection. one per label as a label may be a
    //join point
    static final class Stack implements Cloneable {
        static final int NON_LOAD = -1;

        Type[] data;
        int[]  localLoads;
        int    sp;

        List<Type> localVariableTypes;
        int firstTemp; // index of the first temporary local variable
        // Bitmap marking last slot belonging to a single symbol.
        BitSet symbolBoundary;

        Stack() {
            data = new Type[8];
            localLoads = new int[8];
            localVariableTypes = new ArrayList<>(8);
            symbolBoundary = new BitSet();
        }

        boolean isEmpty() {
            return sp == 0;
        }

        int size() {
            return sp;
        }

        void clear() {
            sp = 0;
        }

        void push(final Type type) {
            if (data.length == sp) {
                final Type[] newData = new Type[sp * 2];
                final int[]  newLocalLoad = new int[sp * 2];
                System.arraycopy(data, 0, newData, 0, sp);
                System.arraycopy(localLoads, 0, newLocalLoad, 0, sp);
                data = newData;
                localLoads = newLocalLoad;
            }
            data[sp] = type;
            localLoads[sp] = NON_LOAD;
            sp++;
        }

        Type peek() {
            return peek(0);
        }

        Type peek(final int n) {
            final int pos = sp - 1 - n;
            return pos < 0 ? null : data[pos];
        }

        /**
         * Retrieve the top <code>count</code> types on the stack without modifying it.
         *
         * @param count number of types to return
         * @return array of Types
         */
        Type[] getTopTypes(final int count) {
            final Type[] topTypes = new Type[count];
            System.arraycopy(data, sp - count, topTypes, 0, count);
            return topTypes;
        }

        int[] getLocalLoads(final int from, final int to) {
            final int count = to - from;
            final int[] topLocalLoads = new int[count];
            System.arraycopy(localLoads, from, topLocalLoads, 0, count);
            return topLocalLoads;
        }

        /**
         * Returns the number of used local variable slots, including all live stack-store temporaries.
         * @return the number of used local variable slots, including all live stack-store temporaries.
         */
        int getUsedSlotsWithLiveTemporaries() {
            // There are at least as many as are declared by the current blocks.
            int usedSlots = firstTemp;
            // Look at every load on the stack, and bump the number of used slots up by the temporaries seen there.
            for(int i = sp; i-->0;) {
                final int slot = localLoads[i];
                if(slot != Label.Stack.NON_LOAD) {
                    final int afterSlot = slot + localVariableTypes.get(slot).getSlots();
                    if(afterSlot > usedSlots) {
                        usedSlots = afterSlot;
                    }
                }
            }
            return usedSlots;
        }

        /**
         *
         * @param joinOrigin the stack from the other branch.
         */
        void joinFrom(final Stack joinOrigin, final boolean breakTarget) {
            assert isStackCompatible(joinOrigin);
            if(breakTarget) {
                // As we're joining labels that can jump across block boundaries, the number of local variables can
                // differ, and we should always respect the one having less variables.
                firstTemp = Math.min(firstTemp, joinOrigin.firstTemp);
            } else {
                assert firstTemp == joinOrigin.firstTemp;
            }
            final int[] otherLoads = joinOrigin.localLoads;
            int firstDeadTemp = firstTemp;
            for(int i = 0; i < sp; ++i) {
                final int localLoad = localLoads[i];
                if(localLoad != otherLoads[i]) {
                    localLoads[i] = NON_LOAD;
                } else if(localLoad >= firstDeadTemp) {
                    firstDeadTemp = localLoad + localVariableTypes.get(localLoad).getSlots();
                }
            }
            // Eliminate dead temporaries
            undefineLocalVariables(firstDeadTemp, false);
            assert isVariablePartitioningEqual(joinOrigin, firstDeadTemp);
            mergeVariableTypes(joinOrigin, firstDeadTemp);
        }

        private void mergeVariableTypes(final Stack joinOrigin, final int toSlot) {
            final ListIterator<Type> it1 = localVariableTypes.listIterator();
            final Iterator<Type> it2 = joinOrigin.localVariableTypes.iterator();

            for(int i = 0; i < toSlot; ++i) {
                final Type thisType = it1.next();
                final Type otherType = it2.next();
                if(otherType == Type.UNKNOWN) {
                    // Variables that are <unknown> on the other branch will become <unknown> here too.
                    it1.set(Type.UNKNOWN);
                } else if (thisType != otherType) {
                    if(thisType.isObject() && otherType.isObject()) {
                        // different object types are merged into Object.
                        // TODO: maybe find most common superclass?
                        it1.set(Type.OBJECT);
                    } else {
                        assert thisType == Type.UNKNOWN;
                    }
                }
            }
        }

        void joinFromTry(final Stack joinOrigin) {
            // As we're joining labels that can jump across block boundaries, the number of local variables can
            // differ, and we should always respect the one having less variables.
            firstTemp = Math.min(firstTemp, joinOrigin.firstTemp);
            assert isVariablePartitioningEqual(joinOrigin, firstTemp);
            mergeVariableTypes(joinOrigin, firstTemp);
        }

        private int getFirstDeadLocal(final List<Type> types) {
            int i = types.size();
            for(final ListIterator<Type> it = types.listIterator(i);
                it.hasPrevious() && it.previous() == Type.UNKNOWN;
                --i) {
                // no body
            }

            // Respect symbol boundaries; we never chop off half a symbol's storage
            while(!symbolBoundary.get(i - 1)) {
                ++i;
            }
            return i;
        }

        private boolean isStackCompatible(final Stack other) {
            if (sp != other.sp) {
                return false;
            }
            for (int i = 0; i < sp; i++) {
                if (!data[i].isEquivalentTo(other.data[i])) {
                    return false;
                }
            }
            return true;
        }

        private boolean isVariablePartitioningEqual(final Stack other, final int toSlot) {
            // No difference in the symbol boundaries before the toSlot
            final BitSet diff = other.getSymbolBoundaryCopy();
            diff.xor(symbolBoundary);
            return diff.previousSetBit(toSlot - 1) == -1;
        }

        void markDeadLocalVariables(final int fromSlot, final int slotCount) {
            final int localCount = localVariableTypes.size();
            if(fromSlot >= localCount) {
                return;
            }
            final int toSlot = Math.min(fromSlot + slotCount, localCount);
            invalidateLocalLoadsOnStack(fromSlot, toSlot);
            for(int i = fromSlot; i < toSlot; ++i) {
                localVariableTypes.set(i, Type.UNKNOWN);
            }
        }

        @SuppressWarnings("unchecked")
        List<Type> getLocalVariableTypesCopy() {
            return (List<Type>)((ArrayList<Type>)localVariableTypes).clone();
        }

        BitSet getSymbolBoundaryCopy() {
            return (BitSet)symbolBoundary.clone();
        }

        /**
         * Returns a list of local variable slot types, but for those symbols that have multiple values, only the slot
         * holding the widest type is marked as live.
         * @return a list of widest local variable slot types.
         */
        List<Type> getWidestLiveLocals(final List<Type> lvarTypes) {
            final List<Type> widestLiveLocals = new ArrayList<>(lvarTypes);
            boolean keepNextValue = true;
            final int size = widestLiveLocals.size();
            for(int i = size - 1; i-- > 0;) {
                if(symbolBoundary.get(i)) {
                    keepNextValue = true;
                }
                final Type t = widestLiveLocals.get(i);
                if(t != Type.UNKNOWN) {
                    if(keepNextValue) {
                        if(t != Type.SLOT_2) {
                            keepNextValue = false;
                        }
                    } else {
                        widestLiveLocals.set(i, Type.UNKNOWN);
                    }
                }
            }
            widestLiveLocals.subList(Math.max(getFirstDeadLocal(widestLiveLocals), firstTemp), widestLiveLocals.size()).clear();
            return widestLiveLocals;
        }

        String markSymbolBoundariesInLvarTypesDescriptor(final String lvarDescriptor) {
            final char[] chars = lvarDescriptor.toCharArray();
            int j = 0;
            for(int i = 0; i < chars.length; ++i) {
                final char c = chars[i];
                final int nextj = j + CodeGeneratorLexicalContext.getTypeForSlotDescriptor(c).getSlots();
                if(!symbolBoundary.get(nextj - 1)) {
                    chars[i] = Character.toLowerCase(c);
                }
                j = nextj;
            }
            return new String(chars);
        }

        Type pop() {
            assert sp > 0;
            return data[--sp];
        }

        @Override
        public Stack clone() {
            try {
                final Stack clone = (Stack)super.clone();
                clone.data = data.clone();
                clone.localLoads = localLoads.clone();
                clone.symbolBoundary = getSymbolBoundaryCopy();
                clone.localVariableTypes = getLocalVariableTypesCopy();
                return clone;
            } catch(final CloneNotSupportedException e) {
                throw new AssertionError("", e);
            }
        }

        private Stack cloneWithEmptyStack() {
            final Stack stack = clone();
            stack.sp = 0;
            return stack;
        }

        int getTopLocalLoad() {
            return localLoads[sp - 1];
        }

        void markLocalLoad(final int slot) {
            localLoads[sp - 1] = slot;
        }

        /**
         * Performs various bookeeping when a value is stored in a local variable slot.
         * @param slot the slot written to
         * @param onlySymbolLiveValue if true, this is the symbol's only live value, and other values of the symbol
         * should be marked dead
         * @param type the type written to the slot
         */
        void onLocalStore(final Type type, final int slot, final boolean onlySymbolLiveValue) {
            if(onlySymbolLiveValue) {
                final int fromSlot = slot == 0 ? 0 : (symbolBoundary.previousSetBit(slot - 1) + 1);
                final int toSlot = symbolBoundary.nextSetBit(slot) + 1;
                for(int i = fromSlot; i < toSlot; ++i) {
                    localVariableTypes.set(i, Type.UNKNOWN);
                }
                invalidateLocalLoadsOnStack(fromSlot, toSlot);
            } else {
                invalidateLocalLoadsOnStack(slot, slot + type.getSlots());
            }

            localVariableTypes.set(slot, type);
            if(type.isCategory2()) {
                localVariableTypes.set(slot + 1, Type.SLOT_2);
            }
        }

        /**
         * Given a slot range, invalidate knowledge about local loads on stack from these slots (because they're being
         * killed).
         * @param fromSlot first slot, inclusive.
         * @param toSlot last slot, exclusive.
         */
        private void invalidateLocalLoadsOnStack(final int fromSlot, final int toSlot) {
            for(int i = 0; i < sp; ++i) {
                final int localLoad = localLoads[i];
                if(localLoad >= fromSlot && localLoad < toSlot) {
                    localLoads[i] = NON_LOAD;
                }
            }
        }

        /**
         * Marks a range of slots as belonging to a defined local variable. The slots will start out with no live value
         * in them.
         * @param fromSlot first slot, inclusive.
         * @param toSlot last slot, exclusive.
         */
        void defineBlockLocalVariable(final int fromSlot, final int toSlot) {
            defineLocalVariable(fromSlot, toSlot);
            assert firstTemp < toSlot;
            firstTemp = toSlot;
        }

        /**
         * Defines a new temporary local variable and returns its allocated index.
         * @param width the required width (in slots) for the new variable.
         * @return the bytecode slot index where the newly allocated local begins.
         */
        int defineTemporaryLocalVariable(final int width) {
            final int fromSlot = getUsedSlotsWithLiveTemporaries();
            defineLocalVariable(fromSlot, fromSlot + width);
            return fromSlot;
        }

        /**
         * Marks a range of slots as belonging to a defined temporary local variable. The slots will start out with no
         * live value in them.
         * @param fromSlot first slot, inclusive.
         * @param toSlot last slot, exclusive.
         */
        void defineTemporaryLocalVariable(final int fromSlot, final int toSlot) {
            defineLocalVariable(fromSlot, toSlot);
        }

        private void defineLocalVariable(final int fromSlot, final int toSlot) {
            assert !hasLoadsOnStack(fromSlot, toSlot);
            assert fromSlot < toSlot;
            symbolBoundary.clear(fromSlot, toSlot - 1);
            symbolBoundary.set(toSlot - 1);
            final int lastExisting = Math.min(toSlot, localVariableTypes.size());
            for(int i = fromSlot; i < lastExisting; ++i) {
                localVariableTypes.set(i, Type.UNKNOWN);
            }
            for(int i = lastExisting; i < toSlot; ++i) {
                localVariableTypes.add(i, Type.UNKNOWN);
            }
        }

        /**
         * Undefines all local variables past the specified slot.
         * @param fromSlot the first slot to be undefined
         * @param canTruncateSymbol if false, the fromSlot must be either the first slot of a symbol, or the first slot
         * after the last symbol. If true, the fromSlot can be in the middle of the storage area for a symbol. This
         * should be used with care - it is only meant for use in optimism exception handlers.
         */
        void undefineLocalVariables(final int fromSlot, final boolean canTruncateSymbol) {
            final int lvarCount = localVariableTypes.size();
            assert lvarCount == symbolBoundary.length();
            assert !hasLoadsOnStack(fromSlot, lvarCount);
            if(canTruncateSymbol) {
                if(fromSlot > 0) {
                    symbolBoundary.set(fromSlot - 1);
                }
            } else {
                assert fromSlot == 0 || symbolBoundary.get(fromSlot - 1);
            }
            if(fromSlot < lvarCount) {
                symbolBoundary.clear(fromSlot, lvarCount);
                localVariableTypes.subList(fromSlot, lvarCount).clear();
            }
            firstTemp = Math.min(fromSlot, firstTemp);
            assert symbolBoundary.length() == localVariableTypes.size();
            assert symbolBoundary.length() == fromSlot;
        }

        private void markAsOptimisticCatchHandler(final int liveLocalCount) {
            // Live temporaries that are no longer on stack are undefined
            undefineLocalVariables(liveLocalCount, true);
            // Temporaries are promoted
            firstTemp = liveLocalCount;
            // No trailing undefined values
            localVariableTypes.subList(firstTemp, localVariableTypes.size()).clear();
            assert symbolBoundary.length() == firstTemp;
            // Generalize all reference types to Object, and promote boolean to int
            for(final ListIterator<Type> it = localVariableTypes.listIterator(); it.hasNext();) {
                final Type type = it.next();
                if(type == Type.BOOLEAN) {
                    it.set(Type.INT);
                } else if(type.isObject() && type != Type.OBJECT) {
                    it.set(Type.OBJECT);
                }
            }
        }

        /**
         * Returns true if any loads on the stack come from the specified slot range.
         * @param fromSlot start of the range (inclusive)
         * @param toSlot end of the range (exclusive)
         * @return true if any loads on the stack come from the specified slot range.
         */
        boolean hasLoadsOnStack(final int fromSlot, final int toSlot) {
            for(int i = 0; i < sp; ++i) {
                final int load = localLoads[i];
                if(load >= fromSlot && load < toSlot) {
                    return true;
                }
            }
            return false;
        }

        @Override
        public String toString() {
            return "stack=" + Arrays.toString(Arrays.copyOf(data, sp))
                 + ", symbolBoundaries=" + String.valueOf(symbolBoundary)
                 + ", firstTemp=" + firstTemp
                 + ", localTypes=" + String.valueOf(localVariableTypes)
                 ;
        }
    }

    /** Next id for debugging purposes, remove if footprint becomes unmanageable */
    private static int nextId = 0;

    /** Name of this label */
    private final String name;

    /** Type stack at this label */
    private transient Label.Stack stack;

    /** ASM representation of this label */
    private transient jdk.internal.org.objectweb.asm.Label label;

    /** Id for debugging purposes, remove if footprint becomes unmanageable */
    private final int id;

    /** Is this label reachable (anything ever jumped to it)? */
    private transient boolean reachable;

    private transient boolean breakTarget;

    /**
     * Constructor
     *
     * @param name name of this label
     */
    public Label(final String name) {
        super();
        this.name = name;
        this.id   = nextId++;
    }

    /**
     * Copy constructor
     *
     * @param label a label to clone
     */
    public Label(final Label label) {
        super();
        this.name = label.name;
        this.id   = label.id;
    }

    jdk.internal.org.objectweb.asm.Label getLabel() {
        if (this.label == null) {
            this.label = new jdk.internal.org.objectweb.asm.Label();
        }
        return label;
    }

    Label.Stack getStack() {
        return stack;
    }

    void joinFrom(final Label.Stack joinOrigin) {
        this.reachable = true;
        if(stack == null) {
            stack = joinOrigin.clone();
        } else {
            stack.joinFrom(joinOrigin, breakTarget);
        }
    }

    void joinFromTry(final Label.Stack joinOrigin, final boolean isOptimismHandler) {
        this.reachable = true;
        if (stack == null) {
            if(!isOptimismHandler) {
                stack = joinOrigin.cloneWithEmptyStack();
                // Optimism handler needs temporaries to remain live, others don't.
                stack.undefineLocalVariables(stack.firstTemp, false);
            }
        } else {
            assert !isOptimismHandler;
            stack.joinFromTry(joinOrigin);
        }
    }

    void markAsBreakTarget() {
        breakTarget = true;
    }

    boolean isBreakTarget() {
        return breakTarget;
    }

    void onCatch() {
        if(stack != null) {
            stack = stack.cloneWithEmptyStack();
        }
    }
    void markAsOptimisticCatchHandler(final Label.Stack currentStack, final int liveLocalCount) {
        stack = currentStack.cloneWithEmptyStack();
        stack.markAsOptimisticCatchHandler(liveLocalCount);
    }

    void markAsOptimisticContinuationHandlerFor(final Label afterConsumeStackLabel) {
        stack = afterConsumeStackLabel.stack.cloneWithEmptyStack();
    }

    boolean isReachable() {
        return reachable;
    }

    boolean isAfter(final Label other) {
        return label.getOffset() > other.label.getOffset();
    }

    private String str;

    @Override
    public String toString() {
        if (str == null) {
            str = name + '_' + id;
        }
        return str;
    }
}
