package simulator;

import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map;

import models.algebra.Constant;
import models.algebra.Expression;
import models.algebra.Type;
import models.dataConstraintModel.DataConstraintModel;
import models.dataConstraintModel.ResourceHierarchy;
import simulator.states.*;

public class Resource {
	private Resource parent = null;
	private Map<String, Resource> children = null;
	private ResourceHierarchy resourceHierarchy = null;
	private ResourceState state = null;
	private Constant parameter = null;
	
	public Resource(Resource resource) {
		this.resourceHierarchy = resource.resourceHierarchy;
		this.parent = resource.parent;
		this.state = (ResourceState) resource.state.clone();
		this.parameter = resource.parameter;
	}
	
	public Resource(ResourceHierarchy resourceHierarchy) {
		this.resourceHierarchy = resourceHierarchy;
		Type resType = resourceHierarchy.getResourceStateType();
		if (resourceHierarchy.getChildren().size() > 0 
				|| DataConstraintModel.typeList.isAncestorOf(resType)
				|| DataConstraintModel.typeMap.isAncestorOf(resType)
				|| DataConstraintModel.typeJson.isAncestorOf(resType)) {
			if (resType != null && DataConstraintModel.typeList.isAncestorOf(resType)) {
				// List resource
				state = new ListResourceState();
			} else if (resType != null && DataConstraintModel.typeMap.isAncestorOf(resType)) {
				// Map resource
				state = new MapResourceState();
			} else {
				// Json resource
				if (resType == null) {
					resType = DataConstraintModel.typeJson;
					resourceHierarchy.setResourceStateType(resType);
				}
				state = new JsonResourceState();
				children = new LinkedHashMap<>();
				for (ResourceHierarchy child: resourceHierarchy.getChildren()) {
					Resource cRes = new Resource(child, this);
					((JsonResourceState) state).addChildState(child.getResourceName(), cRes.getState());
					children.put(child.getResourceName(), cRes);
				}
			}
		} else {
			Expression initValue = resourceHierarchy.getInitialValue();
			if (initValue != null && initValue instanceof Constant) {
				state = new PrimitiveResourceState((Constant) resourceHierarchy.getInitialValue());
			} else {
				state = new PrimitiveResourceState(null);
			}
		}
	}
	
	public Resource(ResourceHierarchy resourceHierarchy, Resource parent) {
		this(resourceHierarchy);
		this.parent = parent;
	}
	
	public Resource(ResourceHierarchy resourceHierarchy, Resource parent, ResourceState state) {
		this.resourceHierarchy = resourceHierarchy;
		this.parent = parent;
		this.state = state;
	}
	
	public Resource(ResourceHierarchy resourceHierarchy, Resource parent, Constant parameter, ResourceState state) {
		this.resourceHierarchy = resourceHierarchy;
		this.parent = parent;
		this.parameter = parameter;
		this.state = state;
	}

	public Resource getParent() {
		return parent;
	}

	public void setParent(Resource parent) {
		this.parent = parent;
	}
	
	public ResourceHierarchy getResourceHierarchy() {
		return resourceHierarchy;
	}
	
	public ResourceIdentifier getResourceIdentifier() {
		ResourceIdentifier resId = null;
		if (parent != null) {
			ResourceIdentifier parentResId = parent.getResourceIdentifier();
			if (resourceHierarchy.getNumParameters() == 0) {
				resId = new ResourceIdentifier(parentResId, resourceHierarchy.getResourceName(), resourceHierarchy);
			} else {
				resId = new ResourceIdentifier(parentResId, parameter, resourceHierarchy);
			}
		} else {
			resId = new ResourceIdentifier(resourceHierarchy.getResourceName(), resourceHierarchy);
		}
		return resId;
	}

	public ResourceState getState() {
		return state;
	}
	
	public void changeState(ResourceState state) {
		this.state = state;
	}

	public Constant getParameter() {
		return parameter;
	}
	
	public Collection<Resource> getChildren() {
		Map<String, Resource> childrenMap = getChildrenMap();
		if (childrenMap == null) return null;
		return childrenMap.values();
	}

	public Map<String, Resource> getChildrenMap() {
		Map<String, Resource> children = null;
		if (resourceHierarchy.getChildren().size() > 0) {
			children = new LinkedHashMap<>();
			ResourceHierarchy childRes = resourceHierarchy.getChildren().iterator().next();
			if (childRes.getNumParameters() > 0) {
				// List or Map type.
				if (state instanceof CompositeResourceState) {
					// If the list or map is not nil.
					Map<String, ResourceState> childStates = ((CompositeResourceState) state).getChildStates();
					for (Map.Entry<String, ResourceState> childEnt: childStates.entrySet()) {
						String childParam = childEnt.getKey();
						ResourceState childState = childEnt.getValue();
						Type thisType = resourceHierarchy.getResourceStateType();
						if (DataConstraintModel.typeList.isAncestorOf(thisType)) {
							children.put(childParam, new Resource(childRes, this, new Constant(childParam, DataConstraintModel.typeInt), childState));
						} else if (DataConstraintModel.typeMap.isAncestorOf(thisType)) {
							children.put(childParam, new Resource(childRes, this, new Constant(childParam, DataConstraintModel.typeString), childState));						
						}
					}
				}
			} else {
				// Json type.
				Map<String, ResourceState> childStates = ((CompositeResourceState) state).getChildStates();
				if (this.children == null || this.children.size() < childStates.size()) {
					for (Map.Entry<String, ResourceState> childEnt: childStates.entrySet()) {
						String childParam = childEnt.getKey();
						ResourceState childState = childEnt.getValue();
						Type thisType = resourceHierarchy.getResourceStateType();
						for (ResourceHierarchy c: resourceHierarchy.getChildren()) {
							if (c.getResourceName().equals(childParam.replace("\"", ""))) {
								childRes = c;
								break;
							}
						}
						if (DataConstraintModel.typeJson.isAncestorOf(thisType)) {
							children.put(childParam, new Resource(childRes, this, new Constant(childParam, DataConstraintModel.typeString), childState));
						}
					}
					this.children = children;
				}
				return this.children;
			}
		}
		return children;
	}
	
	public Resource getDescendant(ResourceIdentifier resId) {
		if (this.getResourceIdentifier().equals(resId)) return this;
		if (!resId.startsWith(this.getResourceIdentifier())) return null;
		for (Resource child: getChildren()) {
			Resource res =  child.getDescendant(resId);
			if (res != null) return res;
		}
		return null;
	}
	
	public Resource getDescendant(String resId) {
		if (this.getResourceIdentifier().toString().equals(resId)) return this;
		if (!resId.startsWith(this.getResourceIdentifier().toString())) return null;
		for (Resource child: getChildren()) {
			Resource res =  child.getDescendant(resId);
			if (res != null) return res;
		}
		return null;
	}
}
