package org.eclipse.imp.pdb.facts.impl.reference;

import java.util.Map;

import org.eclipse.imp.pdb.facts.IConstructor;
import org.eclipse.imp.pdb.facts.IValue;
import org.eclipse.imp.pdb.facts.exceptions.FactTypeUseException;
import org.eclipse.imp.pdb.facts.exceptions.UnexpectedAnnotationTypeException;
import org.eclipse.imp.pdb.facts.exceptions.UnexpectedChildTypeException;
import org.eclipse.imp.pdb.facts.type.Type;
import org.eclipse.imp.pdb.facts.type.TypeStore;
import org.eclipse.imp.pdb.facts.visitors.IValueVisitor;
import org.eclipse.imp.pdb.facts.visitors.VisitorException;

/**
 * Implementation of a typed tree node with access to children via labels
 */
public class Constructor extends Node implements IConstructor {
	/*package*/ Constructor(Type type, IValue[] children) {
		super(type.getName(), type, children);
		
	}
	
	/*package*/ Constructor(Type type) {
		this(type, new IValue[0]);
	}

	private Constructor(Constructor other, int childIndex, IValue newChild) {
		super(other, childIndex, newChild);
	}
	
	private Constructor(Constructor constructor, String label, IValue value) {
		super(constructor, label, value);
	}

	/*package*/ Constructor(Constructor constructor, Map<String, IValue> annotations) {
		super(constructor, annotations);
	}

	private Constructor(Constructor constructor) {
		super(constructor);
	}

	private Constructor(Constructor constructor, String key) {
		super(constructor, key);
	}

	@Override
	public Type getType() {
		return fType.getAbstractDataType();
	}
	
	public Type getConstructorType() {
		return fType;
	}

	public IValue get(String label) {
		return super.get(fType.getFieldIndex(label));
	}

	public Type getChildrenTypes() {
		return fType.getFieldTypes();
	}

	@Override
	public IConstructor set(int i, IValue newChild) throws IndexOutOfBoundsException {
		checkChildType(i, newChild);
		return new Constructor(this, i, newChild);
	}

	
	public IConstructor set(String label, IValue newChild) throws FactTypeUseException {
		int childIndex = fType.getFieldIndex(label);
		checkChildType(childIndex, newChild);
		return new Constructor(this, childIndex, newChild);
	}
	
	private void checkChildType(int i, IValue newChild) {
		Type type = newChild.getType();
		Type expectedType = getConstructorType().getFieldType(i);
		if (!type.isSubtypeOf(expectedType)) {
			throw new UnexpectedChildTypeException(expectedType, type);
		}
	}
	
	@Override
	public boolean equals(Object obj) {
		if(this == obj) {
			return true;
		}
		else if(obj == null) {
			return false;
		}
		else if (getClass() == obj.getClass()) {
			Constructor other = (Constructor) obj;
			return fType.comparable(other.fType) && super.equals(obj);
		}
		return false;
	}

	@Override
	public int hashCode() {
		 return 17 + ~super.hashCode();
	}
	
	@Override
	public <T, E extends Throwable> T accept(IValueVisitor<T,E> v) throws E {
		return v.visitConstructor(this);
	}
	
	public boolean declaresAnnotation(TypeStore store, String label) {
		return store.getAnnotationType(getType(), label) != null;
	}
	
	@Override
	public IConstructor setAnnotation(String label, IValue value) {
		IValue previous = getAnnotation(label);
		
		if (previous != null) {
			Type expected = previous.getType();
	
			if (!expected.comparable(value.getType())) {
				throw new UnexpectedAnnotationTypeException(expected, value.getType());
			}
		}
	
		return new Constructor(this, label, value);
	}
	
	@Override
	public IConstructor joinAnnotations(Map<String, IValue> annotations) {
		return new Constructor(this, annotations);
	}
	
	@Override
	public IConstructor setAnnotations(Map<String, IValue> annotations) {
		return removeAnnotations().joinAnnotations(annotations);
	}
	
	@Override
	public IConstructor removeAnnotations() {
		return new Constructor(this);
	}
	
	@Override
	public IConstructor removeAnnotation(String key) {
		return new Constructor(this, key);
	}

	public boolean has(String label) {
		return getConstructorType().hasField(label);
	}
}

@@ -44,16 +44,42 @@
 rence;%0A%0A
+import java.util.HashMap;%0A
 import j
@@ -444,16 +444,67 @@
 e.Type;%0A
+import org.eclipse.imp.pdb.facts.type.TypeFactory;%0A
 import o
@@ -604,68 +604,8 @@
 tor;
-%0Aimport org.eclipse.imp.pdb.facts.visitors.VisitorException;
 %0A%0A/*
@@ -1489,29 +1489,44 @@
  %7B%0A%09%09return 
-f
+getConstructor
 Type
+()
 .getAbstract
@@ -1575,25 +1575,811 @@
 orType() %7B%0A%09
-%09
+  if (fType.getAbstractDataType().isParameterized()) %7B%0A      assert fType.getAbstractDataType().isOpen();%0A    %0A      // this assures we always have the most concrete type for constructors.%0A      Type%5B%5D actualTypes = new Type%5BfChildren.length%5D;%0A      for (int i = 0; i %3C fChildren.length; i++) %7B%0A        actualTypes%5Bi%5D = fChildren%5Bi%5D.getType();%0A      %7D%0A    %0A      Map%3CType,Type%3E bindings = new HashMap%3CType,Type%3E();%0A      fType.getFieldTypes().match(TypeFactory.getInstance().tupleType(actualTypes), bindings);%0A      %0A      for (Type field : fType.getAbstractDataType().getTypeParameters()) %7B%0A        if (!bindings.containsKey(field)) %7B%0A          bindings.put(field, TypeFactory.getInstance().voidType());%0A        %7D%0A      %7D%0A      %0A      return fType.instantiate(bindings);%0A    %7D%0A    %0A    
 return fType

fix in fast propagated to reference impl