package models.dataSynchronizationModel;

import designPatternExtensions.DependencyInversion;
import designPatternExtensions.MediatorInsertion;

import designPatternExtensions.PresenterInsertion;
import models.deltaAlgebra.*;
import parser.*;
import parser.exceptions.ExpectedJsonValue;

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

/**
 * The final data synchronization design containing delta algebra and dependency injection.
 *
 * @author Nitta
 *
 */
public class DataSynchronizationDesign {
	private List<DataSynchronizationContext> synchronizationContexts;
	private DeltaComplex deltaComplex;
	private List<DependencyInversion> dependencyInversions = new ArrayList<>();
	private List<MediatorInsertion> mediatorInsertions = new ArrayList<>();
	private List<PresenterInsertion> presenterInsertions = new ArrayList<>();

	public static String toComponentName(String name) {
		return name.substring(0, 1).toUpperCase() + name.substring(1);
	}

	public static String toVariableName(String name) {
		return name.substring(0, 1).toLowerCase() + name.substring(1);
	}

	public DataSynchronizationDesign(DataSynchronizationContext synchronizationContexts) throws IllegalRelationException {
		this.synchronizationContexts = new ArrayList<>();
		this.synchronizationContexts.add(synchronizationContexts);
		this.deltaComplex = createDefaultDeltaComplex(synchronizationContexts);
	}

    public DataSynchronizationDesign(JsonValue jsonValue) throws IllegalRelationException {
        this.synchronizationContexts = new ArrayList<>();
        for (StringValue keyValue : jsonValue.getJson().keySet()) {
            String key = keyValue.getStringValue();
            if (key.equals("contexts")) {
                ArrayValue arrayValue = (ArrayValue) jsonValue.getValue(keyValue);
                setSynchronizationContexts(arrayValue);
            }
        }
    }

	public void addDependencyInversion(DependencyInversion dependencyInversion) {
		dependencyInversions.add(dependencyInversion);
	}

	public List<DependencyInversion> getDependencyInversions() {
		return dependencyInversions;
	}

	public void addMediatorInsertion(MediatorInsertion mediatorInsertion) {
		mediatorInsertions.add(mediatorInsertion);
	}

	public List<MediatorInsertion> getMediatorInsertions() {
		return mediatorInsertions;
	}

	public void addPresenterInsertion(PresenterInsertion presenterInsertion) {
		presenterInsertions.add(presenterInsertion);
	}

	public List<PresenterInsertion> getPresenterInsertions() {
		return presenterInsertions;
	}

	public DeltaComplex createDefaultDeltaComplex(DataSynchronizationContext transferContext) throws IllegalRelationException {
		Stack<Delta> deltaStack = constructDefaultDeltas(transferContext);
		// Add all deltas.
		DeltaComplex deltaComplex = null;
		while (!deltaStack.isEmpty()) {
			if (deltaComplex == null) {
				deltaComplex = new DeltaComplex(deltaStack.pop());
			} else {
				deltaComplex.attach(deltaStack.pop());
			}
		}
		return deltaComplex;
	}

	/*
	 * construct default deltas that satisfy the law of demeter.
	 *
	 * @param synchronizationContext a synchronization context in data synchronization model
	 * @return generated deltas
	 */
	private Stack<Delta> constructDefaultDeltas(DataSynchronizationContext synchronizationContext) throws IllegalRelationException {
		Stack<Delta> deltaStack = new Stack<>();
		PushPullValue transferStyle = synchronizationContext.getTransferStyle();
		ObjectNode srcObj = null;
		ObjectNode dstObj = null;
		if (transferStyle == PushPullValue.PUSH) {
			srcObj = synchronizationContext.getSrcTopObject();
			dstObj = synchronizationContext.getDstTopObject();
		} else {
			srcObj = synchronizationContext.getDstTopObject();
			dstObj = synchronizationContext.getSrcTopObject();
		}
		String refName = toVariableName(dstObj.getName());
		ReferenceEdge prevReference = new ReferenceEdge(srcObj, dstObj, refName);
		boolean hasDiagonalRelation = false;
		if (!synchronizationContext.isFlat()) {
			boolean toOne = true;	// identifiable multiplicity
			List<Relation> allRelations = new ArrayList<>(synchronizationContext.getIntermediateRelations());
			for (Relation relation: allRelations) {
				if (synchronizationContext.getSrcObjectHierarchy().isPrefixOf(relation.getSrcObjectHierarchy())
						|| synchronizationContext.getDstObjectHierarchy().isPrefixOf(relation.getDstObjectHierarchy())) {
					hasDiagonalRelation = true;
					break;
				}
			}
			if (!hasDiagonalRelation) {
				allRelations.add(synchronizationContext.getSyncronizationRelation().getPrevRelation());
			} else {
				Relation lastRelation = allRelations.getLast();
				Relation bottomRelation = null;
				if (!synchronizationContext.getSrcObjectHierarchy().isPrefixOf(lastRelation.getSrcObjectHierarchy())) {
					bottomRelation = new Relation(synchronizationContext.getSrcObjectHierarchy().getPrevObjectHierarchy(),
							synchronizationContext.getSrcObjectHierarchy(),
							synchronizationContext.getTransferMultiplicity());
					allRelations.add(bottomRelation);
				} else if (!synchronizationContext.getDstObjectHierarchy().isPrefixOf(lastRelation.getDstObjectHierarchy())) {
					bottomRelation = new Relation(synchronizationContext.getSrcObjectHierarchy(),
							synchronizationContext.getDstObjectHierarchy().getPrevObjectHierarchy(),
							synchronizationContext.getTransferMultiplicity());
					allRelations.add(bottomRelation);
				}
			}
			ObjectHierarchy srcHierarchy = null;
			ObjectHierarchy dstHierarchy = null;
			for (Relation relation: allRelations) {
				ObjectHierarchy newSrcHierarchy = null;
				ObjectHierarchy newDstHierarchy = null;
				if (transferStyle == PushPullValue.PUSH) {
					newSrcHierarchy = relation.getSrcObjectHierarchy();
					newDstHierarchy = relation.getDstObjectHierarchy();
				} else {
					newSrcHierarchy = relation.getDstObjectHierarchy();
					newDstHierarchy = relation.getSrcObjectHierarchy();
				}
				if (srcHierarchy == null) {
					srcHierarchy = new ObjectHierarchy(newSrcHierarchy.getTop());
				}
				if (dstHierarchy == null) {
					dstHierarchy = new ObjectHierarchy(newDstHierarchy.getTop());
				}
				while (dstHierarchy.getDepth() < newDstHierarchy.getDepth() || srcHierarchy.getDepth() < newSrcHierarchy.getDepth()) {
					List<ReferenceEdge> oneDestinations = new ArrayList<>();
					List<ReferenceEdge> manyDestinations = new ArrayList<>();
					boolean tmpToOne = toOne;
					for (int i = 0; i < newDstHierarchy.getDepth(); i++) {
						ReferenceEdge dstEdge = newDstHierarchy.getEdge(i);
						if (dstHierarchy != null && i < dstHierarchy.getDepth()) {
							if (!dstHierarchy.getEdge(i).equals(dstEdge)) {
								throw new IllegalRelationException();
							}
						} else {
							dstHierarchy.addEdge(dstEdge);
							if (dstEdge.toMany()) {
								tmpToOne = false;
								toOne = false;
							}
							if (!tmpToOne &&
									(transferStyle == PushPullValue.PUSH && relation.toOne())
									|| (transferStyle != PushPullValue.PUSH && (relation.oneTo()))) {
								// If the specified multiplicity of destination objects does not match to their identifiable multiplicity,
								// then the reference to select one destination object from identifiable multiple objects is needed.
								manyDestinations.add(dstEdge);
								toOne = true;
							} else {
								// If the specified multiplicity of destination objects and their identifiable are both many or both one,
								// then the reference to select one destination object from identifiable multiple objects is not needed.
								oneDestinations.add(dstEdge);
							}
						}
					}

					// 1) Create component obtaining deltas.
					Delta delta = null;
					ReferenceEdge prevDstEdge = null;
					for (int i = oneDestinations.size() - 1; i >= 0; i--) {
						ReferenceEdge dstEdge = oneDestinations.get(i);
						if (prevDstEdge != null) {
							srcObj = (ObjectNode) dstEdge.getSource();
							dstObj = (ObjectNode) prevDstEdge.getDestination();
							String dataName = toVariableName(prevDstEdge.getName());
							if (!newSrcHierarchy.contains((ObjectNode) dstEdge.getDestination())) {
								PrimitiveDelta primDelta = null;
								if (!prevDstEdge.toMany()) {
									primDelta = new PrimitivePullDelta(dstEdge, prevDstEdge, dataName);    // a primitive component obtaining delta
								} else {
									primDelta = new PrimitivePullDelta(dstEdge, prevDstEdge, dataName, true, false);    // a primitive component obtaining delta
								}
								if (delta == null) {
									delta = new Delta(primDelta);
								} else {
									delta = delta.combine(primDelta);
								}
								if (!prevDstEdge.toMany()) {
									prevDstEdge = new ReferenceEdge(srcObj, dstObj, dataName);
								} else {
									prevDstEdge = new ReferenceEdge(srcObj, dstObj, dataName, prevDstEdge.getKeyTypeName());
								}
							} else {
								prevReference = new ReferenceEdge((ObjectNode) dstEdge.getDestination(), (ObjectNode) dstEdge.getDestination(), dataName);
								break;
							}
						} else {
							prevDstEdge = dstEdge;
						}
					}
					if (delta != null) deltaStack.push(delta);			// add a component obtaining SIMPLEX delta
					if (prevDstEdge != null) {
						if (!prevReference.isIdentical()) {
							srcObj = (ObjectNode) prevReference.getSource();
							dstObj = (ObjectNode) prevDstEdge.getDestination();
							String dataName = toVariableName(prevDstEdge.getName());
							delta = new Delta(new PrimitivePullDelta(prevReference, prevDstEdge, dataName, prevDstEdge.toMany()));
							if (delta != null)
								deltaStack.push(delta);            // add a component obtaining SIMPLEX delta
							if (!prevDstEdge.toMany()) {
								prevReference = new ReferenceEdge(srcObj, dstObj, dataName);
							} else {
								prevReference = new ReferenceEdge(srcObj, dstObj, dataName, prevDstEdge.getKeyTypeName());
							}
						} else {
							prevReference = prevDstEdge;
						}
					}

					// 2) Create container passing deltas.
					delta = null;
					boolean oneTo = true;
					for (int i = 0; i < newSrcHierarchy.getDepth(); i++) {
						ReferenceEdge srcEdge = newSrcHierarchy.getEdge(i);
						if (srcHierarchy != null && i < srcHierarchy.getDepth()) {
							if (!srcHierarchy.getEdge(i).equals(srcEdge)) {
								throw new IllegalRelationException();
							}
						} else {
							srcHierarchy.addEdge(srcEdge);
							if (srcEdge.toMany()) {
								oneTo = false;
							}
							srcObj = (ObjectNode) srcEdge.getDestination();
							dstObj = (ObjectNode) prevReference.getDestination();
							String dataName = toVariableName(prevReference.getName());
							if (!prevReference.isIdentical() && !dstHierarchy.contains(srcObj)) {
								PrimitiveDelta primDelta = null;
								if (toOne) {
									primDelta = new PrimitivePullPushDelta(prevReference, srcEdge, dataName);	// a container passing primitive delta
								} else {
									primDelta = new PrimitivePullPushDelta(prevReference, srcEdge, dataName, true);	// a container passing primitive delta
								}
								if (delta == null) {
									delta = new Delta(primDelta);
								} else {
									if (oneTo) {
										delta = delta.combine(primDelta);
									} else {
										if (delta != null) {
											deltaStack.push(delta);      // add a container passing SIMPLEX delta
										}
										delta = new Delta(primDelta);
									}
								}
								if (toOne) {
									prevReference = new ReferenceEdge(srcObj, dstObj, dataName);
								} else {
									prevReference = new ReferenceEdge(srcObj, dstObj, dataName, prevReference.getKeyTypeName());
								}
							}
						}
					}
					if (delta != null) deltaStack.push(delta);            // add a container passing SIMPLEX delta

					// 3) Create a component selecting delta.
					for (ReferenceEdge dstEdge : manyDestinations) {
						srcObj = (ObjectNode) prevReference.getSource();
						dstObj = (ObjectNode) dstEdge.getDestination();
						String dataName = toVariableName(dstEdge.getName());
						PrimitivePullDelta componentSelectingDelta = null;
						componentSelectingDelta = new PrimitivePullDelta(prevReference, dstEdge, dataName);
						delta = new Delta(componentSelectingDelta);
						deltaStack.push(delta);            // add a component selecting delta
						prevReference = new ReferenceEdge(srcObj, dstObj, dataName);
					}
				}
			}
		}
		if (!hasDiagonalRelation) {
			// 4) Create the bottom (data transfer) delta.
			Delta dataTransferDelta = null;
			if (transferStyle == PushPullValue.PUSH) {
				// PUSH transfer
				if (synchronizationContext.isTransferToOne()) {
					// Simple PUSH

					// Add the data transfer delta.
					srcObj = (ObjectNode) prevReference.getSource();
					dstObj = (ObjectNode) synchronizationContext.getSrcObjectHierarchy().getBottom();
					String dataName = synchronizationContext.getDataName();
					ReferenceEdge dataEdge = new ReferenceEdge(srcObj, dstObj, dataName);
					PrimitivePullPushDelta primDelta = new PrimitivePullPushDelta(dataEdge, prevReference, dataName);        // PUSH data transfer
					if (synchronizationContext.getSyncronizationRelation().toMany()) {
						primDelta.setMultipleTransfer(true);
					}
					dataTransferDelta = new Delta(primDelta);
				} else {
					// Broadcasting
					prevReference = new ReferenceEdge(prevReference);
					prevReference.setKeyTypeName(synchronizationContext.getDstObjectHierarchy().getEdges().getLast().getKeyTypeName());
//					prevReference.setMultiplicity(MultiplicityValue.OneToMany);

					// Add the data transfer delta.
					srcObj = (ObjectNode) prevReference.getSource();
					dstObj = (ObjectNode) synchronizationContext.getSrcObjectHierarchy().getBottom();
					String dataName = synchronizationContext.getDataName();
					ReferenceEdge dataEdge = new ReferenceEdge(srcObj, dstObj, dataName);
					PrimitivePullPushDelta primDelta = new PrimitivePullPushDelta(dataEdge, prevReference, dataName);		// PUSH data transfer
					if (synchronizationContext.getDstObjectHierarchy().getEdges().getLast().toMany()) {
						primDelta.setMultipleDefinition(true);
					}
					if (synchronizationContext.getSyncronizationRelation().toMany()) {
						primDelta.setMultipleTransfer(true);
					}
					dataTransferDelta = new Delta(primDelta);
				}
			} else {
				if (synchronizationContext.isTransferOneTo()) {
					// Simple PULL

					// Add the data transfer delta.
					srcObj = (ObjectNode) prevReference.getDestination();
					dstObj = (ObjectNode) synchronizationContext.getSrcObjectHierarchy().getBottom();
					String dataName = synchronizationContext.getDataName();
					ReferenceEdge dataEdge = new ReferenceEdge(srcObj, dstObj, dataName);
					PrimitivePullDelta primDelta = new PrimitivePullDelta(prevReference, dataEdge, dataName);		// PULL data transfer
					if (synchronizationContext.getSyncronizationRelation().manyTo()) {
						primDelta.setMultipleTransfer(true);
					}
					dataTransferDelta = new Delta(primDelta);
				} else {
					// Data collecting
					prevReference = new ReferenceEdge(prevReference);
					prevReference.setKeyTypeName(synchronizationContext.getSrcObjectHierarchy().getEdges().getLast().getKeyTypeName());
//					prevReference.setMultiplicity(MultiplicityValue.OneToMany);

					// Add the data transfer delta.
					srcObj = (ObjectNode) prevReference.getDestination();
					dstObj = (ObjectNode) synchronizationContext.getSrcObjectHierarchy().getBottom();
					String dataName = synchronizationContext.getDataName();
					ReferenceEdge dataEdge = new ReferenceEdge(srcObj, dstObj, dataName);
					PrimitivePullDelta primDelta = new PrimitivePullDelta(prevReference, dataEdge, dataName, true);		// PULL data transfer
					if (synchronizationContext.getSyncronizationRelation().manyTo()) {
						primDelta.setMultipleTransfer(true);
					}
					dataTransferDelta = new Delta(primDelta);
				}
			}
			deltaStack.push(dataTransferDelta);
		}
		return deltaStack;
	}

	/*
	 * construct default deltas that do not satisfy the law of demeter.
	 *
	 * @param synchronizationContext a synchronization context in data synchronization model
	 * @return generated deltas
	 */
	private Stack<Delta> constructDefaultDeltas2(DataSynchronizationContext synchronizationContext) throws IllegalRelationException {
		Stack<Delta> deltaStack = new Stack<>();
		PushPullValue transferStyle = synchronizationContext.getTransferStyle();
		ObjectNode srcObj = null;
		ObjectNode dstObj = null;
		if (transferStyle == PushPullValue.PUSH) {
			srcObj = synchronizationContext.getSrcTopObject();
			dstObj = synchronizationContext.getDstTopObject();
		} else {
			srcObj = synchronizationContext.getDstTopObject();
			dstObj = synchronizationContext.getSrcTopObject();
		}
		String refName = toVariableName(dstObj.getName());
		ReferenceEdge prevReference = new ReferenceEdge(srcObj, dstObj, refName);
		boolean hasDiagonalRelation = false;
		if (!synchronizationContext.isFlat()) {
			boolean toOne = true;	// identifiable multiplicity
			ObjectHierarchy srcHierarchy = null;
			ObjectHierarchy dstHierarchy = null;
			List<Relation> allRelations = new ArrayList<>(synchronizationContext.getIntermediateRelations());
			for (Relation relation: allRelations) {
				if (synchronizationContext.getSrcObjectHierarchy().isPrefixOf(relation.getSrcObjectHierarchy())
						|| synchronizationContext.getDstObjectHierarchy().isPrefixOf(relation.getDstObjectHierarchy())) {
					hasDiagonalRelation = true;
					break;
				}
			}
			if (!hasDiagonalRelation) {
				allRelations.add(synchronizationContext.getSyncronizationRelation().getPrevRelation());
			} else {
				Relation lastRelation = allRelations.getLast();
				Relation bottomRelation = null;
				if (!synchronizationContext.getSrcObjectHierarchy().isPrefixOf(lastRelation.getSrcObjectHierarchy())) {
					bottomRelation = new Relation(synchronizationContext.getSrcObjectHierarchy().getPrevObjectHierarchy(),
													synchronizationContext.getSrcObjectHierarchy(),
													synchronizationContext.getTransferMultiplicity());
					allRelations.add(bottomRelation);
				} else if (!synchronizationContext.getDstObjectHierarchy().isPrefixOf(lastRelation.getDstObjectHierarchy())) {
					bottomRelation = new Relation(synchronizationContext.getSrcObjectHierarchy(),
													synchronizationContext.getDstObjectHierarchy().getPrevObjectHierarchy(),
													synchronizationContext.getTransferMultiplicity());
					allRelations.add(bottomRelation);
				}
			}
			for (Relation relation: allRelations) {
				ObjectHierarchy newSrcHierarchy = null;
				ObjectHierarchy newDstHierarchy = null;
				if (transferStyle == PushPullValue.PUSH) {
					newSrcHierarchy = relation.getSrcObjectHierarchy();
					newDstHierarchy = relation.getDstObjectHierarchy();
				} else {
					newSrcHierarchy = relation.getDstObjectHierarchy();
					newDstHierarchy = relation.getSrcObjectHierarchy();
				}
				if ((dstHierarchy == null || dstHierarchy.getDepth() < newDstHierarchy.getDepth()) || (srcHierarchy == null || srcHierarchy.getDepth() < newSrcHierarchy.getDepth())) {
					List<ReferenceEdge> oneDestinations = new ArrayList<>();
					List<ReferenceEdge> manyDestinations = new ArrayList<>();
					boolean nextToOne = toOne;
					for (int i = 0; i < newDstHierarchy.getDepth(); i++) {
						ReferenceEdge dstEdge = newDstHierarchy.getEdge(i);
						if (dstHierarchy != null && i < dstHierarchy.getDepth()) {
							if (!dstHierarchy.getEdge(i).equals(dstEdge)) {
								throw new IllegalRelationException();
							}
						} else {
							if (dstEdge.toMany()) {
								nextToOne = false;
							}
							if (nextToOne) {
								oneDestinations.add(dstEdge);
							} else {
								manyDestinations.add(dstEdge);
							}
						}
					}

					// 1) Create container passing simplex deltas.
					List<PrimitiveDelta> primitiveDeltaList = new ArrayList<>();
					for (ReferenceEdge dstEdge: oneDestinations) {
						srcObj = (ObjectNode) prevReference.getSource();
						dstObj = (ObjectNode) dstEdge.getDestination();
						String dataName = toVariableName(dstObj.getName());
						if (!prevReference.isIdentical()) {
							primitiveDeltaList.add(new PrimitivePullDelta(prevReference, dstEdge, dataName));	// a component obtaining primitive delta
						}
						prevReference = new ReferenceEdge(srcObj, dstObj, dataName);
					}
					for (int i = 0; i < newSrcHierarchy.getDepth(); i++) {
						ReferenceEdge srcEdge = newSrcHierarchy.getEdge(i);
						if (srcHierarchy != null && i < srcHierarchy.getDepth()) {
							if (!srcHierarchy.getEdge(i).equals(srcEdge)) {
								throw new IllegalRelationException();
							}
						} else {
							srcObj = (ObjectNode) srcEdge.getDestination();
							dstObj = (ObjectNode) prevReference.getDestination();
							String dataName = toVariableName(dstObj.getName());
							if (!prevReference.isIdentical() && !prevReference.getDestination().equals(srcEdge.getDestination())) {
								if (toOne) {
									primitiveDeltaList.add(new PrimitivePullPushDelta(prevReference, srcEdge, dataName));	// a container passing primitive delta
								} else {
									primitiveDeltaList.add(new PrimitivePullPushDelta(prevReference, srcEdge, dataName, true));	// a container passing primitive delta
								}
								Delta delta = null;
								for (PrimitiveDelta primDelta : primitiveDeltaList) {
									if (delta == null) {
										delta = new Delta(primDelta);
									} else {
										delta = new Delta(primDelta, delta);
									}
								}
								deltaStack.push(delta);            // add a container passing SIMPLEX delta
								primitiveDeltaList.clear();
							}
							prevReference = new ReferenceEdge(srcObj, dstObj, dataName);
						}
					}
					toOne = nextToOne;

					if (!toOne) {
						boolean multipleDefinition = false;		// destination multiplicity of the defined reference
						if	((transferStyle == PushPullValue.PUSH && relation.toOne()) || (transferStyle != PushPullValue.PUSH && relation.oneTo())) {
							// If the specified multiplicity of destination objects does not match to their identifiable multiplicity,
							// then the reference to select one destination object from identifiable multiple objects is needed.
							toOne = true;
						} else {
							// If the specified multiplicity of destination objects and their identifiable multiplicity are both many,
							// then the reference to select one destination object from identifiable multiple objects is needed.
							multipleDefinition = true;
						}
						// 2) Create a component selecting simplex delta.
						PrimitivePullDelta componentSelectingDelta = null;
						for (ReferenceEdge dstEdge : manyDestinations) {
							if (componentSelectingDelta == null) {
								srcObj = (ObjectNode) prevReference.getSource();
								dstObj = (ObjectNode) dstEdge.getDestination();
								String dataName = toVariableName(dstObj.getName());
								componentSelectingDelta = new PrimitivePullDelta(prevReference, dstEdge, dataName, multipleDefinition);
								primitiveDeltaList.add(componentSelectingDelta);
								Delta delta = null;
								for (PrimitiveDelta primDelta : primitiveDeltaList) {
									if (delta == null) {
										delta = new Delta(primDelta);
									} else {
										delta = new Delta(primDelta, delta);
									}
								}
								deltaStack.push(delta);            // add a component selecting SIMPLEX delta
								primitiveDeltaList.clear();
								if (!multipleDefinition) {
									prevReference = new ReferenceEdge(srcObj, dstObj, dataName);
								} else {
									prevReference = new ReferenceEdge(srcObj, dstObj, dataName, dstEdge.getKeyTypeName());
								}
							} else {
								srcObj = (ObjectNode) prevReference.getSource();
								dstObj = (ObjectNode) dstEdge.getDestination();
								String dataName = toVariableName(dstObj.getName());
								componentSelectingDelta = new PrimitivePullDelta(prevReference, dstEdge, dataName, multipleDefinition);	// a component obtaining primitive delta
								primitiveDeltaList.add(componentSelectingDelta);
								if (!multipleDefinition) {
									prevReference = new ReferenceEdge(srcObj, dstObj, dataName);
								} else {
									prevReference = new ReferenceEdge(srcObj, dstObj, dataName, dstEdge.getKeyTypeName());
								}
							}
						}
					}
					// 3) Create a component obtaining simplex delta.
					if (primitiveDeltaList.size() > 0) {
						Delta delta = null;
						for (PrimitiveDelta primDelta: primitiveDeltaList) {
							if (delta == null) {
								delta = new Delta(primDelta);
							} else {
								delta = new Delta(primDelta, delta);
							}
						}
						deltaStack.push(delta);			// add a component obtaining SIMPLEX delta
					}
				} else {
					throw new IllegalRelationException();
				}
				srcHierarchy = newSrcHierarchy;
				dstHierarchy = newDstHierarchy;
			}
		}
		if (!hasDiagonalRelation) {
			// 4) Create the bottom (data transfer) delta.
			Delta dataTransferDelta = null;
			if (transferStyle == PushPullValue.PUSH) {
				// PUSH transfer
				if (synchronizationContext.isTransferToOne()) {
					// Simple PUSH

					// Add the data transfer delta.
					srcObj = (ObjectNode) prevReference.getSource();
					dstObj = (ObjectNode) synchronizationContext.getSrcObjectHierarchy().getBottom();
					String dataName = synchronizationContext.getDataName();
					ReferenceEdge dataEdge = new ReferenceEdge(srcObj, dstObj, dataName);
					PrimitivePullPushDelta primDelta = new PrimitivePullPushDelta(dataEdge, prevReference, dataName);        // PUSH data transfer
					if (synchronizationContext.getSyncronizationRelation().toMany()) {
						primDelta.setMultipleTransfer(true);
					}
					dataTransferDelta = new Delta(primDelta);
				} else {
					// Broadcasting
					prevReference = new ReferenceEdge(prevReference);
					prevReference.setKeyTypeName(synchronizationContext.getDstObjectHierarchy().getEdges().getLast().getKeyTypeName());
//					prevReference.setMultiplicity(MultiplicityValue.OneToMany);

					// Add the data transfer delta.
					srcObj = (ObjectNode) prevReference.getSource();
					dstObj = (ObjectNode) synchronizationContext.getSrcObjectHierarchy().getBottom();
					String dataName = synchronizationContext.getDataName();
					ReferenceEdge dataEdge = new ReferenceEdge(srcObj, dstObj, dataName);
					PrimitivePullPushDelta primDelta = new PrimitivePullPushDelta(dataEdge, prevReference, dataName);		// PUSH data transfer
					if (synchronizationContext.getDstObjectHierarchy().getEdges().getLast().toMany()) {
						primDelta.setMultipleDefinition(true);
					}
					if (synchronizationContext.getSyncronizationRelation().toMany()) {
						primDelta.setMultipleTransfer(true);
					}
					dataTransferDelta = new Delta(primDelta);
				}
			} else {
				if (synchronizationContext.isTransferOneTo()) {
					// Simple PULL

					// Add the data transfer delta.
					srcObj = (ObjectNode) prevReference.getDestination();
					dstObj = (ObjectNode) synchronizationContext.getSrcObjectHierarchy().getBottom();
					String dataName = synchronizationContext.getDataName();
					ReferenceEdge dataEdge = new ReferenceEdge(srcObj, dstObj, dataName);
					PrimitivePullDelta primDelta = new PrimitivePullDelta(prevReference, dataEdge, dataName);		// PULL data transfer
					if (synchronizationContext.getSyncronizationRelation().manyTo()) {
						primDelta.setMultipleTransfer(true);
					}
					dataTransferDelta = new Delta(primDelta);
				} else {
					// Data collecting
					prevReference = new ReferenceEdge(prevReference);
					prevReference.setKeyTypeName(synchronizationContext.getSrcObjectHierarchy().getEdges().getLast().getKeyTypeName());
//					prevReference.setMultiplicity(MultiplicityValue.OneToMany);

					// Add the data transfer delta.
					srcObj = (ObjectNode) prevReference.getDestination();
					dstObj = (ObjectNode) synchronizationContext.getSrcObjectHierarchy().getBottom();
					String dataName = synchronizationContext.getDataName();
					ReferenceEdge dataEdge = new ReferenceEdge(srcObj, dstObj, dataName);
					PrimitivePullDelta primDelta = new PrimitivePullDelta(prevReference, dataEdge, dataName, true);		// PULL data transfer
					if (synchronizationContext.getSyncronizationRelation().manyTo()) {
						primDelta.setMultipleTransfer(true);
					}
					dataTransferDelta = new Delta(primDelta);
				}
			}
			deltaStack.push(dataTransferDelta);
		}
		return deltaStack;
	}

    private void setSynchronizationContexts(ArrayValue arrayValue) throws IllegalRelationException {
        List<Value> values = arrayValue.getValues();
        for (int i = 0; i<values.size(); i++) {
            if (!(values.get(i) instanceof JsonValue)) {
                throw new ExpectedJsonValue("contextsの中身がJsonではありません。");
            }
            if (i == 0) {
                DataSynchronizationContext transferContext = new DataSynchronizationContext(values.get(i));
                this.synchronizationContexts.add(transferContext);
                this.deltaComplex = createDefaultDeltaComplex(transferContext);
            } else {
                DataSynchronizationContext transferContext = new DataSynchronizationContext(values.get(i));
                addTransferContext(transferContext);
            }
        }
    }

	public DeltaComplex getDeltaComplex() {
		return deltaComplex;
	}

	public void addTransferContext(DataSynchronizationContext transferContext) throws IllegalRelationException {
		synchronizationContexts.add(transferContext);
		deltaComplex.merge(createDefaultDeltaComplex(transferContext));
	}

	public List<DataSynchronizationContext> getSynchronizationContexts() {
		return synchronizationContexts;
	}
}
