package lu.tudor.santec.gecamed.esante.gui.tab;

import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.swing.JTree;
import javax.swing.event.TreeModelEvent;
import javax.swing.event.TreeModelListener;
import javax.swing.tree.TreeModel;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;

import lu.tudor.santec.gecamed.esante.ejb.entity.beans.CdaDocument;

/**
 * @author jens.ferring(at)tudor.lu
 * 
 * @version
 * <br>$Log: CdaTreeRoot.java,v $
 * <br>Revision 1.11  2014-02-04 10:08:34  ferring
 * <br>eSante ID management completed
 * <br>Only those documents will be shown, that are retrieved by the RSQ
 * <br>
 * <br>Revision 1.10  2013-11-25 08:27:25  ferring
 * <br>Root expanding and notifying of JTree fixed
 * <br>
 * <br>Revision 1.9  2013-11-22 14:51:12  ferring
 * <br>Restructured CDA tree and list notification, in order not to use an EntityBean as model.
 * <br>Buttons added to delete the CDA file and remove the incident entry
 * <br>
 * <br>Revision 1.8  2013-11-21 10:51:07  ferring
 * <br>tree added to root
 * <br>
 * <br>Revision 1.7  2013-11-21 09:48:55  ferring
 * <br>*** empty log message ***
 * <br>
 * <br>Revision 1.6  2013-11-12 07:56:37  ferring
 * <br>TreeModel changed
 * <br>
 * <br>Revision 1.5  2013-11-11 11:45:49  ferring
 * <br>load documents from database before synchronising with eSante
 * <br>
 * <br>Revision 1.4  2013-11-08 08:45:46  ferring
 * <br>sorting node correctly and notifying model correctly about changes
 * <br>
 * <br>Revision 1.3  2013-10-21 08:21:14  ferring
 * <br>tree expanding changed
 * <br>
 * <br>Revision 1.2  2013-10-10 13:32:09  ferring
 * <br>notify document tree for changes
 * <br>
 * <br>Revision 1.1  2013-10-09 13:36:36  ferring
 * <br>eSanté icon changed and tree view initialised
 * <br>
 */

public class CdaTreeRoot extends CdaTreeNode implements CdaListener, TreeModel
{
	/* ======================================== */
	// MEMBERS
	/* ======================================== */
	
	protected Map<String, NodeStructure>	treeStructure;
	
//	protected DefaultTreeModel				treeModel;
	
	protected Set<JTree>					trees		= new HashSet<JTree>();
	
	protected Collection<TreeModelListener>	listener	= new LinkedList<TreeModelListener>();
	
	
	
	/* ======================================== */
	// CONSTRUCTORS
	/* ======================================== */
	
	public CdaTreeRoot ()
	{
		super();
		this.root			= this;
//		this.treeModel		= new DefaultTreeModel(this);
		this.treeStructure	= new HashMap<String, NodeStructure>();
	}
	
	
	
	/* ======================================== */
	// CLASS BODY
	/* ======================================== */
	
	public TreePath addDocument (CdaDocument document)
	{
		return new CdaTreeNode(document, getParent(document, true))
				.createPath();
	}
	
	
	public TreePath[] addDocuments (List<CdaDocument> documents)
	{
		TreePath[]	paths = new TreePath[documents.size()];
		int			index = 0;
		
		
		for (CdaDocument document : documents)
		{
			paths[index++]	= addDocument(document);
		}
		reload();
		
		return paths;
	}
	
	
	@Override
	public void clear ()
	{
		super.clear();
		
		treeStructure.clear();
		reload();
	}
	
	
	public CdaTreeNode getNode (CdaDocument document)
	{
		String			classCode	= document.getClassCode();
		String			typeCode	= document.getTypeCode();
		NodeStructure	structure	= treeStructure.get(classCode);
		CdaTreeNode		node;
		
		if (structure != null)
		{
			node = structure.childNodes.get(typeCode);
			if (node != null)
			{
				for (CdaTreeNode child : node.children)
				{
					if (child.equals(document))
						return child;
				}
			}
		}
		
		// document not found in nodes
		return null;
	}
	
	
	@Override
	public TreePath createPath ()
	{
		return new TreePath(this);
	}
	
	
	public TreePath createPath (TreeNode node)
	{
		if (node instanceof CdaTreeNode)
			return ((CdaTreeNode) node).createPath();
		else
		{
			List<TreeNode> pathList = new LinkedList<TreeNode>();
			
			while (node != null)
			{
				pathList.add(node);
				node = node.getParent();
			}
			
			TreeNode[] path = new TreeNode[pathList.size()];
			int index = path.length - 1;
			for (TreeNode pathNode : pathList)
			{
				path[index--] = pathNode;
			}
			
			return new TreePath(path);
		}
	}
	
	
	
	/* ======================================== */
	// LIST DATA LISTENER METHODS
	/* ======================================== */
	
	public void documentsChanged (CdaChangeEvent e)
	{
		CdaTreeNode	node;
		
		
		if (e.getEventType() == CdaChangeEvent.DOCUMENTS_SOURCE_CHANGED)
		{
			for (JTree tree : getTrees())
			{
				collapse(tree, CdaTreeNode.LEVEL_TYPE);
//				expand(tree, CdaTreeNode.LEVEL_CLASS);
			}
		}
		else
		{
			for (CdaDocument doc : e.getDocuments())
			{
				if (e.getEventType() == CdaChangeEvent.DOCUMENTS_ADDED)
				{
					addDocument(doc);
				}
				else if (e.getEventType() == CdaChangeEvent.DOCUMENTS_REMOVED)
				{
					int start	= e.getStartIndex();
					int end		= e.getEndIndex();
					
					if ((start == 0 || start == CdaChangeEvent.MAX_SIZE)
							&& (end == CdaChangeEvent.MAX_SIZE))
					{
						// remove all
						root.clear();
					}
					else
					{
						throw new RuntimeException("Not yet implemented");
//						node = getNode(doc);
//						if (node != null)
//							node.removeFromParent();
					}
				}
				else
				{
					node = getNode(doc);
					if (node != null && node.getParent() != null)
						valueForPathChanged(node.getParent().createPath(), node);
				}
			}
		}
	}
	
	
	public void addTree (JTree tree)
	{
		trees.add(tree);
	}
	
	
	public Set<JTree> getTrees ()
	{
		return trees;
	}
	
	
	
	/* ======================================== */
	// HELP METHODS
	/* ======================================== */
	
	private CdaTreeNode getParent (CdaDocument document, boolean createNodes)
	{
		String classCode	= document.getClassCode();
		String typeCode		= document.getTypeCode();
		NodeStructure structure = treeStructure.get(classCode);
		
		if (structure == null)
		{
			if (!createNodes)
				return null;
			
			structure = new NodeStructure(document, root);
			treeStructure.put(classCode, structure);
		}
		
		CdaTreeNode node = structure.childNodes.get(typeCode);
		
		if (node == null)
		{
			if (!createNodes)
				return null;
			
			node	= new CdaTreeNode(document, structure.node);
			structure.childNodes.put(typeCode, node);
		}
		
		return node;
	}
	
	
//	private void fireDocumentChanged (int type, CdaChangeEvent e)
//	{
//		CdaTreeNode	node;
//		
//		
//		for (CdaDocument doc : e.getDocuments())
//		{
//			if (type == CdaChangeEvent.DOCUMENTS_ADDED)
//			{
//				addDocument(doc);
//			}
//			else
//			{
//				node = getNode(doc);
//				if (node == null)
//					continue;
//				
//				valueForPathChanged(node.createPath(), node);
//			}
//		}
//	}
	
	
	
	/* ======================================== */
	// CLASS NODE STRUCTURE
	/* ======================================== */
	
	private class NodeStructure
	{
		private CdaTreeNode					node;
		private Map<String, CdaTreeNode>	childNodes;
		
		private NodeStructure (CdaDocument document, CdaTreeNode parent)
		{
			this.node	= new CdaTreeNode(document, parent);
			childNodes	= new HashMap<String, CdaTreeNode>();
		}
	}
	
	
	/* ---------------------------------------- */
	// TREE MODEL METHODS
	/* ---------------------------------------- */
	
	public Object getRoot ()
	{
		return this;
	}
	
	
	public Object getChild (Object parent, int index)
	{
		return ((CdaTreeNode)parent).getChildAt(index);
	}
	
	
	public int getChildCount (Object parent)
	{
		return ((CdaTreeNode)parent).getChildCount();
	}
	
	
	public boolean isLeaf (Object node)
	{
		return ((CdaTreeNode)node).isLeaf();
	}
	
	
	public int getIndexOfChild (Object parent, Object child)
	{
		return ((CdaTreeNode)parent).getIndex((CdaTreeNode) child);
	}
	
	
	public void addTreeModelListener (TreeModelListener l)
	{
		listener.add(l);
	}
	
	
	public void removeTreeModelListener (TreeModelListener l)
	{
		listener.remove(l);
	}
	
	
	public void valueForPathChanged (TreePath parentPath, Object changedChild)
	{
		int				index = ((TreeNode)parentPath.getLastPathComponent()).getIndex((TreeNode) changedChild);
		TreeModelEvent	event = new TreeModelEvent(this, parentPath, 
				new int[] { index }, new Object[] { changedChild });
		
		for (TreeModelListener l : listener)
			l.treeNodesChanged(event);
	}
	
	
	public void treeNodeInserted (TreePath parentPath, TreeNode insertedChild)
	{
		int				index = ((TreeNode)parentPath.getLastPathComponent()).getIndex(insertedChild);
		TreeModelEvent	event = new TreeModelEvent(this, parentPath, 
				new int[] { index }, new Object[] { insertedChild });
		
		for (TreeModelListener l : listener)
			l.treeNodesInserted(event);
	}
	
	
	public void treeNodeRemoved (TreePath parentPath, int removedChildIndex, TreeNode removedChild)
	{
		TreeModelEvent event = new TreeModelEvent(this, parentPath, 
				new int[] { removedChildIndex }, new Object[] { removedChild });
		
		for (TreeModelListener l : listener)
			l.treeNodesRemoved(event);
	}
	
	
	public void treeStructureChanged ()
	{
		treeStructureChanged(this);
	}
	
	
	public void treeStructureChanged (CdaTreeNode node)
	{
		TreeModelEvent event = new TreeModelEvent(this, node.createPath());
		
		for (TreeModelListener l : listener)
			l.treeStructureChanged(event);
	}
}
