package accrue.analysis.interprocanalysis;

import java.util.Set;

import polyglot.frontend.ExtensionInfo;
import polyglot.types.ClassType;

public interface EdgeIdentifier {
    EdgeType getKey();

    NodeIdentifier getTarget();

    public abstract static class EdgeType {
    	protected final boolean isIntra;
    	public EdgeType(boolean isIntra) {
    		this.isIntra = isIntra;
    	}
    	/**
    	 * Returns true if this edge is a result from an intra-procedural analysis.
    	 * NB: 
    	 * isIntra => source and target are in same procedure
    	 * !isIntra does NOT imply that source and target are in different procedures.
    	 */
        public boolean isIntra() {
        	return isIntra;
        }
    }

    public static final EdgeType EDGE_CALL = new EdgeType(false) {
        @Override
        public boolean equals(Object o) {
            return this == o;
        }

        @Override
        public int hashCode() {
            return 276232;
        }

        @Override
        public String toString() {
            return "CALL";
        }
    };

    public static final EdgeType EDGE_RETURN = new EdgeType(false) {
        @Override
        public boolean equals(Object o) {
            return this == o;
        }

        @Override
        public int hashCode() {
            return 230429;
        }

        @Override
        public String toString() {
            return "RETURN";
        }
    };

    public static final EdgeType EDGE_TRUE = new EdgeType(true) {
        @Override
        public boolean equals(Object o) {
            return this == o;
        }

        @Override
        public int hashCode() {
            return 467841;
        }

        @Override
        public String toString() {
            return "TRUE";
        }
    };

    public static final EdgeType EDGE_FALSE = new EdgeType(true) {
        @Override
        public boolean equals(Object o) {
            return this == o;
        }

        @Override
        public int hashCode() {
            return 148764;
        }

        @Override
        public String toString() {
            return "FALSE";
        }
    };

    public static final EdgeType EDGE_CONTROL = new EdgeType(true) {
        @Override
        public boolean equals(Object o) {
            return this == o;
        }

        @Override
        public int hashCode() {
            return 276232;
        }

        @Override
        public String toString() {
            return "CONTROL";
        }
    };

    public static final EdgeType EDGE_INIT = new EdgeType(false) {
        @Override
        public boolean equals(Object o) {
            return this == o;
        }

        @Override
        public int hashCode() {
            return 1235563;
        }

        @Override
        public String toString() {
            return "INIT";
        }
    };

    public static class ExceptionEdge extends EdgeType {
        protected final ClassType type;

        public ExceptionEdge(ClassType type) {
        	super(false);
            this.type = type;
        }

        public ClassType type() {
            return type;
        }
        
        public String toString() {
            return type.toString();
        }
    }

    /**
     * A LocalUseKey is used for data dependency edges between defs and uses of
     * Local and Formal locations
     */
    public static final class LocalUseKey extends EdgeType {
        protected final String used;

        public LocalUseKey(String used) {
        	this(used, false);
        }
        /**
         * @param used The local or formal used by this data dependency.
         */
        public LocalUseKey(String used, boolean isIntra) {
        	super(isIntra);
            this.used = used;
        }

        @Override
        public boolean equals(Object o) {
            if (o instanceof LocalUseKey) {
            	LocalUseKey luk = (LocalUseKey) o;
                return luk.used.equals(used) && luk.isIntra == isIntra;
            }
            return false;
        }

        @Override
        public int hashCode() {
            return used.hashCode() ^ (isIntra ? 0 : 1);
        }

        @Override
        public String toString() {
            return used;
        }
    };

    /**
     * A HeapUseKey is used for data dependency edges between defs and uses of
     * heap locations
     */
	// TODO: we don't really need to track the heap locations except for
	// dynamically partitioned objects, but we do need to
	// differentiate between edges connecting the same statements.
    public static class HeapUseKey extends EdgeType {
        protected final Set<AbstractLocation> used;
        protected final int hashCode;

        /**
         * @param used The set of objects that may be used by this data dependency.
         * @param isIntra Whether this edge is the result of an intra-procedural analysis.
         */
        public HeapUseKey(Set<AbstractLocation> used, boolean isIntra) {
        	super(isIntra);
            this.used = used;
            this.hashCode = used.hashCode() ^ (isIntra ? 0 : 1);
        }

        /**
         * @param used The set of objects that may be used by this data dependency.
         */
        public HeapUseKey(Set<AbstractLocation> used) {
        	this(used, false);
        }

        public Set<AbstractLocation> used() {
            return used;
        }

        @Override
        public boolean equals(Object o) {
            if (o instanceof HeapUseKey) {
            	HeapUseKey huk = (HeapUseKey) o;
                return huk.used.equals(used) && huk.isIntra == isIntra;
            }
            return false;
        }

        @Override
        public int hashCode() {
            return hashCode;
        }

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