/* ====================================================================
 * The Apache Software License, Version 1.1
 *
 * Copyright (c) 2000 The Apache Software Foundation.  All rights
 * reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. The end-user documentation included with the redistribution,
 *    if any, must include the following acknowledgment:
 *       "This product includes software developed by the
 *        Apache Software Foundation (http://www.apache.org/)."
 *    Alternately, this acknowledgment may appear in the software itself,
 *    if and wherever such third-party acknowledgments normally appear.
 *
 * 4. The names "Apache" and "Apache Software Foundation" must
 *    not be used to endorse or promote products derived from this
 *    software without prior written permission. For written
 *    permission, please contact apache@apache.org.
 *
 * 5. Products derived from this software may not be called "Apache",
 *    nor may "Apache" appear in their name, without prior written
 *    permission of the Apache Software Foundation.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Apache Software Foundation.  For more
 * information on the Apache Software Foundation, please see
 * <http://www.apache.org/>.
 *
 * Portions of this software are based upon public domain software
 * originally written at the National Center for Supercomputing Applications,
 * University of Illinois, Urbana-Champaign.
 */
package com.bonevich.eclipse.cvsgrapher.model;

import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.team.internal.ccvs.core.CVSTag;
import org.eclipse.team.internal.ccvs.core.ILogEntry;
import org.eclipse.team.internal.ccvs.ui.VersionCollator;

import java.util.*;

/**
 * The CVS Graph root, representing the reosurce being displayed.
 * 
 * @author jbonevic
 */
public final class CvsGraphModel
{
	private static final String MAIN_BRANCH = "MAIN";
	private static final String MERGE_TAG_PREFIX = "MERGE";
	private static final String SOURCE_TAG_PREFIX = "SOURCE";
	
	private final List _entries;    //elements: ILogEntry

	private CvsGraphBranchModel _rootBranch;
	private List _nodes = new ArrayList();	//elements: ICvsGraphEntryModel
	private Map  _sourceTagMap = new HashMap();	//elements: tag => CvsGraphNodeModel
	private Map  _mergeTagMap = new HashMap();	//elements: tag => CvsGraphNodeModel
	
	public CvsGraphModel(ILogEntry[] entries)
	{
		_entries = Arrays.asList(entries);
		Collections.sort(_entries, new LogEntrySorter());
		init();
	}

	private void init()
	{
		ListIterator entries = _entries.listIterator();
		_rootBranch = createBranch("1", MAIN_BRANCH);
		addEntry(_rootBranch, _rootBranch, entries);
		
		createMergeConnections();
	}

	private void createMergeConnections()
	{
		Iterator sourceTags = _sourceTagMap.keySet().iterator();
		while (sourceTags.hasNext())
		{
			String sourceTag = (String) sourceTags.next();
			CvsGraphVersionModel sourceNode = (CvsGraphVersionModel) _sourceTagMap.get(sourceTag);
			CvsGraphVersionModel mergeNode = (CvsGraphVersionModel) _mergeTagMap.get(sourceTag);
			if (sourceNode != null && mergeNode != null)
			{
				createConnection(sourceNode, mergeNode, true);
			}
		}
	}

	private void addEntry(ICvsGraphNodeModel previous, CvsGraphBranchModel currentBranch, ListIterator entries)
	{
//		System.out.println("[CvsGraphModel] adding entry = " + previous.getVersion());
		if (!entries.hasNext())
		{
			return;
		}
		ILogEntry entry = (ILogEntry) entries.next();
		if (isBranchTerminus(currentBranch, entry))
		{
			entries.previous();
			return;
		}
		previous = createVersion(previous, entry);
		List branchTags = processTags(entry, previous);
		if (branchTags != null)
		{
			int versionSuffix = 0;
			Iterator tags = branchTags.iterator();
			while (tags.hasNext())
			{
				versionSuffix += 2;
				String version = previous.getVersion() + "." + versionSuffix;
				addBranch(previous, entries, (String) tags.next(), version);
			}
		}
		addEntry(previous, currentBranch, entries);
	}

	private void addBranch(ICvsGraphNodeModel previous, ListIterator entries, String tag, String version)
	{
		CvsGraphBranchModel branch = createBranch(version, tag);
		previous.addChildNode(branch);
		branch.setParent(previous);

//		System.out.println("[CvsGraphModel] adding branch = " + branch.getVersion());
		
		createConnection(previous, branch);
		addEntry(branch, branch, entries);
	}

	private CvsGraphBranchModel createBranch(String version, String tag)
	{
		CvsGraphBranchModel branch = new CvsGraphBranchModel(version, tag);
		_nodes.add(branch);
		return branch;
	}

	private List processTags(ILogEntry entry, ICvsGraphNodeModel node)
	{
		List branchTags = null;
		CVSTag[] tags = entry.getTags();
		if (tags.length > 0)
		{
			branchTags = new ArrayList(tags.length);
			for (int j = 0; j < tags.length; j++)
			{
				String tag = tags[j].getName();
				String ucTag = tag.toUpperCase();
				if (tags[j].getType() == CVSTag.BRANCH)
				{
					// add in reverse order - earliest to most recent tag
					branchTags.add(0, tag);
				}
				else if (ucTag.startsWith(MERGE_TAG_PREFIX))
				{
					_mergeTagMap.put(tag.substring(MERGE_TAG_PREFIX.length()), node);
				}
				else if (ucTag.startsWith(SOURCE_TAG_PREFIX))
				{
					_sourceTagMap.put(tag.substring(SOURCE_TAG_PREFIX.length()), node);
				}
			}
		}
		return branchTags;
	}

	private CvsGraphVersionModel createVersion(ICvsGraphNodeModel previous, ILogEntry entry)
	{
		CvsGraphVersionModel node = new CvsGraphVersionModel(entry);
		_nodes.add(node);
		previous.addChildNode(node);
		node.setParent(previous);
		
//		System.out.println("[CvsGraphModel] new node = " + node.getVersion());
			
		createConnection(previous, node);
			
//		System.out.println("[CvsGraphModel] connected to previous = " + previous.getVersion());
			
		return node;
	}

	private void createConnection(ICvsGraphNodeModel previous, ICvsGraphNodeModel node)
	{
		createConnection(previous, node, false);
	}

	private void createConnection(ICvsGraphNodeModel src, ICvsGraphNodeModel dest, boolean isMerge)
	{
		// if (isMerge) create different connection model
		CvsGraphConnectionModel connection = new CvsGraphConnectionModel(isMerge);

		connection.setSource(src);
		connection.setTarget(dest);
		
		src.addSourceConnection(connection);
		dest.addTargetConnection(connection);
	}

	private boolean isBranchTerminus(CvsGraphBranchModel currentBranch, ILogEntry next)
	{
		String previousVersion = currentBranch.getVersion();
		String nextVersion = next.getRevision();
		return (! nextVersion.startsWith(previousVersion));
	}

	public List getNodes()
	{
		return _nodes;
	}

	public CvsGraphBranchModel getRootBranch()
	{
		return _rootBranch;
	}

	protected ILogEntry adaptToLogEntry(Object element)
	{
		// Get the log entry for the provided object
		ILogEntry entry = null;
		if (element instanceof ILogEntry)
		{
			entry = (ILogEntry) element;
		}
		else if (element instanceof IAdaptable)
		{
			entry = (ILogEntry) ((IAdaptable) element).getAdapter(ILogEntry.class);
		}
		return entry;
	}

	final class LogEntrySorter implements Comparator
	{
		private VersionCollator versionCollator = new VersionCollator();

		/**
		 * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object)
		 */
		public int compare(Object arg0, Object arg1)
		{
			ILogEntry e1 = adaptToLogEntry(arg0);
			ILogEntry e2 = adaptToLogEntry(arg1);
			
			return versionCollator.compare(e1.getRevision(), e2.getRevision());
		}
	}

}
