package accrue.analysis.pointer;

import java.util.concurrent.ConcurrentHashMap;

import polyglot.types.FieldInstance;
import polyglot.types.Type;
import polyglot.util.InternalCompilerError;

/**
 * Represents a field of an abstract object for pointer analysis.
 * 
 *
 */
public class ObjectField extends PointsToGraphNode {
    public final HContext o;
    public final FieldInstance fi;
    public final String fieldName;

    public static ObjectField create(HContext o, FieldInstance fi) {
        ObjectField n = new ObjectField(o, fi);
        return memoize(n);
    }

    public static ObjectField create(HContext o, String fieldName,
            Type expectedType) {
        ObjectField n = new ObjectField(o, fieldName, expectedType);
        return memoize(n);
    }

    private final static ConcurrentHashMap<PTGNOFWrapper, ObjectField> memo =
            new ConcurrentHashMap<PTGNOFWrapper, ObjectField>();

    private static ObjectField memoize(ObjectField n) {
        PTGNOFWrapper w = new PTGNOFWrapper(n);
        ObjectField m = memo.get(w);
        if (m == null) {
            m = memo.putIfAbsent(w, n);
            if (m == null) {
                m = n;
            }
        }
        return m;
    }

    private ObjectField(HContext o, FieldInstance fi) {
        this.o = o;
        this.fi = fi;
        this.fieldName = fi.name();

        if (o == null || fi == null) throw new IllegalArgumentException();
        if (fi.flags().isStatic()) {
            throw new InternalCompilerError("static fields should just be locations!");
        }
        if (!o.type().isSubtype(fi.container())) {
            throw new InternalCompilerError("Malformed Graph Node? Trying to have "
                    + fi + " on a " + o.type());
        }
        hashCode = computeHashCode();
    }

    private ObjectField(HContext o, String fieldName, Type expectedType) {
        this.o = o;
        this.fi = null;
        this.fieldName = fieldName;
        if (o == null || fieldName == null || expectedType == null)
            throw new IllegalArgumentException(o + " " + fieldName + " "
                    + expectedType);
        if (o.type().isClass()
                && o.type().toClass().fieldNamed(fieldName) != null) {
            throw new InternalCompilerError("Trying to use field " + fieldName
                    + " on type " + o.type()
                    + ". Use other constructor instead.");
        }
        hashCode = computeHashCode();
    }

    @Override
    public String toString() {
        return "<" + o + ",\n " + (fi == null ? fieldName : fi.toString())
                + ">";
    }

    private static class PTGNOFWrapper {
        PTGNOFWrapper(ObjectField n) {
            this.n = n;
        }

        final ObjectField n;

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

        @Override
        public boolean equals(Object o) {
            return n.equalsSemantic(((PTGNOFWrapper) o).n);
        }
    }

    public boolean equalsSemantic(ObjectField that) {
        if (this == that) return true;
        return this.o.equals(that.o)
                && (this.fi == null ? (that.fi == null && this.fieldName.equals(that.fieldName))
                        : this.fi.equals(that.fi));
    }

    @Override
    public boolean equals(Object o) {
        return this == o;
    }

    private final int hashCode;

    private int computeHashCode() {
        return o.hashCode()
                ^ (fi != null ? fi.hashCode() : fieldName.hashCode());
    }

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

    @Override
    public Type expectedType() {
        return fi == null ? null : fi.type();
    }
}
