/*
 * Copyright (c) 2016, 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.
 *
 * 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 org.graalvm.compiler.replacements.classfile;

import static org.graalvm.compiler.replacements.classfile.Classfile.skipFully;
import static org.graalvm.compiler.replacements.classfile.ClassfileConstant.CONSTANT_Class;

import java.io.DataInputStream;
import java.io.IOException;

import org.graalvm.compiler.debug.GraalError;
import org.graalvm.compiler.replacements.classfile.ClassfileConstant.ClassRef;
import org.graalvm.compiler.replacements.classfile.ClassfileConstant.ExecutableRef;
import org.graalvm.compiler.replacements.classfile.ClassfileConstant.FieldRef;
import org.graalvm.compiler.replacements.classfile.ClassfileConstant.Primitive;
import org.graalvm.compiler.replacements.classfile.ClassfileConstant.Utf8;

import jdk.vm.ci.meta.ConstantPool;
import jdk.vm.ci.meta.JavaConstant;
import jdk.vm.ci.meta.JavaField;
import jdk.vm.ci.meta.JavaMethod;
import jdk.vm.ci.meta.JavaType;
import jdk.vm.ci.meta.ResolvedJavaMethod;
import jdk.vm.ci.meta.Signature;

class ClassfileConstantPool implements ConstantPool, ConstantPoolPatch {

    final ClassfileConstant[] entries;
    final ClassfileBytecodeProvider context;

    public static class Bytecodes {
        public static final int LDC = 18; // 0x12
        public static final int LDC_W = 19; // 0x13
        public static final int LDC2_W = 20; // 0x14
        public static final int GETSTATIC = 178; // 0xB2
        public static final int PUTSTATIC = 179; // 0xB3
        public static final int GETFIELD = 180; // 0xB4
        public static final int PUTFIELD = 181; // 0xB5
        public static final int INVOKEVIRTUAL = 182; // 0xB6
        public static final int INVOKESPECIAL = 183; // 0xB7
        public static final int INVOKESTATIC = 184; // 0xB8
        public static final int INVOKEINTERFACE = 185; // 0xB9
        public static final int INVOKEDYNAMIC = 186; // 0xBA
        public static final int NEW = 187; // 0xBB
        public static final int NEWARRAY = 188; // 0xBC
        public static final int ANEWARRAY = 189; // 0xBD
        public static final int CHECKCAST = 192; // 0xC0
        public static final int INSTANCEOF = 193; // 0xC1
        public static final int MULTIANEWARRAY = 197; // 0xC5
    }

    ClassfileConstantPool(DataInputStream stream, ClassfileBytecodeProvider context) throws IOException {
        this.context = context;
        byte tag;

        int count = stream.readUnsignedShort();
        entries = new ClassfileConstant[count];

        int i = 1;
        while (i < count) {
            entries[i] = readConstant(stream);
            tag = entries[i].tag;

            if ((tag == ClassfileConstant.CONSTANT_Double) || (tag == ClassfileConstant.CONSTANT_Long)) {
                i += 2;
            } else {
                i += 1;
            }
        }
    }

    static final ClassfileConstant readConstant(DataInputStream stream) throws IOException {
        byte tag = stream.readByte();

        switch (tag) {
            case ClassfileConstant.CONSTANT_Class:
                return new ClassfileConstant.ClassRef(stream);
            case ClassfileConstant.CONSTANT_Fieldref:
                return new ClassfileConstant.FieldRef(stream);
            case ClassfileConstant.CONSTANT_Methodref:
                return new ClassfileConstant.MethodRef(stream);
            case ClassfileConstant.CONSTANT_InterfaceMethodref:
                return new ClassfileConstant.InterfaceMethodRef(stream);
            case ClassfileConstant.CONSTANT_String:
                return new ClassfileConstant.StringRef(stream);
            case ClassfileConstant.CONSTANT_Integer:
                return new ClassfileConstant.Primitive(tag, JavaConstant.forInt(stream.readInt()));
            case ClassfileConstant.CONSTANT_Float:
                return new ClassfileConstant.Primitive(tag, JavaConstant.forFloat(stream.readFloat()));
            case ClassfileConstant.CONSTANT_Long:
                return new ClassfileConstant.Primitive(tag, JavaConstant.forLong(stream.readLong()));
            case ClassfileConstant.CONSTANT_Double:
                return new ClassfileConstant.Primitive(tag, JavaConstant.forDouble(stream.readDouble()));
            case ClassfileConstant.CONSTANT_NameAndType:
                return new ClassfileConstant.NameAndType(stream);
            case ClassfileConstant.CONSTANT_Utf8:
                return new ClassfileConstant.Utf8(stream.readUTF());
            case ClassfileConstant.CONSTANT_MethodHandle:
                skipFully(stream, 3); // reference_kind, reference_index
                return new ClassfileConstant.Unsupported(tag, "CONSTANT_MethodHandle_info");
            case ClassfileConstant.CONSTANT_MethodType:
                skipFully(stream, 2); // descriptor_index
                return new ClassfileConstant.Unsupported(tag, "CONSTANT_MethodType_info");
            case ClassfileConstant.CONSTANT_Dynamic:
                skipFully(stream, 4); // bootstrap_method_attr_index, name_and_type_index
                return new ClassfileConstant.Unsupported(tag, "CONSTANT_Dynamic_info");
            case ClassfileConstant.CONSTANT_InvokeDynamic:
                skipFully(stream, 4); // bootstrap_method_attr_index, name_and_type_index
                return new ClassfileConstant.Unsupported(tag, "CONSTANT_InvokeDynamic_info");
            default:
                throw new GraalError("Invalid constant pool tag: " + tag);
        }
    }

    @Override
    public int length() {
        return entries.length;
    }

    <T extends ClassfileConstant> T get(Class<T> c, int index) {
        return c.cast(entries[index]);
    }

    @Override
    public void loadReferencedType(int index, int opcode) {
        if (opcode == Bytecodes.INVOKEDYNAMIC) {
            throw new GraalError("INVOKEDYNAMIC not supported by " + ClassfileBytecodeProvider.class.getSimpleName());
        }
        entries[index].loadReferencedType(this, index, opcode);
    }

    @Override
    public JavaField lookupField(int index, ResolvedJavaMethod method, int opcode) {
        return get(FieldRef.class, index).resolve(this, opcode);
    }

    @Override
    public JavaMethod lookupMethod(int index, int opcode) {
        if (opcode == Bytecodes.INVOKEDYNAMIC) {
            throw new GraalError("INVOKEDYNAMIC not supported by" + ClassfileBytecodeProvider.class.getSimpleName());
        }
        return get(ExecutableRef.class, index).resolve(this, opcode);
    }

    @Override
    public JavaType lookupType(int index, int opcode) {
        return get(ClassRef.class, index).resolve(this);
    }

    @Override
    public JavaType lookupReferencedType(int index, int opcode) {
        switch (opcode) {
            case Bytecodes.CHECKCAST:
            case Bytecodes.INSTANCEOF:
            case Bytecodes.NEW:
            case Bytecodes.ANEWARRAY:
            case Bytecodes.MULTIANEWARRAY:
            case Bytecodes.LDC:
            case Bytecodes.LDC_W:
            case Bytecodes.LDC2_W:
                return get(ClassRef.class, index).resolve(this);
            case Bytecodes.GETSTATIC:
            case Bytecodes.PUTSTATIC:
            case Bytecodes.GETFIELD:
            case Bytecodes.PUTFIELD:
                FieldRef f = get(FieldRef.class, index);
                return get(ClassRef.class, f.classIndex).resolve(this);
            case Bytecodes.INVOKEVIRTUAL:
            case Bytecodes.INVOKESPECIAL:
            case Bytecodes.INVOKESTATIC:
            case Bytecodes.INVOKEINTERFACE:
                ExecutableRef e = get(ExecutableRef.class, index);
                return get(ClassRef.class, e.classIndex).resolve(this);
            default:
                throw GraalError.shouldNotReachHere("Unexpected opcode: " + opcode);
        }
    }

    @Override
    public String lookupUtf8(int index) {
        return ((Utf8) entries[index]).value;
    }

    @Override
    public Signature lookupSignature(int index) {
        throw GraalError.shouldNotReachHere();
    }

    @Override
    public Object lookupConstant(int index) {
        ClassfileConstant c = entries[index];
        if (c instanceof Primitive) {
            Primitive p = (Primitive) c;
            return p.value;
        }
        switch (c.tag) {
            case CONSTANT_Class:
                final int opcode = -1;
                return lookupType(index, opcode);
            case ClassfileConstant.CONSTANT_String:
                return ((ClassfileConstant.StringRef) c).getValue(this);
            default:
                throw new GraalError("Unexpected constant pool tag %s", c.tag);
        }
    }

    @Override
    public JavaConstant lookupAppendix(int index, int opcode) {
        if (opcode == Bytecodes.INVOKEVIRTUAL) {
            return null;
        }
        throw GraalError.shouldNotReachHere();
    }
}