package accrue.analysis.interprocvarcontext;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import polyglot.types.ConstructorInstance;
import polyglot.types.MethodInstance;
import polyglot.types.ProcedureInstance;
import polyglot.types.ReferenceType;
import polyglot.types.Type;
import polyglot.util.InternalCompilerError;

/**
 * Provides a way to store and look up LibrarySignatures for procedure instances.
 */
public class SignatureRepository<T> {
    private final Map<String, T> m = new HashMap<String, T>();

    public void addSignature(String fullClassName, String methodName,
            List<String> argTypes, T sig) {
        if (m.put(mangleMethodName(fullClassName, methodName, argTypes), sig) != null) {
            throw new InternalCompilerError("Multiple signatures for "
                    + mangleMethodName(fullClassName, methodName, argTypes));
        }
    }

    public void addSignatureForConstructor(String fullClassName,
            List<String> argTypes, T sig) {
        if (m.put(mangleConstructor(fullClassName, argTypes), sig) != null) {
            throw new InternalCompilerError("Multiple signatures for "
                    + mangleConstructor(fullClassName, argTypes));
        }
    }

    public T lookup(ProcedureInstance pi) {
        if (pi instanceof MethodInstance) {
            MethodInstance mi = (MethodInstance) pi;
            String containerName = containerName(mi.container());
            String s =
                    mangleMethodName(containerName,
                                     mi.name(),
                                     typeListToStrings(pi.formalTypes()));
            if (m.containsKey(s)) return m.get(s);
            return m.get(mangleMethodName(containerName, mi.name(), null));
        }
        else if (pi instanceof ConstructorInstance) {
            ConstructorInstance ci = (ConstructorInstance) pi;
            String containerName = containerName(ci.container());
            String s =
                    mangleConstructor(containerName,
                                      typeListToStrings(pi.formalTypes()));
            if (m.containsKey(s)) return m.get(s);
            return m.get(mangleConstructor(containerName, null));

        }
        else {
            throw new InternalCompilerError("Don't support " + pi + " yet");
        }
    }

    private String containerName(ReferenceType container) {
        if (container.isClass()) {
            return container.toClass().fullName();
        }
        else if (container.isArray()) {
            return container.toArray().toString();
        }
        else {
            throw new InternalCompilerError("Currently don't handle containers "
                    + container);
        }
    }

    private List<String> typeListToStrings(List<? extends Type> formalTypes) {
        List<String> ls = new ArrayList<String>(formalTypes.size());
        for (Type t : formalTypes) {
            String s = t.toString();
            if (t.isClass()) s = t.toClass().fullName();
            ls.add(s);
        }
        return ls;
    }

    private String mangleMethodName(String fullClassName, String methodName,
            List<String> argTypes) {
        StringBuffer sb = new StringBuffer();
        sb.append(fullClassName);
        sb.append('#');
        sb.append(methodName);
        sb.append('#');
        if (argTypes == null) {
            sb.append("*");
        }
        else {
            for (String a : argTypes) {
                sb.append('@');
                sb.append(a);
            }
        }
        return sb.toString();
    }

    private String mangleConstructor(String fullClassName, List<String> argTypes) {
        StringBuffer sb = new StringBuffer();
        sb.append(fullClassName);
        sb.append("&");
        if (argTypes == null) {
            sb.append("*");
        }
        else {
            for (String a : argTypes) {
                sb.append('@');
                sb.append(a);
            }
        }
        return sb.toString();
    }

    @Override
    public String toString() {
        return m.toString();
    }
}
