package accrue.analysis.interprocanalysis;

import polyglot.ast.JLang;
import polyglot.types.ConstructorInstance;
import polyglot.util.InternalCompilerError;
import accrue.analysis.pointer.CContext;
import accrue.analysis.pointer.ConstructorContext;

/**
 * A context in which to analyze code. In a context-sensitive analysis, 
 * the same piece of code may be analyzed multiple times, each time with a different 
 * AnalysisContext.
 * 
 *  An AnalysisContext is a pair of a CContext and a ConstructorContext. 
 *  The ConstructorContext is used to distinguish copies of instance initializers,
 *  which must be analyzed separately for each constructor call to super. That is,
 *  for most AnalysisContexts, the constructorContext will be 
 *  ConstructorContext.NORMAL, meaning that the
 *  CContext fully determines the context for the analysis of code. However, when
 *  an instance initializer is being analyzed, the AnalysisContext will
 *  contain a reference to a constructor instance of the same class as the
 *  container of the instance initializer, in order to distinguish the 
 *  different analyses of the instance initializer within the same CContext.  
 *  
 */
public class AnalysisContext implements Cloneable {
    private CContext cContext;
    /**
     * The ConstructorContext with which to access LocalNodes and AllocNodes.
     * constructorContexts are used to distinguish copies of 
     * instance initializers, since one copy of each instance initializer
     * must be analyzed for each constructor call to super. Thus, the 
     * ConstructorContext, if it is not equal to ConstructorContext.NORMAL,
     * will contain a reference to the constructor instance of a constructor
     * of the same class as the container of the instance initializer being
     * analyzed.
     */
    private ConstructorContext constructorContext;

    private final JLang lang;

    public AnalysisContext(CContext cContext,
            ConstructorContext constructorContext, JLang lang) {
        super();
        this.cContext = cContext;
        this.constructorContext = constructorContext;
        this.lang = lang;
    }

    public AnalysisContext constructorContext(ConstructorInstance ci) {
        AnalysisContext n = this.copy();
        n.constructorContext = new ConstructorContext(ci);
        return n;
    }

    protected AnalysisContext copy() {
        try {
            return (AnalysisContext) this.clone();
        }
        catch (CloneNotSupportedException e) {
            throw new InternalCompilerError("Clone weirdness");
        }
    }

    public CContext getCContext() {
        return cContext;
    }

    public ConstructorContext getConstructorContext() {
        return constructorContext;
    }

    @Override
    public int hashCode() {
        return constructorContext.hashCode() ^ cContext.hashCode();
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (obj == null) return false;
        if (!(obj instanceof AnalysisContext)) return false;
        AnalysisContext other = (AnalysisContext) obj;
        if (constructorContext == null) {
            if (other.constructorContext != null) return false;
        }
        else if (!constructorContext.equals(other.constructorContext))
            return false;
        if (cContext == null) {
            if (other.cContext != null) return false;
        }
        else if (!cContext.equals(other.cContext)) return false;
        return true;
    }

    @Override
    public String toString() {
        return "AnalysisContext [constructorContext=" + constructorContext
                + ", codeContext=" + cContext + "]";
    }

    public AnalysisContext convert(Class<?> toClass) {
        if (this.getClass().equals(toClass)) return this;
        throw new InternalCompilerError("Do not know how to convert a "
                + this.getClass() + " to a " + toClass);
    }

    public JLang lang() {
        return this.lang;
    }
}
