package org.spoofax.terms.attachments;

import static org.spoofax.interpreter.terms.IStrategoTerm.MUTABLE;
import static org.spoofax.terms.attachments.ParentAttachment.getParent;
import static org.spoofax.terms.attachments.ParentAttachment.putParent;

import org.spoofax.interpreter.terms.IStrategoAppl;
import org.spoofax.interpreter.terms.IStrategoConstructor;
import org.spoofax.interpreter.terms.IStrategoInt;
import org.spoofax.interpreter.terms.IStrategoList;
import org.spoofax.interpreter.terms.IStrategoPlaceholder;
import org.spoofax.interpreter.terms.IStrategoReal;
import org.spoofax.interpreter.terms.IStrategoString;
import org.spoofax.interpreter.terms.IStrategoTerm;
import org.spoofax.interpreter.terms.IStrategoTuple;
import org.spoofax.interpreter.terms.ITermFactory;
import org.spoofax.terms.AbstractTermFactory;

/** 
 * @author Lennart Kats <lennart add lclnet.nl>
 */
public class ParentTermFactory extends AbstractTermFactory {
	
	// (doesn't implement WrappedTermFactory since it crucially needs
	//  to override any and all new term construction methods)
	
	private final ITermFactory baseFactory;

	public ParentTermFactory(ITermFactory baseFactory) {
		super(MUTABLE);
		assert !(baseFactory instanceof ParentTermFactory);
		this.baseFactory = baseFactory.getFactoryWithStorageType(MUTABLE);
		assert checkStorageType(this.baseFactory, MUTABLE);
	}

	public ITermFactory getFactoryWithStorageType(int storageType) {
		assert getDefaultStorageType() <= storageType;
		return this;
	}

	public IStrategoPlaceholder makePlaceholder(IStrategoTerm template) {
		return baseFactory.makePlaceholder(template);
	}

	public IStrategoInt makeInt(int i) {
		return baseFactory.makeInt(i);
	}

	public IStrategoReal makeReal(double d) {
		return baseFactory.makeReal(d);
	}

	public IStrategoString makeString(String s) {
		return baseFactory.makeString(s);
	}

	public IStrategoTerm annotateTerm(IStrategoTerm term, IStrategoList annotations) {
		return baseFactory.annotateTerm(term, annotations);
	}

	@Override
	public IStrategoAppl makeAppl(IStrategoConstructor constructor, IStrategoTerm[] kids, IStrategoList annotations) {
		IStrategoAppl result = baseFactory.makeAppl(constructor, kids, annotations);
		assert ParentAttachment.get(result) == null :
			"Unexpected parent attachment; doubly wrapped term factory?";
		configure(result, kids);
		return result;
	}

	@Override
	public IStrategoTuple makeTuple(IStrategoTerm[] kids, IStrategoList annotations) {
		IStrategoTuple result = baseFactory.makeTuple(kids, annotations);
		configure(result, kids);
		return result;
	}

	@Override
	public IStrategoList makeList(IStrategoTerm[] kids, IStrategoList annotations) {
		IStrategoList result = baseFactory.makeList(kids, annotations);
		configure(result, kids);
		return result;
	}

	@Override
	public IStrategoList makeListCons(IStrategoTerm head, IStrategoList tail, IStrategoList annos) {
		IStrategoList result = baseFactory.makeListCons(head, tail, annos);
		putParent(head, null, result);
		putParent(tail, null, result);
		return result;
	}
	
	protected void configure(IStrategoTerm parent, IStrategoTerm[] kids) {
		for (IStrategoTerm kid : kids)
			putParent(kid, parent, null);
	}

	public void copyAttachments(IStrategoTerm from, IStrategoTerm to, boolean ignoreParentAttachments) {
		if (!ignoreParentAttachments) {
			super.copyAttachments(from, to);
		} else {
			ParentAttachment parent = ParentAttachment.get(to);
			super.copyAttachments(from, to);
			putParent(to, parent);
		}
	}

	public IStrategoString tryMakeUniqueString(String name) {
		return baseFactory.tryMakeUniqueString(name);
	}
	
	public static boolean isParentTermFactory(ITermFactory factory) {
		if (factory instanceof ParentTermFactory)
			return true;
		IStrategoTerm i = factory.makeInt(42);
		return getParent(factory.makeTuple(i).getSubterm(0)) != null;
	}

}

@@ -243,16 +243,56 @@
 tParent;
+%0Aimport static org.spoofax.terms.Term.*;
 %0A%0Aimport
@@ -865,16 +865,63 @@
 Factory;
+%0Aimport org.spoofax.terms.StrategoListIterator;
 %0A%0A/** %0A 
@@ -1661,30 +1661,53 @@
 mplate) %7B%0A%09%09
-return
+IStrategoPlaceholder result =
  baseFactory
@@ -1734,16 +1734,64 @@
 plate);%0A
+%09%09configure(result, template);%0A%09%09return result;%0A
 %09%7D%0A%0A%09pub
@@ -2119,58 +2119,387 @@
 %7B%0A%09%09
-return baseFactory.annotateTerm(term, annotations)
+// This is a strange case%0A%09%09// SpoofaxTestingJSGLRI.parseTestedFragments() depends on it%0A%09%09IStrategoTerm result = baseFactory.annotateTerm(term, annotations);%0A%09%09if (isTermList(term)) %7B%0A%09%09%09for (IStrategoTerm subterm : StrategoListIterator.iterable((IStrategoList) term))%0A%09%09%09%09configure(result, subterm);%0A%09%09%7D else %7B%0A%09%09%09configure(result, result.getAllSubterms());%0A%09%09%7D%0A%09%09return result
 ;%0A%09%7D
@@ -3657,16 +3657,16 @@
 : kids)%0A
-
 %09%09%09putPa
@@ -3688,24 +3688,130 @@
 t, null);%0A%09%7D
+%0A%09%0A%09protected void configure(IStrategoTerm parent, IStrategoTerm kid) %7B%0A%09%09putParent(kid, parent, null);%0A%09%7D
 %0A%0A%09public vo

update parent terms when adding annotations, which may or may not be a good idea