/*
The contents of this file are subject to the Common Public Attribution License 
Version 1.0 (the "License"); you may not use this file except in compliance with 
the License. You may obtain a copy of the License at 
http://www.projity.com/license . The License is based on the Mozilla Public 
License Version 1.1 but Sections 14 and 15 have been added to cover use of 
software over a computer network and provide for limited attribution for the 
Original Developer. In addition, Exhibit A has been modified to be consistent 
with Exhibit B.

Software distributed under the License is distributed on an "AS IS" basis, 
WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the 
specific language governing rights and limitations under the License. The 
Original Code is OpenProj. The Original Developer is the Initial Developer and 
is Projity, Inc. All portions of the code written by Projity are Copyright (c) 
2006, 2007. All Rights Reserved. Contributors Projity, Inc.

Alternatively, the contents of this file may be used under the terms of the 
Projity End-User License Agreeement (the Projity License), in which case the 
provisions of the Projity License are applicable instead of those above. If you 
wish to allow use of your version of this file only under the terms of the 
Projity License and not to allow others to use your version of this file under 
the CPAL, indicate your decision by deleting the provisions above and replace 
them with the notice and other provisions required by the Projity  License. If 
you do not delete the provisions above, a recipient may use your version of this 
file under either the CPAL or the Projity License.

[NOTE: The text of this license may differ slightly from the text of the notices 
in Exhibits A and B of the license at http://www.projity.com/license. You should 
use the latest text at http://www.projity.com/license for your modifications.
You may not remove this license text from the source files.]

Attribution Information: Attribution Copyright Notice: Copyright � 2006, 2007 
Projity, Inc. Attribution Phrase (not exceeding 10 words): Powered by OpenProj, 
an open source solution from Projity. Attribution URL: http://www.projity.com 
Graphic Image as provided in the Covered Code as file:  openproj_logo.png with 
alternatives listed on http://www.projity.com/logo

Display of Attribution Information is required in Larger Works which are defined 
in the CPAL as a work which combines Covered Code or portions thereof with code 
not governed by the terms of the CPAL. However, in addition to the other notice 
obligations, all copies of the Covered Code in Executable and Source Code form 
distributed must, as a form of attribution of the original author, include on 
each user interface screen the "OpenProj" logo visible to all users.  The 
OpenProj logo should be located horizontally aligned with the menu bar and left 
justified on the top left of the screen adjacent to the File menu.  The logo 
must be at least 100 x 25 pixels.  When users click on the "OpenProj" logo it 
must direct them back to http://www.projity.com.  
*/
package com.projity.grouping.core.model;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Set;

import javax.swing.event.TreeModelListener;
import javax.swing.tree.TreePath;
import javax.swing.undo.UndoableEdit;
import javax.swing.undo.UndoableEditSupport;

import com.projity.association.AssociationList;
import com.projity.document.Document;
import com.projity.field.Field;
import com.projity.field.FieldContext;
import com.projity.field.FieldParseException;
import com.projity.grouping.core.Node;
import com.projity.grouping.core.NodeBridge;
import com.projity.grouping.core.NodeFactory;
import com.projity.grouping.core.VoidNodeImpl;
import com.projity.grouping.core.hierarchy.HierarchyUtils;
import com.projity.grouping.core.hierarchy.MutableNodeHierarchy;
import com.projity.grouping.core.hierarchy.NodeHierarchy;
import com.projity.pm.assignment.Assignment;
import com.projity.pm.assignment.AssignmentService;
import com.projity.pm.assignment.HasAssignments;
import com.projity.pm.dependency.Dependency;
import com.projity.pm.resource.ResourceImpl;
import com.projity.pm.task.NormalTask;
import com.projity.pm.task.Task;
import com.projity.undo.ModelFieldEdit;
import com.projity.undo.NodeCreationEdit;
import com.projity.undo.NodeDeletionEdit;
import com.projity.undo.NodeImplChangeAndValueSetEdit;
import com.projity.undo.NodeImplChangeEdit;
import com.projity.undo.NodePasteEdit;
import com.projity.undo.NodeUndoInfo;
import com.projity.undo.UndoController;

/**
 *
 */
public class DefaultNodeModel implements NodeModel {

	protected NodeHierarchy hierarchy;
	protected NodeModelDataFactory dataFactory = null;

	/**
	 * 
	 */
	public DefaultNodeModel() {
		hierarchy=new MutableNodeHierarchy();
	}
	
	public DefaultNodeModel(NodeModelDataFactory dataFactory) {
		this();
		this.dataFactory = dataFactory;
	}
	//for clone()
	DefaultNodeModel(NodeHierarchy hierarchy, NodeModelDataFactory dataFactory) {
		this.hierarchy=hierarchy;
		this.dataFactory = dataFactory;
	}
	
	public void addBefore(LinkedList siblings,Node newNode,int actionType){
		Node previous,next,parent;
		boolean firstChild;
		if (siblings.size()==0){
			return;
		}else if (siblings.size()==1){
			previous=null;
			next=(Node)siblings.removeLast();
			parent=null;
			firstChild=true;
		}else{
			previous=(Node)siblings.removeFirst();  //no need to clone, list used only here, see CommonSpreadSheetModel
			next=(Node)siblings.removeLast();
			parent=(Node)next.getParent();
			firstChild=(parent==previous);
			if (firstChild) parent=previous;
			else parent=(Node)previous.getParent();
			remove(siblings, NodeModel.SILENT);
		}
		siblings.add(newNode);
		add(parent,siblings,(firstChild)?0:(parent.getIndex(previous)+1),NodeModel.SILENT);
		
		getDataFactory().setGroupDirty(true);
		//TODO need undo here
	}
	
	public void addBefore(Node sibling,Node newNode,int actionType){
		Node parent=(Node)sibling.getParent();
		add(parent,newNode,parent.getIndex(sibling),actionType);
	}
	public void addBefore(Node sibling,List newNodes,int actionType){
		Node parent=(Node)sibling.getParent();
		add(parent,newNodes,parent.getIndex(sibling),actionType);
	}
	public void add(Node parent,Node child,int actionType){
		add(parent,child,-1,actionType);
	}
	public void add(Node parent,Node child,int position,int actionType){
		ArrayList children=new ArrayList();
		children.add(child);
		add(parent,children,position,actionType);
		//hierarchy.add(parent,child,position,actionType);
	}
	public void add(Node parent,List children,int actionType){
		add(parent,children,-1,actionType);
		//hierarchy.add(parent,children,actionType);
	}
	public void add(Node parent,List children,int position,int actionType){
		hierarchy.add(parent,children,position,actionType);
		//Undo
		if (isUndo(actionType)) postEdit(new NodeCreationEdit(this,parent,children,position));

	}
	
	
	public void add(Node child,int actionType){
		add((Node)hierarchy.getRoot(),child,actionType);
	}
	
	public Node newNode(Node parent,int position,int actionType){
		//check if position is correct
		Node node;
		int p=position;
		int i=0;
		for (Enumeration e=parent.children();e.hasMoreElements();i++){
			node=(Node)e.nextElement();
			if (i==p){
				if (node.getImpl() instanceof Assignment) p++;
				else{
					Node newNode=NodeFactory.getInstance().createVoidNode();
					add(parent,newNode,p,NodeModel.NORMAL);
					return newNode;
				}
			}
		}
		Node newNode=NodeFactory.getInstance().createVoidNode();
		add(parent,newNode,-1,NodeModel.NORMAL);
		return newNode;
		
	}
	
	public void paste(Node parent,List nodes,int position,int actionType){
		//nodes=copy(nodes,NodeModel.SILENT); //make an other copy, in case it is copied more than one time
		//done in transfert handler
		
		hierarchy.paste(parent,nodes,position,this,actionType);
		//Undo
		if (isUndo(actionType)) postEdit(new NodePasteEdit(this,parent,nodes,position));
	}

	public boolean isAncestor(Node parent, Node child) {
		if (child == null)
			return false;
		if (parent == child)
			return true;
		return isAncestor(parent,getParent(child));
	}
	
	public boolean isAncestorOrDescendant(Node one, Node two) {
		return isAncestor(one,two) || isAncestor(two,one);
	}

	public boolean testAncestorOrDescendant(Node one, List nodes) {
		Iterator i = nodes.iterator();
		while (i.hasNext()) {
			if (isAncestorOrDescendant(one,(Node)i.next()))
				return false;
		}
		return true;
	}
	
	public void move(Node parent,List nodes,int position,int actionType){
		if (!testAncestorOrDescendant(parent,nodes)) // don't allow circular
			return;
		List cutNodes=cut(nodes,false,actionType);
		//List cutNodes=cut(nodes,actionType&UNDO); //TODO fixes bug 225 but it's probably breaking undo
		paste(parent,cutNodes,position,actionType);
	}

	
	/**
	 * Convenience method to add a collection of objects (not nodes) to the node model
	 * @param parent
	 * @param collection
	 */	
	public void addImplCollection(Node parent, Collection collection,int actionType) {
		Iterator i = collection.iterator();
		Node child;
		while (i.hasNext()) {
			child = NodeFactory.getInstance().createNode(i.next());
			add(parent,child,actionType);
		}
			
	}
	
	public void remove(Node node,int actionType){
		remove(node, actionType, true,true);
	}
	public void remove(Node node,int actionType,boolean removeDependencies){
		remove(node, actionType, true,removeDependencies);
	}
	protected void remove(Node node,int actionType,boolean filterAssignments,boolean removeDependencies){
		ArrayList nodes=new ArrayList();
		nodes.add(node);
		remove(nodes,actionType,filterAssignments,removeDependencies);
		//hierarchy.remove(node,this,actionType); 
		//it calls back removeApartFromHierarchy for each node to remove
	}
	public void remove(List nodes,int actionType){
		remove(nodes, actionType, true);
	}
	public void remove(List nodes,int actionType,boolean removeDependencies){
		remove(nodes, actionType, true,removeDependencies);
	}
	protected void remove(List nodes,int actionType,boolean filterAssignments,boolean removeDependencies){
		if (undoController!=null&&isUndo(actionType)){
			undoController.getEditSupport().beginUpdate();
		}
		try {
			ArrayList roots=new ArrayList();
			HierarchyUtils.extractParents(nodes, roots);
			if (filterAssignments)
				for (Iterator i=roots.iterator();i.hasNext();){
					Node node=(Node)i.next();
					if (node.getImpl() instanceof Assignment){
						i.remove();
						AssignmentService.getInstance().remove(node, this, true);
					}
				}
			boolean containsSubprojects=false;
			List parents=new ArrayList();
			List positions=new ArrayList();
			NodeBridge node,parent;
			for (Iterator i=roots.iterator();i.hasNext();){
				node=(NodeBridge)i.next();
				if (NodeModelUtil.nodeIsSubproject(node))
					containsSubprojects=true;
				parent=(NodeBridge)node.getParent();
				parents.add(parent);
				positions.add(new Integer(parent.getIndex(node)));
			}
			if (!confirmRemove(roots))
				return;
			
			if (undoController!=null&&isUndo(actionType)){
				if (containsSubprojects) undoController.clear();
			}
			
			hierarchy.remove(roots,this,actionType,removeDependencies); 
			//it calls back removeApartFromHierarchy for each node to remove
			hierarchy.checkEndVoidNodes(actionType);

			//Undo
			if (undoController!=null){
				if (!containsSubprojects&&isUndo(actionType)){
					postEdit(new NodeDeletionEdit(this,parents,roots,positions));
				}
			}
		} finally{
			if (undoController!=null){
				if (isUndo(actionType)){
					undoController.getEditSupport().endUpdate();
				}
			}
		}
	}
	public void removeAll(int actionType){
		hierarchy.removeAll(this,actionType);
	}
	public boolean removeApartFromHierarchy(Node node,boolean cleanAssignment,int actionType,boolean removeDependencies){
		if (!isEvent(actionType)) 
			return true;
//		try {
//			beginUpdate();
			if (node.getImpl() instanceof Assignment){
				Assignment assignment=(Assignment)node.getImpl();
//				if (cleanAssignment) 
					AssignmentService.getInstance().remove(assignment,cleanAssignment,this,isUndo(actionType)); //LC 8/4/2006 - hk 7/8/2006 changed null to this so event will be fired
//				else if (assignment.getResource()!=ResourceImpl.getUnassignedInstance()){
//					assignment.getResource().removeAssignment(assignment);
//				}
			
			
			//AssignmentService.getInstance().remove((Assignment)node.getImpl(),this);
			}else if (dataFactory!=null&&!node.isVoid())
				dataFactory.remove(node.getImpl(),this,false,isUndo(actionType),removeDependencies); //TODO make this work properly with subproject
//		} finally {
//			endUpdate();
//		}
		return true;
	}
	
	public List cut(List nodes,int actionType){
		return cut(nodes,true,actionType);
	}	
	public List cut(List nodes,boolean clone,int actionType){
		List newNodes=copy(nodes,clone,actionType);
		remove(nodes,actionType);
		return newNodes;
//		ArrayList parentNodes=new ArrayList(nodes.size());
//		HierarchyUtils.extractParents(nodes,parentNodes);
//		remove(parentNodes,actionType);
//		//TODO check parent is null
//		return parentNodes;
	}
	
	
	public List copy(List nodes,int actionType){
		return copy(nodes,true,actionType);
	}
	
	public List copy(List nodes,boolean clone,int actionType){
		ArrayList parentNodes=new ArrayList(nodes.size());
		HierarchyUtils.extractParents(nodes,parentNodes);
		if (!clone) return parentNodes;
		Set assignedNodes=new HashSet();
		Map implMap=new HashMap();
		Set<Dependency> predecessors=new HashSet<Dependency>();
		Set<Dependency> successors=new HashSet<Dependency>();
		for (ListIterator i=parentNodes.listIterator();i.hasNext();){
			Node parent=(Node)i.next();
			Node newParent=cloneNode(parent,null,implMap,predecessors,successors);
			cloneBranch(parent,newParent,assignedNodes,implMap,predecessors,successors);
			i.remove();
			i.add(newParent);
		}
		
		//rebuild dependencies
		for (Dependency dependency : predecessors) {
			if (successors.contains(dependency)){
				Task predecessor=(Task)implMap.get(dependency.getPredecessor());
				Task successor=(Task)implMap.get(dependency.getSuccessor());
				if (predecessor!=null&&successor!=null){
					Dependency d=Dependency.getInstance(predecessor, successor, dependency.getDependencyType(), dependency.getLag());
					//Serializer.connectDependency(dependency, predecessor, successor);
					predecessor.getDependencyList(false).add(d);
					successor.getDependencyList(true).add(d);
				}
			}
		}
		
		for (Iterator i=assignedNodes.iterator();i.hasNext();){
			addAssignments((Node)i.next());
		}
		
		for (ListIterator i=parentNodes.listIterator();i.hasNext();){
			Node node=(Node)i.next();
			cleanBranch(node);
		}

		return parentNodes;
	}
	private void cloneBranch(Node parent,Node newParent,Set assignedNodes,Map implMap,Set<Dependency> predecessors,Set<Dependency> successors){
		for (Iterator i=parent.childrenIterator();i.hasNext();){
				Node child=(Node)i.next();
				if (child.getImpl() instanceof Assignment){
					assignedNodes.add(newParent);
				}else{
					Node newChild=cloneNode(child,newParent,implMap,predecessors,successors);
					cloneBranch(child,newChild,assignedNodes,implMap,predecessors,successors);
				}
		}
	}
	private Node cloneNode(Node oldNode,Node newParent,Map implMap,Set<Dependency> predecessors,Set<Dependency> successors){
		Object oldNodeImpl=oldNode.getImpl();
		Object newNodeImpl=cloneNodeImpl(oldNodeImpl);
		implMap.put(oldNodeImpl, newNodeImpl);
		if (oldNodeImpl instanceof Task){
			Task t=(Task)oldNodeImpl;
			predecessors.addAll(t.getDependencyList(true));
			successors.addAll(t.getDependencyList(false));
		}
		Object parentImpl = (newParent==null)?null:newParent.getImpl();
		NodeModelDataFactory factory = getFactory(parentImpl);

		factory.addUnvalidatedObject(newNodeImpl,this,parentImpl);

		Node newNode=NodeFactory.getInstance().createNode(newNodeImpl);
		if (newParent!=null) newParent.add(newNode);
		if (parentImpl != null&& parentImpl instanceof Task)
			((Task)parentImpl).setWbsChildrenNodes(getHierarchy().getChildren(newParent)); //rebuild children task's wbs cache
		return newNode;
	}
	private Object cloneNodeImpl(Object impl){
				if (impl instanceof VoidNodeImpl){
					return new VoidNodeImpl();
				}else if (impl instanceof NormalTask){
					return ((NormalTask)impl).clone();
				}else if (impl instanceof ResourceImpl){
					return ((ResourceImpl)impl).clone();
				}//TOTO assignments
		return null;
	}
	
	private void cleanBranch(Node parent){
		for (Iterator i=parent.childrenIterator();i.hasNext();){
				Node child=(Node)i.next();
				cleanNodeImpl(child.getImpl());
				cleanBranch(child);
		}
	}
	private void cleanNodeImpl(Object impl){
		if (impl instanceof NormalTask){
			((NormalTask)impl).cleanClone();
		}else if (impl instanceof ResourceImpl){
			((ResourceImpl)impl).cleanClone();
		}
	}
	
	private void addAssignments(Node node){
		if (node.getImpl() instanceof HasAssignments){
			AssociationList assignments=((HasAssignments)node.getImpl()).getAssignments();
			if (assignments==null) return;
			for (ListIterator i=assignments.listIterator(assignments.size());i.hasPrevious();){
				Assignment assignment=(Assignment)i.previous();
				if (assignment.isDefault()) continue;
				Node assignmentNode=NodeFactory.getInstance().createNode(assignment);
				node.insert(assignmentNode,0);
			}
		}
	}
	
	
	
	
	
	
	
	public Object clone(){
		return new DefaultNodeModel((NodeHierarchy)hierarchy.clone(), dataFactory);
	}
	
	public Iterator iterator(){
		return hierarchy.iterator();
	}
	public Iterator iterator(Node rootNode){
		return hierarchy.iterator(rootNode);
	}
	public Iterator shallowIterator(int maxLevel,boolean returnRoot){
		return hierarchy.shallowIterator(maxLevel,returnRoot);
	}
	/**
	 * @return Returns the hierarchy.
	 */
	public NodeHierarchy getHierarchy() {
		return hierarchy;
	}

	
	
	/**
	 * @param hierarchy The hierarchy to set.
	 */
	public void setHierarchy(NodeHierarchy hierarchy) {
		this.hierarchy = hierarchy;
	}
	
	/* (non-Javadoc)
	 * @see com.projity.grouping.core.NodeModel#hasChildren(com.projity.grouping.core.Node)
	 */
	public boolean hasChildren(Node node) {
		return !hierarchy.isLeaf(node);
	}
	public boolean isSummary(Node node){
		return hierarchy.isSummary(node);
	}
	/**
	 * @param key
	 * @param c
	 * @return
	 */
	public Node search(Object key, Comparator c) {
		return hierarchy.search(key, c);
	}
	

	
	private static ImplComparator implComparatorInstance = null;
	public static ImplComparator getImplComparatorInstance() {
		if (implComparatorInstance == null)
			implComparatorInstance = new ImplComparator();
		return implComparatorInstance;
	}

	public static class ImplComparator implements Comparator {
		ImplComparator() {}
		public int compare(Object node, Object impl) {
			if (((Node)node).getImpl() == impl)
				return 0;
			else
				return 1;
		}
	}
	
	public Node search(Object key) {
		//TODO consider using a hashtable instead of searching like this
		return search(key,getImplComparatorInstance());
	} 	
	
	
	// Below is for tree model
	/**
	 * @param arg0
	 */
	public void addTreeModelListener(TreeModelListener arg0) {
		hierarchy.addTreeModelListener(arg0);
	}
	/**
	 * @param arg0
	 * @param arg1
	 * @return
	 */
	public Object getChild(Object arg0, int arg1) {
		return hierarchy.getChild(arg0, arg1);
	}
	/**
	 * @param arg0
	 * @return
	 */
	public int getChildCount(Object arg0) {
		return hierarchy.getChildCount(arg0);
	}
	/**
	 * @param arg0
	 * @param arg1
	 * @return
	 */
	public int getIndexOfChild(Object arg0, Object arg1) {
		return hierarchy.getIndexOfChild(arg0, arg1);
	}
	/**
	 * @return
	 */
	public Object getRoot() {
		return hierarchy.getRoot();
	}
	/**
	 * @param arg0
	 * @return
	 */
	public boolean isLeaf(Object arg0) {
		return hierarchy.isLeaf(arg0);
	}
	/**
	 * @param arg0
	 */
	public void removeTreeModelListener(TreeModelListener arg0) {
		hierarchy.removeTreeModelListener(arg0);
	}
	/**
	 * @param arg0
	 * @param arg1
	 */
	public void valueForPathChanged(TreePath arg0, Object arg1) {
		hierarchy.valueForPathChanged(arg0, arg1);
	}

	/* (non-Javadoc)
	 * @see com.projity.grouping.core.NodeModel#setFieldValue(com.projity.field.Field, com.projity.grouping.core.Node, java.lang.Object, java.lang.Object, com.projity.field.FieldContext)
	 */
	public void setFieldValue(Field field, Node node, Object eventSource, Object value, FieldContext context,int actionType) throws FieldParseException {
		Object oldValue=field.getValue(node,this,context);
		
//		// this prevents the field from sending an update message.  However, ideally the field will send the message and the hiearchy event wont
//		if (context != null)
//			context.setUserObject(FieldContext.getNoUpdateInstance());
		
		
		field.setValue(node, this,eventSource, value, context);
		
//		No longer sending update event
//		if (isEvent(actionType)) hierarchy.fireUpdate(new Node[]{node});
		//TODO treat the ObjectEvent instead

		//Undo
		if (isUndo(actionType)) postEdit(new ModelFieldEdit(this,field,node,eventSource,value,oldValue,context));

	}

	public Node replaceImplAndSetFieldValue(Node node, LinkedList previous, Field field, Object eventSource, Object value,FieldContext context,int actionType) throws FieldParseException {
		//the line following a subproject is connected to the main project
		if (previous!=null&&previous.size()>0){
			Node p=(Node)previous.getFirst();
			if (p!=null&&p.isInSubproject()&&node.getSubprojectLevel()<p.getSubprojectLevel()){
				while (node.getSubprojectLevel()<p.getSubprojectLevel()) p=(Node)p.getParent();
				LinkedList newPrevious=new LinkedList();
				newPrevious.add(p);
				Node vn,pvn;
				for (Iterator i=previous.iterator();i.hasNext();){
					vn=(Node)i.next();
					pvn=(Node)vn.getParent();
					while(pvn!=null&&pvn!=p) pvn=(Node)pvn.getParent();
					if (pvn!=p) newPrevious.add(vn);
				}
				Object parentImpl = p.getImpl();
				NodeModelDataFactory factory = getFactory(parentImpl);
				return replaceImplAndSetFieldValue(node,newPrevious,factory.createUnvalidatedObject(this, parentImpl),field,eventSource,value,context,actionType);

			}
//			if (p!=null&&p.getImpl() instanceof NormalTask){
//				Task task=(Task)p.getImpl();
//				boolean subprojectParent=false;
//				while (task.getOwningProject()!=task.getProject()){
//					Node pParent=(Node)p.getParent();
//					if (pParent.getIndex(p)==pParent.getChildCount()-1){
//						p=pParent;
//						subprojectParent=true;
//					}else{
//						subprojectParent=false;
//						break;
//					}
//				}
//				if (subprojectParent){
//					LinkedList newPrevious=(LinkedList)previous.clone();
//					newPrevious.set(0, p);
//					Object parentImpl = p.getImpl();
//					NodeModelDataFactory factory = getFactory(parentImpl);
//					return replaceImplAndSetFieldValue(node,newPrevious,factory.createUnvalidatedObject(this, parentImpl),field,eventSource,value,context,actionType);
//
//				}
//			}
			
		}
		
		Node parent=(Node)node.getParent();
		Object parentImpl = (parent==getHierarchy().getRoot())?null:parent.getImpl();
		NodeModelDataFactory factory = getFactory(parentImpl);
		return replaceImplAndSetFieldValue(node,previous,factory.createUnvalidatedObject(this, parentImpl),field,eventSource,value,context,actionType);
	}
	
	private NodeModelDataFactory getFactory(Object parentImpl) {
		if (parentImpl == null)
			return dataFactory;
		else
			return dataFactory.getFactoryToUseForChildOfParent(parentImpl);
	}
	public Node replaceImplAndSetFieldValue(Node node, LinkedList previous, Object newImpl, Field field, Object eventSource, Object value,FieldContext context,int actionType) throws FieldParseException {
		List previousPosition=null;
		//move in hierarchy
		if (previous!=null){
			LinkedList p=(LinkedList)previous.clone();
			Node sibling=(Node)p.removeFirst();
			Node parent=(Node)sibling.getParent();
			p.add(node);
			if (getUndoableEditSupport()!=null&isUndo(actionType)){
				previousPosition=new ArrayList(p.size());
				for (Iterator i=p.iterator();i.hasNext();){
					Node n=(Node)i.next();
					previousPosition.add(new NodeImplChangeAndValueSetEdit.Position((Node)n.getParent(),n,n.getParent().getIndex(n)));
				}
			}
			remove(p, NodeModel.SILENT);
			add(parent,p,parent.getIndex(sibling)+1,NodeModel.SILENT);
			//TODO need undo here
		}

		
		
		Node parent=(Node)node.getParent();
		
		Object parentImpl = (parent==getHierarchy().getRoot())?null:parent.getImpl();
		NodeModelDataFactory factory = getFactory(parentImpl);
		factory.addUnvalidatedObject(newImpl,this, parentImpl);
		Object oldImpl=node.getImpl();
		node.setImpl(newImpl);
		try {
			field.setValue(node, this,null, value, context); // will throw if error
		} catch (FieldParseException e) {
			factory.rollbackUnvalidated(this, newImpl); // in some cases, such as ValueObjectForInterval, some cleanup is needed
			throw e;
		}
		// if no exception was thrown, then validate the object and hook it into model
		factory.validateObject(newImpl, this, eventSource, null,true);
		
		hierarchy.renumber();
		
//		dataFactory.fireCreated(newImpl);
		hierarchy.checkEndVoidNodes(actionType^NodeModel.EVENT);
		getHierarchy().fireInsertion(new Node[]{node}); //TODO Cause critical path to run twice
		
		//Undo
		if (isUndo(actionType)) postEdit(new NodeImplChangeAndValueSetEdit(this,node,previous,previousPosition,oldImpl,field,value,context,eventSource));
		return node;
	}
	public Node replaceImpl(Node node,Object newImpl, Object eventSource,int actionType){
		Node parent = getParent(node);
		Object parentImpl = (parent==getHierarchy().getRoot())?null:parent.getImpl();
		NodeModelDataFactory factory = getFactory(parentImpl);

		factory.addUnvalidatedObject(newImpl,this, parentImpl);
		Object oldImpl=node.getImpl();
		node.setImpl(newImpl);
		factory.validateObject(newImpl, this, eventSource,null,false);
		
		hierarchy.renumber();
		
//		dataFactory.fireCreated(newImpl);
		getHierarchy().fireRemoval(new Node[]{node}); //TODO Cause critical path to run twice
		
		hierarchy.checkEndVoidNodes(actionType);
		//Undo
		if (isUndo(actionType)) postEdit(new NodeImplChangeEdit(this,node,oldImpl,eventSource));
		return node;
	}
	
	
	public NodeModelDataFactory getDataFactory() {
		return dataFactory;
	}
	/**
	 * @param dataFactory The dataFactory to set.
	 */
	public void setDataFactory(NodeModelDataFactory dataFactory) {
		this.dataFactory = dataFactory;
	}
	
	public List getChildren(Node parent){
		return getHierarchy().getChildren(parent);
	}
	public Node getParent(Node child){
		return getHierarchy().getParent(child);
	}

    public Document getDocument() {
        return null;
    }
    
    public static boolean isEvent(int actionType){
    	return (actionType&NodeModel.EVENT)==NodeModel.EVENT;
    }
    public static boolean isUndo(int actionType){
    	return (actionType&NodeModel.UNDO)==NodeModel.UNDO;
    }

	
    protected UndoController undoController;
    

	
	public UndoController getUndoController() {
		return undoController;
	}

	public void setUndoController(UndoController undoController) {
		this.undoController = undoController;
	}

	public UndoableEditSupport getUndoableEditSupport() {
		if (undoController==null) return null;
		return undoController.getEditSupport();
	}
//	public void setUndoableEditSupport(UndoableEditSupport undoableEditSupport) {
//		this.undoableEditSupport = undoableEditSupport;
//	}

	public void postEdit(UndoableEdit edit){
		if (getUndoableEditSupport()!=null){
			getUndoableEditSupport().postEdit(edit);
		}

	}
	
	public boolean confirmRemove(List nodes) {
		return true;
	}
	
	
	protected boolean local,master=true;

	public boolean isLocal() {
		return local;
	}

	public void setLocal(boolean local) {
		this.local = local;
	}

	public boolean isMaster() {
		return master;
	}

	public void setMaster(boolean master) {
		this.master = master;
	}
	
	
	
	
//	protected int updateLevel=0;
//	protected synchronized void beginUpdate(){
//		updateLevel++;
//	}
//	protected synchronized void endUpdate(){
//		updateLevel--;
//	}
//	protected synchronized int getUpdateLevel(){
//		return updateLevel;
//	}

	
}
