package org.benf.cfr.reader.entities.constantpool;

import org.benf.cfr.reader.bytecode.analysis.parse.utils.Pair;
import org.benf.cfr.reader.bytecode.analysis.types.ClassNameUtils;
import org.benf.cfr.reader.bytecode.analysis.types.JavaRefTypeInstance;
import org.benf.cfr.reader.bytecode.analysis.types.JavaTypeInstance;
import org.benf.cfr.reader.bytecode.analysis.types.StackType;
import org.benf.cfr.reader.entities.AbstractConstantPoolEntry;
import org.benf.cfr.reader.util.bytestream.ByteData;
import org.benf.cfr.reader.util.output.Dumper;

public class ConstantPoolEntryClass extends AbstractConstantPoolEntry implements ConstantPoolEntryLiteral {
    private static final long OFFSET_OF_NAME_INDEX = 1;

    final int nameIndex;
    transient JavaTypeInstance javaTypeInstance = null;

    public ConstantPoolEntryClass(ConstantPool cp, ByteData data) {
        super(cp);
        this.nameIndex = data.getU2At(OFFSET_OF_NAME_INDEX);
    }

    @Override
    public long getRawByteLength() {
        return 3;
    }

    @Override
    public String toString() {
        return "CONSTANT_Class " + nameIndex;
    }

    public String getTextPath() {
        return ClassNameUtils.convertFromPath(getClassNameString(nameIndex)) + ".class";
    }

    public String getFilePath() {
        return getClassNameString(nameIndex) + ".class";
    }

    private String getClassNameString(int index) {
        return getCp().getUTF8Entry(index).getValue();
    }

    @Override
    public void dump(Dumper d) {
        d.print("Class " + getClassNameString(nameIndex));
    }

    private JavaTypeInstance convertFromString(String rawType) {
        if (rawType.startsWith("[")) {
            return ConstantPoolUtils.decodeTypeTok(rawType, getCp());
        } else {
            return getCp().getClassCache().getRefClassFor(ClassNameUtils.convertFromPath(rawType));
        }
    }

    public JavaTypeInstance getTypeInstance() {
        if (javaTypeInstance == null) {
            String rawType = getClassNameString(nameIndex);
            javaTypeInstance = convertFromString(rawType);
        }
        return javaTypeInstance;
    }

    public JavaTypeInstance getTypeInstanceKnownOuter(ConstantPoolEntryClass outer) {
        if (javaTypeInstance != null) {
            return javaTypeInstance;
        }
        String thisInnerType = getClassNameString(nameIndex);
        String thisOuterType = getClassNameString(outer.nameIndex);
        Pair<JavaRefTypeInstance, JavaRefTypeInstance> pair = getCp().getClassCache().getRefClassForInnerOuterPair(thisInnerType, thisOuterType);
        javaTypeInstance = pair.getFirst();
        return javaTypeInstance;
    }

    public JavaTypeInstance getTypeInstanceKnownInner(ConstantPoolEntryClass inner) {
        if (javaTypeInstance != null) {
            return javaTypeInstance;
        }
        String thisInnerType = getClassNameString(inner.nameIndex);
        String thisOuterType = getClassNameString(nameIndex);
        Pair<JavaRefTypeInstance, JavaRefTypeInstance> pair = getCp().getClassCache().getRefClassForInnerOuterPair(thisInnerType, thisOuterType);
        javaTypeInstance = pair.getSecond();
        return javaTypeInstance;
    }

    @Override
    public StackType getStackType() {
        return StackType.REF;
    }
}