package simulator;

import java.util.Map;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

import models.algebra.Constant;
import models.algebra.Expression;
import models.dataConstraintModel.Selector;
import models.dataFlowModel.DataTransferChannel;

public class ChannelState implements Cloneable {
	private DataTransferChannel channel;
	private ReferenceStructure referenceStructure;
	
	public ChannelState(DataTransferChannel channel) {
		this.channel = channel;
	}

	public DataTransferChannel getChannel() {
		return channel;
	}

	public Map<Expression, Expression> getDependingParamAndValues(List<Constant> channelValues) {
		if (referenceStructure == null) {
			return null;
		}
		return referenceStructure.getDependingParamAndValues(channelValues);
	}
	
	public List<List<Constant>> getDependedChannelSelectorValues(Expression dependingVariable, Expression itsValue) {
		if (referenceStructure == null) {
			return null;
		}
		Map<Expression, Expression> dependingVarToVal = new HashMap<>();
		dependingVarToVal.put(dependingVariable, itsValue);
		return referenceStructure.getDependedChannelSelectorValues(dependingVarToVal);
	}
	
	public List<List<Constant>> getDependedChannelSelectorValues(Map<Expression, Expression> dependingVarToVal) {
		if (referenceStructure == null) {
			return null;
		}
		return referenceStructure.getDependedChannelSelectorValues(dependingVarToVal);
	}
	
	public void addDependingParamAndValue(List<Constant> channelValues, Expression dependingVariable, Expression itsValue) {
		if (referenceStructure == null) {
			referenceStructure = new ReferenceStructure();
		}
		referenceStructure.addDependingParamAndValue(channelValues, dependingVariable, itsValue);
	}
	
	public Object clone() {
		ChannelState newChannelState = new ChannelState(channel);
		newChannelState.referenceStructure = (ReferenceStructure) referenceStructure.clone();
		return newChannelState;
	}
	
	private static class ReferenceStructure implements Cloneable {
		private Map<Expression, Expression> dependingParamAndValues = null;
		private Map<Constant, ReferenceStructure> referenceStructure;
		
		public Map<Expression, Expression> getDependentParamAndValues() {
			return dependingParamAndValues;
		}
		
		public Map<Expression, Expression> getDependingParamAndValues(List<Constant> channelValues) {
			ReferenceStructure subStructure = this;
			for (Constant chVal: channelValues) {
				if (subStructure.getDependentParamAndValues() == null) {
					subStructure = subStructure.getReferenceStructure(chVal);
				} else {
					return null;
				}
			}
			return subStructure.getDependentParamAndValues();
		}
		
		public List<List<Constant>> getDependedChannelSelectorValues(Map<Expression, Expression> dependingVarToVal) {
			List<List<Constant>> channelValuesList = new ArrayList<>();
			if (dependingParamAndValues != null) {
				boolean doesMatch = true;
				for (Expression dependingVariable: dependingVarToVal.keySet()) {
					if (dependingParamAndValues.keySet().contains(dependingVariable)) {
						if (!dependingParamAndValues.get(dependingVariable).equals(dependingVarToVal.get(dependingVariable))) {
							doesMatch = false;
						}
					}
				}
				if (doesMatch) {
					List<List<Constant>> chValsList = new ArrayList<>();
					chValsList.add(new ArrayList<>());
					return chValsList;
				} else {
					return null;
				}
			} else {
				for (Constant chVal: referenceStructure.keySet()) {
					List<List<Constant>> chValsList = referenceStructure.get(chVal).getDependedChannelSelectorValues(dependingVarToVal);
					if (chValsList != null) {
						for (List<Constant> chVals: chValsList) {
							chVals.add(0, chVal);
							channelValuesList.add(chVals);
						}
					}
				}
			}
			return channelValuesList;
		}
		
		public void addDependingParamAndValue(Expression dependingVariable, Expression itsValue) {
			if (dependingParamAndValues == null) {
				dependingParamAndValues = new HashMap<>();
			}
			dependingParamAndValues.put(dependingVariable, itsValue);
		}
		
		public void addDependingParamAndValue(List<Constant> channelValues, Expression dependingVariable, Expression itsValue) {
			ReferenceStructure subStructure = this;
			for (Constant chVal: channelValues) {
				if (subStructure.referenceStructure == null) {
					subStructure.referenceStructure = new HashMap<>();
				}
				ReferenceStructure nextStructure = subStructure.referenceStructure.get(chVal);
				if (nextStructure == null) {
					nextStructure = new ReferenceStructure();
					subStructure.referenceStructure.put(chVal, nextStructure);
				}
				subStructure = nextStructure;
			}
			subStructure.addDependingParamAndValue(dependingVariable, itsValue);
		}
		
		public ReferenceStructure getReferenceStructure(Constant channelParam) {
			return referenceStructure.get(channelParam);
		}
		
		public Object clone() {
			ReferenceStructure newReferenceStructure = new ReferenceStructure();
			if (dependingParamAndValues != null) {
				newReferenceStructure.dependingParamAndValues = new HashMap<>(dependingParamAndValues);
			}
			if (referenceStructure != null) {
				newReferenceStructure.referenceStructure = new HashMap<>();
				for (Map.Entry<Constant, ReferenceStructure> refEnt: referenceStructure.entrySet()) {
					newReferenceStructure.referenceStructure.put(refEnt.getKey(), (ReferenceStructure) refEnt.getValue().clone());
				}
			}
			return newReferenceStructure;
		}
	}
}
