package accrue.analysis.callgraph;

import accrue.analysis.interprocanalysis.EdgeIdentifier;
import accrue.analysis.interprocanalysis.Ordered;
import accrue.analysis.preciseExceptions.ThrowableType;

import java.util.Collections;
import java.util.HashSet;
import java.util.Set;

public class PCGAbsVal implements Ordered<PCGAbsVal> {
    private final Set<ThrowableType> throwTypes;
    private final Set<EdgeIdentifier> throwSites;
    private final Set<EdgeIdentifier> returnSites;

    private final boolean canTerminateNormally;
    public static final PCGAbsVal EMPTY =
            new PCGAbsVal(Collections.<ThrowableType> emptySet(),
                          Collections.<EdgeIdentifier> emptySet(),
                          Collections.<EdgeIdentifier> emptySet(),
                          false);

    public PCGAbsVal(Set<ThrowableType> terminationKinds,
            Set<EdgeIdentifier> throwSites, Set<EdgeIdentifier> returnSites,
            boolean canTerminateNormally) {
        this.throwTypes = terminationKinds;
        this.throwSites = throwSites;
        this.returnSites = returnSites;

        this.canTerminateNormally = canTerminateNormally;
    }

    @Override
    public boolean leq(PCGAbsVal o) {
        return this.equals(o);
    }

    @Override
    public PCGAbsVal upperBound(PCGAbsVal o) {
        if (o == null) return this;
        Set<ThrowableType> tt = new HashSet<ThrowableType>(this.throwTypes());
        tt.addAll(o.throwTypes());

        Set<EdgeIdentifier> ts = new HashSet<EdgeIdentifier>(this.throwSites());
        ts.addAll(o.throwSites());

        Set<EdgeIdentifier> rs =
                new HashSet<EdgeIdentifier>(this.returnSites());
        ts.addAll(o.returnSites());

        return new PCGAbsVal(tt, ts, rs, this.canTerminateNormally()
                || o.canTerminateNormally());
    }

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

    public Set<ThrowableType> throwTypes() {
        return throwTypes;
    }

    public Set<EdgeIdentifier> throwSites() {
        return throwSites;
    }

    public Set<EdgeIdentifier> returnSites() {
        return returnSites;
    }

    public boolean canTerminateNormally() {
        return canTerminateNormally;
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + (canTerminateNormally ? 1231 : 1237);
        result =
                prime * result
                        + ((throwTypes == null) ? 0 : throwTypes.hashCode());
        result =
                prime * result
                        + ((throwSites == null) ? 0 : throwSites.hashCode());
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (obj == null) return false;
        if (!(obj instanceof PCGAbsVal)) return false;
        PCGAbsVal other = (PCGAbsVal) obj;
        if (canTerminateNormally != other.canTerminateNormally) return false;
        if (throwTypes == null) {
            if (other.throwTypes != null) return false;
        }
        else if (!throwTypes.equals(other.throwTypes)) return false;
        if (throwSites == null) {
            if (other.throwSites != null) return false;
        }
        else if (!throwSites.equals(other.throwSites)) return false;
        return true;
    }

    @Override
    public PCGAbsVal widen(PCGAbsVal o) {
        return upperBound(o);
    }

}
