/*
 * 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.DataInputStream;
import java.io.IOException;
import java.util.Map;

/**
 * <code>InnerClasses_attribute</code>.
 */
public class InnerClassesAttribute extends AttributeInfo {
    /**
     * The name of this attribute <code>"InnerClasses"</code>.
     */
    public static final String tag = "InnerClasses";

    InnerClassesAttribute(ConstPool cp, int n, DataInputStream in)
        throws IOException
    {
        super(cp, n, in);
    }

    private InnerClassesAttribute(ConstPool cp, byte[] info) {
        super(cp, tag, info);
    }

    /**
     * Constructs an empty InnerClasses attribute.
     *
     * @see #append(String, String, String, int)
     */
    public InnerClassesAttribute(ConstPool cp) {
        super(cp, tag, new byte[2]);
        ByteArray.write16bit(0, get(), 0);
    }

    /**
     * Returns <code>number_of_classes</code>.
     */
    public int tableLength() { return ByteArray.readU16bit(get(), 0); }

    /**
     * Returns <code>classes[nth].inner_class_info_index</code>.
     */
    public int innerClassIndex(int nth) {
        return ByteArray.readU16bit(get(), nth * 8 + 2);
    }

    /**
     * Returns the class name indicated
     * by <code>classes[nth].inner_class_info_index</code>.
     * The class name is fully-qualified and separated by dot.
     *
     * @return null or the class name.
     * @see ConstPool#getClassInfo(int)
     */
    public String innerClass(int nth) {
        int i = innerClassIndex(nth);
        if (i == 0)
            return null;
        return constPool.getClassInfo(i);
    }

    /**
     * Sets <code>classes[nth].inner_class_info_index</code> to
     * the given index.
     */
    public void setInnerClassIndex(int nth, int index) {
        ByteArray.write16bit(index, get(), nth * 8 + 2);
    }

    /**
     * Returns <code>classes[nth].outer_class_info_index</code>.
     */
    public int outerClassIndex(int nth) {
        return ByteArray.readU16bit(get(), nth * 8 + 4);
    }

    /**
     * Returns the class name indicated
     * by <code>classes[nth].outer_class_info_index</code>.
     *
     * @return null or the class name.
     */
    public String outerClass(int nth) {
        int i = outerClassIndex(nth);
        if (i == 0)
            return null;
        return constPool.getClassInfo(i);
    }

    /**
     * Sets <code>classes[nth].outer_class_info_index</code> to
     * the given index.
     */
    public void setOuterClassIndex(int nth, int index) {
        ByteArray.write16bit(index, get(), nth * 8 + 4);
    }

    /**
     * Returns <code>classes[nth].inner_name_index</code>.
     */
    public int innerNameIndex(int nth) {
        return ByteArray.readU16bit(get(), nth * 8 + 6);
    }

    /**
     * Returns the simple class name indicated
     * by <code>classes[nth].inner_name_index</code>.
     *
     * @return null or the class name.
     */
    public String innerName(int nth) {
        int i = innerNameIndex(nth);
        if (i == 0)
            return null;
        return constPool.getUtf8Info(i);
    }

    /**
     * Sets <code>classes[nth].inner_name_index</code> to
     * the given index.
     */
    public void setInnerNameIndex(int nth, int index) {
        ByteArray.write16bit(index, get(), nth * 8 + 6);
    }

    /**
     * Returns <code>classes[nth].inner_class_access_flags</code>.
     */
    public int accessFlags(int nth) {
        return ByteArray.readU16bit(get(), nth * 8 + 8);
    }

    /**
     * Sets <code>classes[nth].inner_class_access_flags</code> to
     * the given index.
     */
    public void setAccessFlags(int nth, int flags) {
        ByteArray.write16bit(flags, get(), nth * 8 + 8);
    }

    /**
     * Finds the entry for the given inner class.
     *
     * @param name      the fully-qualified class name separated by dot and $.
     * @return the index or -1 if not found.
     * @since 3.22
     */
    public int find(String name) {
        int n = tableLength();
        for (int i = 0; i < n; i++)
            if (name.equals(innerClass(i)))
                return i;

        return -1;
    }

    /**
     * Appends a new entry.
     *
     * @param inner     <code>inner_class_info_index</code>
     * @param outer     <code>outer_class_info_index</code>
     * @param name      <code>inner_name_index</code>
     * @param flags     <code>inner_class_access_flags</code>
     */
    public void append(String inner, String outer, String name, int flags) {
        int i = constPool.addClassInfo(inner);
        int o = constPool.addClassInfo(outer);
        int n = constPool.addUtf8Info(name);
        append(i, o, n, flags);
    }

    /**
     * Appends a new entry.
     *
     * @param inner     <code>inner_class_info_index</code>
     * @param outer     <code>outer_class_info_index</code>
     * @param name      <code>inner_name_index</code>
     * @param flags     <code>inner_class_access_flags</code>
     */
    public void append(int inner, int outer, int name, int flags) {
        byte[] data = get();
        int len = data.length;
        byte[] newData = new byte[len + 8];
        for (int i = 2; i < len; ++i)
            newData[i] = data[i];

        int n = ByteArray.readU16bit(data, 0);
        ByteArray.write16bit(n + 1, newData, 0);

        ByteArray.write16bit(inner, newData, len);
        ByteArray.write16bit(outer, newData, len + 2);
        ByteArray.write16bit(name, newData, len + 4);
        ByteArray.write16bit(flags, newData, len + 6);

        set(newData);
    }

    /**
     * Removes the {@code nth} entry.  It does not eliminate
     * constant pool items that the removed entry refers to.
     * {@link ClassFile#compact()} should be executed to remove
     * these unnecessary items. 
     *
     * @param nth       0, 1, 2, ...
     * @return  the number of items after the removal.
     * @see ClassFile#compact()
     */
    public int remove(int nth) {
        byte[] data = get();
        int len = data.length;
        if (len < 10)
            return 0;

        int n = ByteArray.readU16bit(data, 0);
        int nthPos = 2 + nth * 8;
        if (n <= nth)
            return n;

        byte[] newData = new byte[len - 8];
        ByteArray.write16bit(n - 1, newData, 0);
        int i = 2, j = 2;
        while (i < len)
            if (i == nthPos)
                i += 8;
            else
                newData[j++] = data[i++];

        set(newData);
        return n - 1;
    }

    /**
     * Makes a copy.  Class names are replaced according to the
     * given <code>Map</code> object.
     *
     * @param newCp     the constant pool table used by the new copy.
     * @param classnames        pairs of replaced and substituted
     *                          class names.
     */
    @Override
    public AttributeInfo copy(ConstPool newCp, Map<String,String> classnames) {
        byte[] src = get();
        byte[] dest = new byte[src.length];
        ConstPool cp = getConstPool();
        InnerClassesAttribute attr = new InnerClassesAttribute(newCp, dest);
        int n = ByteArray.readU16bit(src, 0);
        ByteArray.write16bit(n, dest, 0);
        int j = 2;
        for (int i = 0; i < n; ++i) {
            int innerClass = ByteArray.readU16bit(src, j);
            int outerClass = ByteArray.readU16bit(src, j + 2);
            int innerName = ByteArray.readU16bit(src, j + 4);
            int innerAccess = ByteArray.readU16bit(src, j + 6);

            if (innerClass != 0)
                innerClass = cp.copy(innerClass, newCp, classnames);

            ByteArray.write16bit(innerClass, dest, j);

            if (outerClass != 0)
                outerClass = cp.copy(outerClass, newCp, classnames);

            ByteArray.write16bit(outerClass, dest, j + 2);

            if (innerName != 0)
                innerName = cp.copy(innerName, newCp, classnames);

            ByteArray.write16bit(innerName, dest, j + 4);
            ByteArray.write16bit(innerAccess, dest, j + 6);
            j += 8;
        }

        return attr;
    }
}
