package models.deltaAlgebra;

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

/**
 * An open/closed (possibly composed) delta.
 * 
 * @author Nitta
 *
 */
public class Delta {
	private PrimitiveDelta bottomDelta = null;
	private Delta subDelta = null;
	
	public Delta(PrimitiveDelta delta) {
		bottomDelta = delta;
	}
	
	public Delta(PrimitiveDelta bottomDelta, Delta subDelta) {
		this.bottomDelta = bottomDelta;
		this.subDelta = subDelta;
	}
	
	public ObjectNode getCoordinator() {
		if (subDelta == null) return bottomDelta.getCoordinator();
		return subDelta.getCoordinator();
	}

	public List<ReferenceEdge> getPullEdges() {
		if (subDelta == null) return bottomDelta.getPullEdges();
		List<ReferenceEdge> pullEdges = new ArrayList<>(subDelta.getPullEdges());
		pullEdges.addAll(bottomDelta.getPullEdges());
		return pullEdges;
	}

	public List<ReferenceEdge> getPushEdges() {
		if (subDelta == null) return bottomDelta.getPushEdges();
		List<ReferenceEdge> pushEdges = new ArrayList<>(subDelta.getPushEdges());
		pushEdges.addAll(bottomDelta.getPushEdges());
		return pushEdges;
	}
	
	/**
	 * binding composition
	 * (this o delta)
	 * 
	 * @param delta
	 * @return this o delta
	 */
	public Delta combine(PrimitiveDelta delta) {
		return new Delta(delta, this);
	}
	
	/**
	 * binding composition
	 * (this o delta)
	 * 
	 * @param delta
	 * @return this o delta
	 */
	public Delta combine(Delta delta) {
		Delta preDelta = this.copy();
		for (PrimitiveDelta atomicDelta: delta.split()) {
			preDelta = preDelta.combine(atomicDelta);
		}
		return preDelta;
	}
	
	/**
	 * if this = d1 o d2 o ... o dn
	 * 
	 * @return [d1, d2, ..., dn]
	 */
	public List<PrimitiveDelta> split() {
		if (subDelta == null) {
			List<PrimitiveDelta> atomicDeltaSequence = new ArrayList<>();
			atomicDeltaSequence.add(bottomDelta);
			return atomicDeltaSequence;
		}
		List<PrimitiveDelta> atomicDeltaSequence = subDelta.split();
		atomicDeltaSequence.add(bottomDelta);
		return atomicDeltaSequence;
	}

	public Delta copy() {
		if (subDelta == null) {
			return new Delta(bottomDelta.copy());
		}
		return new Delta(bottomDelta.copy(), subDelta.copy());
	}

	@Override
	public boolean equals(Object o) {
		if (this == o) return true;
		if (o == null || !(o instanceof Delta)) return false;
		Delta another = (Delta) o;
		return bottomDelta.equals(another.bottomDelta) && ((subDelta == null && another.subDelta == null) || subDelta.equals(another.subDelta));
	}

	@Override
	public int hashCode() {
		return bottomDelta.hashCode() + (subDelta == null ? 0 : subDelta.hashCode());
	}

	public String toString() {
		return bottomDelta.toString() + (subDelta == null ? "" : " o " + subDelta.toString());
	}
}
