/*
 * Javassist, a Java-bytecode translator toolkit.
 * Copyright (C) 1999- Shigeru Chiba. All Rights Reserved.
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License.  Alternatively, the contents of this file may be used under
 * the terms of the GNU Lesser General Public License Version 2.1 or later,
 * or the Apache License Version 2.0.
 *
 * 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.
 */

package javassist.bytecode;

import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.IOException;
import java.util.Map;

import javassist.CannotCompileException;

/**
 * Another <code>stack_map</code> attribute defined in CLDC 1.1 for J2ME.
 *
 * <p>This is an entry in the attributes table of a Code attribute.
 * It was introduced by J2ME CLDC 1.1 (JSR 139) for pre-verification.
 *
 * <p>According to the CLDC specification, the sizes of some fields are not 16bit
 * but 32bit if the code size is more than 64K or the number of the local variables
 * is more than 64K.  However, for the J2ME CLDC technology, they are always 16bit.
 * The implementation of the StackMap class assumes they are 16bit.  
 *
 * @see MethodInfo#doPreverify
 * @see StackMapTable
 * @since 3.12
 */
public class StackMap extends AttributeInfo {
    /**
     * The name of this attribute <code>"StackMap"</code>.
     */
    public static final String tag = "StackMap";


    /**
     * Constructs a <code>stack_map</code> attribute.
     */
    StackMap(ConstPool cp, byte[] newInfo) {
        super(cp, tag, newInfo);
    }

    StackMap(ConstPool cp, int name_id, DataInputStream in)
        throws IOException
    {
        super(cp, name_id, in);
    }

    /**
     * Returns <code>number_of_entries</code>.
     */
    public int numOfEntries() {
    	return ByteArray.readU16bit(info, 0);
    }

    /**
     * <code>Top_variable_info.tag</code>.
     */
    public static final int TOP = 0;

    /**
     * <code>Integer_variable_info.tag</code>.
     */
    public static final int INTEGER = 1;

    /**
     * <code>Float_variable_info.tag</code>.
     */
    public static final int FLOAT = 2;

    /**
     * <code>Double_variable_info.tag</code>.
     */
    public static final int DOUBLE = 3;

    /**
     * <code>Long_variable_info.tag</code>.
     */
    public static final int LONG = 4;

    /**
     * <code>Null_variable_info.tag</code>.
     */
    public static final int NULL = 5;

    /**
     * <code>UninitializedThis_variable_info.tag</code>.
     */
    public static final int THIS = 6;

    /**
     * <code>Object_variable_info.tag</code>.
     */
    public static final int OBJECT = 7;

    /**
     * <code>Uninitialized_variable_info.tag</code>.
     */
    public static final int UNINIT = 8;

    /**
     * Makes a copy.
     */
    @Override
    public AttributeInfo copy(ConstPool newCp, Map<String,String> classnames) {
        Copier copier = new Copier(this, newCp, classnames);
        copier.visit();
        return copier.getStackMap();
    }

    /**
     * A code walker for a StackMap attribute.
     */
    public static class Walker {
        byte[] info;

        /**
         * Constructs a walker.
         */
        public Walker(StackMap sm) {
            info = sm.get();
        }

        /**
         * Visits each entry of the stack map frames. 
         */
        public void visit() {
            int num = ByteArray.readU16bit(info, 0);
            int pos = 2;
            for (int i = 0; i < num; i++) {
                int offset = ByteArray.readU16bit(info, pos);
                int numLoc = ByteArray.readU16bit(info, pos + 2);
                pos = locals(pos + 4, offset, numLoc);
                int numStack = ByteArray.readU16bit(info, pos);
                pos = stack(pos + 2, offset, numStack);
            }
        }

        /**
         * Invoked when <code>locals</code> of <code>stack_map_frame</code>
         * is visited.  
         */
        public int locals(int pos, int offset, int num) {
            return typeInfoArray(pos, offset, num, true);
        }

        /**
         * Invoked when <code>stack</code> of <code>stack_map_frame</code>
         * is visited.  
         */
        public int stack(int pos, int offset, int num) {
            return typeInfoArray(pos, offset, num, false);
        }

        /**
         * Invoked when an array of <code>verification_type_info</code> is
         * visited.
         *
         * @param num       the number of elements.
         * @param isLocals  true if this array is for <code>locals</code>.
         *                  false if it is for <code>stack</code>.
         */
        public int typeInfoArray(int pos, int offset, int num, boolean isLocals) {
            for (int k = 0; k < num; k++)
                pos = typeInfoArray2(k, pos);

            return pos;
        }

        int typeInfoArray2(int k, int pos) {
            byte tag = info[pos];
            if (tag == OBJECT) {
                int clazz = ByteArray.readU16bit(info, pos + 1);
                objectVariable(pos, clazz);
                pos += 3;
            }
            else if (tag == UNINIT) {
                int offsetOfNew = ByteArray.readU16bit(info, pos + 1);
                uninitialized(pos, offsetOfNew);
                pos += 3;
            }
            else {
                typeInfo(pos, tag);
                pos++;
            }

            return pos;
        }

        /**
         * Invoked when an element of <code>verification_type_info</code>
         * (except <code>Object_variable_info</code> and
         * <code>Uninitialized_variable_info</code>) is visited.
         */
        public void typeInfo(int pos, byte tag) {}

        /**
         * Invoked when an element of type <code>Object_variable_info</code>
         * is visited.
         */
        public void objectVariable(int pos, int clazz) {}

        /**
         * Invoked when an element of type <code>Uninitialized_variable_info</code>
         * is visited.
         */
        public void uninitialized(int pos, int offset) {}
    }

    static class Copier extends Walker {
        byte[] dest;
        ConstPool srcCp, destCp;
        Map<String,String> classnames;

        Copier(StackMap map, ConstPool newCp, Map<String,String> classnames) {
            super(map);
            srcCp = map.getConstPool();
            dest = new byte[info.length];
            destCp = newCp;
            this.classnames = classnames;
        }
        @Override
        public void visit() {
            int num = ByteArray.readU16bit(info, 0);
            ByteArray.write16bit(num, dest, 0);
            super.visit();
        }

        @Override
        public int locals(int pos, int offset, int num) {
            ByteArray.write16bit(offset, dest, pos - 4);
            return super.locals(pos, offset, num);
        }

        @Override
        public int typeInfoArray(int pos, int offset, int num, boolean isLocals) {
            ByteArray.write16bit(num, dest, pos - 2);
            return super.typeInfoArray(pos, offset, num, isLocals);
        }

        @Override
        public void typeInfo(int pos, byte tag) {
            dest[pos] = tag;
        }

        @Override
        public void objectVariable(int pos, int clazz) {
            dest[pos] = OBJECT;
            int newClazz = srcCp.copy(clazz, destCp, classnames);
            ByteArray.write16bit(newClazz, dest, pos + 1);
        }

        @Override
        public void uninitialized(int pos, int offset) {
            dest[pos] = UNINIT;
            ByteArray.write16bit(offset, dest, pos + 1);
        }

        public StackMap getStackMap() {
            return new StackMap(destCp, dest);
        }
    }

    /**
     * Updates this stack map table when a new local variable is inserted
     * for a new parameter.
     *
     * @param index          the index of the added local variable.
     * @param tag            the type tag of that local variable.
     *                       It is available by <code>StackMapTable.typeTagOf(char)</code>.
     * @param classInfo      the index of the <code>CONSTANT_Class_info</code> structure
     *                       in a constant pool table.  This should be zero unless the tag
     *                       is <code>ITEM_Object</code>.
     *
     * @see javassist.CtBehavior#addParameter(javassist.CtClass)
     * @see StackMapTable#typeTagOf(char)
     * @see ConstPool
     */
    public void insertLocal(int index, int tag, int classInfo)
        throws BadBytecode
    {
        byte[] data = new InsertLocal(this, index, tag, classInfo).doit();
        this.set(data);
    }

    static class SimpleCopy extends Walker {
        Writer writer;

        SimpleCopy(StackMap map) {
            super(map);
            writer = new Writer();
        }

        byte[] doit() {
            visit();
            return writer.toByteArray();
        }

        @Override
        public void visit() {
            int num = ByteArray.readU16bit(info, 0);
            writer.write16bit(num);
            super.visit();
        }

        @Override
        public int locals(int pos, int offset, int num) {
            writer.write16bit(offset);
            return super.locals(pos, offset, num);
        }

        @Override
        public int typeInfoArray(int pos, int offset, int num, boolean isLocals) {
            writer.write16bit(num);
            return super.typeInfoArray(pos, offset, num, isLocals);
        }

        @Override
        public void typeInfo(int pos, byte tag) {
            writer.writeVerifyTypeInfo(tag, 0);
        }

        @Override
        public void objectVariable(int pos, int clazz) {
            writer.writeVerifyTypeInfo(OBJECT, clazz);
        }

        @Override
        public void uninitialized(int pos, int offset) {
            writer.writeVerifyTypeInfo(UNINIT, offset);
        }
    }

    static class InsertLocal extends SimpleCopy {
        private int varIndex;
        private int varTag, varData;

        InsertLocal(StackMap map, int varIndex, int varTag, int varData) {
            super(map);
            this.varIndex = varIndex;
            this.varTag = varTag;
            this.varData = varData;
        }

        @Override
        public int typeInfoArray(int pos, int offset, int num, boolean isLocals) {
            if (!isLocals || num < varIndex)
                return super.typeInfoArray(pos, offset, num, isLocals);

            writer.write16bit(num + 1);
            for (int k = 0; k < num; k++) {
                if (k == varIndex)
                    writeVarTypeInfo();

                pos = typeInfoArray2(k, pos);
            }

            if (num == varIndex)
                writeVarTypeInfo();

            return pos;
        }

        private void writeVarTypeInfo() {
            if (varTag == OBJECT)
                writer.writeVerifyTypeInfo(OBJECT, varData);
            else if (varTag == UNINIT)
                writer.writeVerifyTypeInfo(UNINIT, varData);
            else
                writer.writeVerifyTypeInfo(varTag, 0);
        }
    }

    void shiftPc(int where, int gapSize, boolean exclusive)
        throws BadBytecode
    {
        new Shifter(this, where, gapSize, exclusive).visit();
    }

    static class Shifter extends Walker {
        private int where, gap;
        private boolean exclusive;

        public Shifter(StackMap smt, int where, int gap, boolean exclusive) {
            super(smt);
            this.where = where;
            this.gap = gap;
            this.exclusive = exclusive;
        }

        @Override
        public int locals(int pos, int offset, int num) {
            if (exclusive ? where <= offset : where < offset)
                ByteArray.write16bit(offset + gap, info, pos - 4);

            return super.locals(pos, offset, num);
        }

        @Override
        public void uninitialized(int pos, int offset) {
            if (where <= offset)
                ByteArray.write16bit(offset + gap, info, pos + 1);
        }
    }

    /**
     * @see CodeIterator.Switcher#adjustOffsets(int, int)
     */
    void shiftForSwitch(int where, int gapSize) throws BadBytecode {
        new SwitchShifter(this, where, gapSize).visit();
    }

    static class SwitchShifter extends Walker {
        private int where, gap;

        public SwitchShifter(StackMap smt, int where, int gap) {
            super(smt);
            this.where = where;
            this.gap = gap;
        }

        @Override
        public int locals(int pos, int offset, int num) {
            if (where == pos + offset)
                ByteArray.write16bit(offset - gap, info, pos - 4);
            else if (where == pos)
                ByteArray.write16bit(offset + gap, info, pos - 4);

            return super.locals(pos, offset, num);
        }
    }

    /**
     * Undocumented method.  Do not use; internal-use only.
     *
     * <p>This method is for javassist.convert.TransformNew.
     * It is called to update the stack map when
     * the NEW opcode (and the following DUP) is removed. 
     *
     * @param where     the position of the removed NEW opcode.
     */
     public void removeNew(int where) throws CannotCompileException {
         byte[] data = new NewRemover(this, where).doit();
         this.set(data);
    }

    static class NewRemover extends SimpleCopy {
        int posOfNew;

        NewRemover(StackMap map, int where) {
            super(map);
            posOfNew = where;
        }

        @Override
        public int stack(int pos, int offset, int num) {
            return stackTypeInfoArray(pos, offset, num);
        }

        private int stackTypeInfoArray(int pos, int offset, int num) {
            int p = pos;
            int count = 0;
            for (int k = 0; k < num; k++) {
                byte tag = info[p];
                if (tag == OBJECT)
                    p += 3;
                else if (tag == UNINIT) {
                    int offsetOfNew = ByteArray.readU16bit(info, p + 1);
                    if (offsetOfNew == posOfNew)
                        count++;

                    p += 3;
                }
                else
                    p++;
            }

            writer.write16bit(num - count);
            for (int k = 0; k < num; k++) {
                byte tag = info[pos];
                if (tag == OBJECT) {
                    int clazz = ByteArray.readU16bit(info, pos + 1);
                    objectVariable(pos, clazz);
                    pos += 3;
                }
                else if (tag == UNINIT) {
                    int offsetOfNew = ByteArray.readU16bit(info, pos + 1);
                    if (offsetOfNew != posOfNew)
                        uninitialized(pos, offsetOfNew);

                    pos += 3;
                }
                else {
                    typeInfo(pos, tag);
                    pos++;
                }
            }

            return pos;
        }
    }

    /**
     * Prints this stack map.
     */
    public void print(java.io.PrintWriter out) {
        new Printer(this, out).print();
    }

    static class Printer extends Walker {
        private java.io.PrintWriter writer;

        public Printer(StackMap map, java.io.PrintWriter out) {
            super(map);
            writer = out;
        }

        public void print() {
            int num = ByteArray.readU16bit(info, 0);
            writer.println(num + " entries");
            visit();
        }

        @Override
        public int locals(int pos, int offset, int num) {
            writer.println("  * offset " + offset);
            return super.locals(pos, offset, num);
        }
    }

    /**
     * Internal use only.
     */
    public static class Writer {
        // see javassist.bytecode.stackmap.MapMaker

        private ByteArrayOutputStream output;

        /**
         * Constructs a writer.
         */
        public Writer() {
            output = new ByteArrayOutputStream();
        }

        /**
         * Converts the written data into a byte array.
         */
        public byte[] toByteArray() {
            return output.toByteArray();
        }

        /**
         * Converts to a <code>StackMap</code> attribute.
         */
        public StackMap toStackMap(ConstPool cp) {
            return new StackMap(cp, output.toByteArray());
        }

        /**
         * Writes a <code>union verification_type_info</code> value.
         *
         * @param data      <code>cpool_index</code> or <code>offset</code>.
         */
        public void writeVerifyTypeInfo(int tag, int data) {
            output.write(tag);
            if (tag == StackMap.OBJECT || tag == StackMap.UNINIT)
                write16bit(data);
        }

        /**
         * Writes a 16bit value.
         */
        public void write16bit(int value) {
            output.write((value >>> 8) & 0xff);
            output.write(value & 0xff);
        }
    }
}
