/* 
 * Copyright 2009, Timo Baumann and the Inpro project
 * 
    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.

 */
package org.cocolab.inpro.incremental.unit;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.cocolab.inpro.incremental.BaseDataKeeper;

public abstract class IU {
	
	public static final IU FIRST_IU = new IU() {}; 
	private static int IU_idCounter = 0;
	protected final int id;

	protected IU sameLevelLink;
	
	protected List<IU> groundedIn;
	protected List<IU> grounds; 
	
	protected static BaseDataKeeper bd = null;
	
	/**
	 * call this, if you want to provide a sameLevelLink and a groundedIn list
	 * and you want groundedIn to be deeply SLLed to the sameLevelLink's groundedIn-IUs  
	 */
	public IU(IU sll, List<IU> groundedIn, boolean deepSLL) {
		this.id = IU.getNewID();
		this.groundedIn = groundedIn;
		if (deepSLL && (sll != null)) {
			connectSLL(sll);
		} else {
			this.sameLevelLink = sll;
		}
		
	}
	
	/**
	 * call this, if you want to provide both a sameLevelLink and a groundedIn list
	 */
	public IU(IU sll, List<IU> groundedIn) {
		this(sll, groundedIn, false);
	}
	
	public IU(List<IU> groundedIn) {
		this((IU) null, groundedIn);
	}
	
	/**
	 * call this, if you want to provide a sameLevelLink 
	 */
	public IU(IU sll) {
		this(sll, null);
	}
	
	/**
	 * this constructor must be called in order to acquire an IU with a valid ID. 
	 */
	public IU() {
		this.id = IU.getNewID();
	}
	
	/**
	 * called to acquire a new ID
	 * @return a process-unique ID.
	 */
	private static synchronized int getNewID() {
		return IU_idCounter++;
	}
	
	public static void setBaseData(BaseDataKeeper bd) {
		assert (IU.bd == null) : "You're trying to re-set basedata. This may be a bug.";
		IU.bd = bd;
	}

	public void setSameLevelLink(IU link) {
		if (sameLevelLink != null) {
			throw new RuntimeException("SLL may not be changed");
		} else {
			sameLevelLink = link;
		}
	}
	
	public IU getSameLevelLink() {
		return sameLevelLink;
	}
	
	public void connectSLL(IU link) {
		setSameLevelLink(link);
		if (groundedIn != null) {
			IU firstGrounding = groundedIn.get(0);
			IU prevLast;
			if (link.groundedIn != null) {
				prevLast = link.groundedIn.get(link.groundedIn.size());
				if (prevLast.getClass() != firstGrounding.getClass()) {
					throw new RuntimeException("I can only connect IUs of identical types!");
				}
			} else {
				prevLast = FIRST_IU;
			}
			firstGrounding.connectSLL(prevLast);
		}
	}
	

	
	/**
	 * return the start of the timespan this IU covers 
	 * @return NaN if time is unavailable, a time (in seconds) otherwise
	 */
	public double startTime() {
		if ((groundedIn != null) && (groundedIn.size() > 0)) {
			return groundedIn.get(0).startTime();
		} else {
			return Double.NaN;
		}
	}
	
	/**
	 * return the end of the timespan this IU covers 
	 * @return NaN if time is unavailable, a time (in seconds) otherwise
	 */
	public double endTime() {
		if ((groundedIn != null) && (groundedIn.size() > 0)) {
			return groundedIn.get(groundedIn.size() - 1).endTime();
		} else {
			return Double.NaN;
		}
	}
		
	@SuppressWarnings("unchecked")
	public List<? extends IU> groundedIn() {
		return groundedIn != null ? groundedIn : Collections.EMPTY_LIST;
	}

	public boolean equals(IU iu) {
		return (this.id == iu.id); 
	}
	
	/**
	 * this is used to notify an IU that it's status has changed
	 * for example, in the abstract model, an IU might want to notify
	 * the grounded-in IUs, that it is now commited, and hence the
	 * grounded-in IUs have to become commited, too 
	 * 
	 * by convention, the ADD EditType does not result in an update, 
	 * as adding an IU should coincide with the IU's construction
	 * 
	 * also, we don't notify on REVOKE (this is done by Java's Garbage
	 * Collector and can be accessed through the finalize() method), 
	 * leaving (for now) only the COMMIT EditType to actually result
	 * in an update. in future, more edits (such as GROUNDED_IN_UPDATED,
	 * ASSERTnn and so on) will result in calls to this method
	 * 
	 * @param edit
	 */
	public void update(EditType edit) {
		switch (edit) {
			case COMMIT: // commit what you're grounding
				for (IU iu : groundedIn()) {
					iu.update(edit);
				}
				break;
			case REVOKE: // revoke what is grounded in you
				for (IU iu : grounds) {
					iu.update(edit);
				}
				break;
			case ADD: // nothing to do, whoever adds us should set us up correctly
		}
	}
	
	public void ground(IU iu) {
		if (grounds == null) {
			// we typically ground just 1 IU, so there's no need for an initial capacity of 10
			grounds = new ArrayList<IU>(1);
		}
		grounds.add((IU) iu);
		if (!iu.groundedIn.contains(this))
			iu.groundedIn.add(this);
	}
	
	public String toString() {
		return Integer.toString(id);
	}
	
	public String deepToString() {
		StringBuilder sb = new StringBuilder("[IU of type ");
		sb.append(this.getClass());
		sb.append(" with content ");
		sb.append(this.toString());
		sb.append("\n  SLL: ");
		if (sameLevelLink != null) {
			sb.append(sameLevelLink.toString());
		} else {
			sb.append("none");
		}
		sb.append("\n  grounded in:\n  [");
		if (groundedIn != null) {
			for (IU iu : groundedIn) {
				sb.append(iu.deepToString());
				sb.append("  ");
			}
		} else {
			sb.append("none");
		}
		sb.append("]\n]\n");
		return sb.toString();
 	}
	
	public String toOAAString() {
		return null;
	}

	public String toTEDviewXML() {
		return null;
	}
	
}

@@ -5209,16 +5209,47 @@
 1);%0A%09%09%7D%0A
+%09%09if (!grounds.contains(iu)) %0A%09
 %09%09ground

check that grounding IUs are not added more than once