package accrue.jl5.pointer;

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

import polyglot.ast.CodeNode;
import polyglot.ast.ProcedureDecl;
import polyglot.ext.jl5.ast.EnumConstantDecl;
import polyglot.ext.jl5.types.EnumInstance;
import polyglot.frontend.ExtensionInfo;
import polyglot.types.ClassType;
import polyglot.types.CodeInstance;
import polyglot.types.FieldInstance;
import polyglot.types.Flags;
import polyglot.types.InitializerInstance;
import polyglot.types.MemberInstance;
import polyglot.types.Type;
import polyglot.types.TypeSystem;
import polyglot.util.InternalCompilerError;
import accrue.analysis.ext.AccrueExt_c;
import accrue.analysis.pointer.ConstructorContext;
import accrue.analysis.pointer.ReferenceVariable;
import accrue.analysis.pointer.StmtAllocToLocal;
import accrue.analysis.pointer.StmtRegistrar;
import accrue.jl5.ext.ExtEnumConstantDecl;

public class JL5StmtRegistrar extends StmtRegistrar {

    private Map<FieldInstance, ReferenceVariable> enumConstantDecls =
            new LinkedHashMap<FieldInstance, ReferenceVariable>();

    private Map<EnumConstantDecl, CodeInfo> enumConstantDeclCodeInfos =
            new LinkedHashMap<EnumConstantDecl, CodeInfo>();

    public JL5StmtRegistrar(ExtensionInfo extInfo) {
        super(extInfo);
    }

    public ReferenceVariable nodeForEnumConstantDecl(FieldInstance fi) {
        if (!fi.flags().isStatic()) {
            throw new InternalCompilerError("Non static field");
        }
        if (!(fi instanceof EnumInstance)) {
            throw new InternalCompilerError("Non enum constant");
        }
        ReferenceVariable ln = enumConstantDecls.get(fi);
        if (ln == null) {
            ln =
                    freshReferenceVariable("enumConstDecl-"
                                                   + fi.container()
                                                       .toClass()
                                                       .name() + "."
                                                   + fi.name(),
                                           true,
                                           fi.type(),
                                           fi.position()); // is a static local node
            enumConstantDecls.put(fi, ln);
        }
        return ln;
    }

    public void registerEnumConstantDecl(EnumConstantDecl ecd,
            CodeInfo codeInfo, ConstructorContext constructorContext) {
        ClassType container =
                ((MemberInstance) codeInfo.codeInstance).container().toClass();
        ExtEnumConstantDecl ext = (ExtEnumConstantDecl) AccrueExt_c.ext(ecd);
        ext.setAllocNode(freshAllocNode(ecd.type(),
                                        container,
                                        true,
                                        ecd.toString(),
                                        ecd.position()),
                         constructorContext);
        this.addStmt(new StmtAllocToLocal(nodeForEnumConstantDecl(ecd.enumInstance()),
                                          ext.getAllocNode(constructorContext),
                                          ext.node().position(),
                                          codeInfo.codeInstance));
    }

    @Override
    public Collection<CodeInstance> getInitialCodeInstances() {
        Collection<CodeInstance> cis = super.getInitialCodeInstances();
        for (ProcedureDecl pd : this.getStartProcs()) {
            cis.add(pd.procedureInstance());
        }
        for (CodeNode init : this.getStaticInitializers()) {
            cis.add(init.codeInstance());
        }
        for (EnumConstantDecl ecd : this.getEnumConstantDecls()) {
            cis.add(this.getCodeInfo(ecd).codeInstance);
        }
        return cis;
    }

    public CodeInfo getCodeInfo(EnumConstantDecl ecd) {
        return this.enumConstantDeclCodeInfos.get(ecd);
    }

    public Set<EnumConstantDecl> getEnumConstantDecls() {
        return this.enumConstantDeclCodeInfos.keySet();
    }

    /**
     * Record the existence of an EnumConstantDecl
     * @param ecd
     */
    public void recordEnumConstantDecl(EnumConstantDecl ecd) {
        TypeSystem ts = ecd.type().typeSystem();

        // Create an initializer instance for the EnumConstantDecl,
        // since the JL5 type system doesn't provide one, but we need
        // one in order to process the pointer statements correctly. 
        InitializerInstance ii =
                ts.initializerInstance(ecd.position(),
                                       ecd.memberInstance()
                                          .container()
                                          .toClass(),
                                       Flags.STATIC);
        Map<Type, ReferenceVariable> exceptionRets =
                new LinkedHashMap<Type, ReferenceVariable>();

        exceptionRets.put(ts.RuntimeException(),
                          freshReferenceVariable("enumConstantDecl-init-throw-RuntimeException",
                                                 false,
                                                 ts.RuntimeException(),
                                                 ecd.position()));
        exceptionRets.put(ts.Error(),
                          freshReferenceVariable("enumConstantDecl-init-throw-Error",
                                                 false,
                                                 ts.Error(),
                                                 ecd.position()));
        CodeInfo codeInfo =
                new CodeInfo(ii,
                             null,
                             Collections.<ReferenceVariable> emptyList(),
                             null,
                             exceptionRets);
        this.enumConstantDeclCodeInfos.put(ecd, codeInfo);
    }
}
