package org.benf.cfr.reader.state;

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.TypeConstants;
import org.benf.cfr.reader.util.collections.MapFactory;
import org.benf.cfr.reader.util.collections.SetFactory;

import java.util.Collection;
import java.util.Map;
import java.util.Set;

public class ClassCache {

    private final Map<String, JavaRefTypeInstance> refClassTypeCache = MapFactory.newMap();
    // We want to avoid generating names which collide with classes.
    // This is a nice simple check.
    private final Set<String> simpleClassNamesSeen = SetFactory.newSet();
    private final Map<String, String> renamedClasses = MapFactory.newMap();

    private final DCCommonState dcCommonState;

    ClassCache(DCCommonState dcCommonState) {
        this.dcCommonState = dcCommonState;
        // TODO:  Not sure I need to do this any more.
        add(TypeConstants.ASSERTION_ERROR.getRawName(), TypeConstants.ASSERTION_ERROR);
        add(TypeConstants.OBJECT.getRawName(), TypeConstants.OBJECT);
        add(TypeConstants.STRING.getRawName(), TypeConstants.STRING);
        add(TypeConstants.ENUM.getRawName(), TypeConstants.ENUM);
    }

    public JavaRefTypeInstance getRefClassFor(String rawClassName) {
        /*
         * If the path (or pseudopath) has been renamed because it's a collision,
         * we need to replace with the deduplicated version - otherwise the file
         * will not match the type.
         */
        String originalRawClassName = ClassNameUtils.convertToPath(rawClassName);
        rawClassName = dcCommonState.getPossiblyRenamedFileFromClassFileSource(originalRawClassName);
        String name = ClassNameUtils.convertFromPath(rawClassName);
        JavaRefTypeInstance typeInstance = refClassTypeCache.get(name);
        String originalName = null;
        if (!rawClassName.equals(originalRawClassName)) {
            originalName = ClassNameUtils.convertFromPath(originalRawClassName);
        }
        if (typeInstance == null) {
            typeInstance = JavaRefTypeInstance.create(name, dcCommonState);
            add(name, originalName, typeInstance);
        }
        return typeInstance;
    }

    private void add(String name, JavaRefTypeInstance typeInstance) {
        add(name, null, typeInstance);
    }

    private void add(String name, String originalName, JavaRefTypeInstance typeInstance) {
        refClassTypeCache.put(name, typeInstance);
        simpleClassNamesSeen.add(typeInstance.getRawShortName());
        if (originalName != null) {
            renamedClasses.put(name, originalName);
        }
    }

    public boolean isClassName(String name) {
        return simpleClassNamesSeen.contains(name);
    }

    public Pair<JavaRefTypeInstance, JavaRefTypeInstance> getRefClassForInnerOuterPair(String rawInnerName, String rawOuterName) {
        String innerName = ClassNameUtils.convertFromPath(rawInnerName);
        String outerName = ClassNameUtils.convertFromPath(rawOuterName);
        JavaRefTypeInstance inner = refClassTypeCache.get(innerName);
        JavaRefTypeInstance outer = refClassTypeCache.get(outerName);
        if (inner != null && outer != null) return Pair.make(inner, outer);
        Pair<JavaRefTypeInstance, JavaRefTypeInstance> pair = JavaRefTypeInstance.createKnownInnerOuter(innerName, outerName, outer, dcCommonState);
        if (inner == null) {
            add(innerName, pair.getFirst());
            inner = pair.getFirst();
        }
        if (outer == null) {
            add(outerName, pair.getSecond());
            outer = pair.getSecond();
        }
        return Pair.make(inner, outer);

    }

    public Collection<JavaRefTypeInstance> getLoadedTypes() {
        return refClassTypeCache.values();
    }

    String getOriginalName(String typeName) {
        return renamedClasses.get(typeName);
    }
}