package ui.controller;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.stream.Collectors;

import classes.AbstractCanvasObject;
import classes.GroupNode;
import classes.Flexibility;
import classes.HolonElement;
import classes.HolonObject;
import ui.model.Model;

/**
 * Class to Manage to flexibilities.
 * To order, view Flexibilities.
 * @author tom
 *
 */
public class FlexManager {
	private int timeStep;
	private  List<Flexibility> allFlexModels;
	private List<Flexibility> allFlexesOrderedThisTimeStep = new ArrayList<Flexibility>();
	private HashMap<HolonElement, List<FlexWrapper>> accessOtherFlex = new HashMap<HolonElement, List<FlexWrapper>>();
	private HashMap<Flexibility, FlexWrapper> accessFlexMap = new HashMap<Flexibility, FlexWrapper>();
	public FlexManager(Model model, int timeStep, FlexManager timestepBefore){
		//Thread.dumpStack();
		this.timeStep = timeStep;
		allFlexModels = getAllFlexFromModel(model);
		//fill accessFlexMap
		allFlexModels.stream().map(flex -> new FlexWrapper(flex, timeStep, (timestepBefore != null)?timestepBefore.getFlexWrapper(flex):null)).forEach(flexWrapper -> {
			accessFlexMap.put(flexWrapper.getFlex(), flexWrapper);
			HolonElement ele = flexWrapper.getFlex().getElement();
			if(accessOtherFlex.containsKey(ele)) {
				accessOtherFlex.get(ele).add(flexWrapper);
			}else {
				ArrayList<FlexWrapper> toAdd = new ArrayList<FlexWrapper>();
				toAdd.add(flexWrapper);
				accessOtherFlex.put(ele, new ArrayList<FlexWrapper>(toAdd));
			}
			});
		//because when added not all flexes can see others
		accessFlexMap.values().stream().forEach(flexWrapper -> flexWrapper.revalidateState());
	}

	
	
	
	
	private FlexWrapper getFlexWrapper(Flexibility flex) {
		return accessFlexMap.getOrDefault(flex, null);
	}

	public List<FlexWrapper> getAllFlexWrapper() {
		return accessFlexMap.values().stream().collect(Collectors.toList());
	}
	
	public List<FlexWrapper> getAllFlexWrapperWithState(FlexState state) {
		return accessFlexMap.values().stream().filter(flexWrapper -> (flexWrapper.getState() == state)).collect(Collectors.toList());
	}



	private List<Flexibility> getAllFlexFromModel(Model model) {
		return createListOfHolonObjects(model.getObjectsOnCanvas()).stream().flatMap(hObject -> hObject.getElements().stream()).flatMap(hElement -> hElement.flexList.stream()).collect(Collectors.toList());
	}

	private List<HolonObject> createListOfHolonObjects(List<AbstractCanvasObject> objectsOnCanvas) {
		List<HolonObject> list = new ArrayList<HolonObject>();
		for(AbstractCanvasObject aCps :  objectsOnCanvas) {
			if(aCps instanceof HolonObject) list.add((HolonObject) aCps);
			else if(aCps instanceof GroupNode)list.addAll(createListOfHolonObjects(((GroupNode)aCps).getNodes()));
		}
		return list;
	}


	public int getTimeStep() {
		return timeStep;
	}
	
	public List<Flexibility> getAllFlexesOrderedThisTimeStep() {
		return allFlexesOrderedThisTimeStep;
	}

	public void orderFlexFromList(List<Flexibility> flexList) {
		flexList.stream().forEach(flex -> {
			FlexWrapper flexToOrder = accessFlexMap.get(flex);
			if(flexToOrder!=null)flexToOrder.order();
		});
	}
	public void orderFlex(Flexibility flex) {
		FlexWrapper flexToOrder =  accessFlexMap.get(flex);
		if(flexToOrder!=null)flexToOrder.order();
	}
	public boolean isAFlexInUseOfHolonElement(HolonElement ele) {
		return ele.flexList.stream().filter(flex -> this.accessFlexMap.containsKey(flex)).anyMatch(flex -> (this.accessFlexMap.get(flex).getState() == FlexState.IN_USE));
	}
	/**
	 * Or Return null
	 * @param flex
	 * @return
	 */
	public FlexWrapper getFlexWrapperFromFlexibility(Flexibility flex) {
		return accessFlexMap.get(flex);
	}
	
	public static enum FlexState{
			IN_USE, ON_COOLDOWN, OFFERED, NOT_OFFERED, UNAVAILABLE
		}
	//Classes
	public class FlexWrapper{
		private Flexibility flex;
		private FlexState state;
		int timeStep;
		int durationEndTime = -1;
		int coolDownEndTime = -1;
		public FlexWrapper(Flexibility flex, int timeStep, FlexWrapper old) {
			this.flex = flex;
			this.timeStep = timeStep;
			if(old == null) {
				state = flex.fulfillsConstrains()?(flex.offered?FlexState.OFFERED:FlexState.NOT_OFFERED):FlexState.UNAVAILABLE;
			}else {
				durationEndTime = old.durationEndTime;
				coolDownEndTime = old.coolDownEndTime;
				revalidateState();
			}
				
			
		}
		
		public void revalidateState() {
			if(remainingTimeTillActivation() == 0) state = (flex.fulfillsConstrains() && !otherFlexInUseOrOnCooldown())?(flex.offered?FlexState.OFFERED:FlexState.NOT_OFFERED):FlexState.UNAVAILABLE;
			else if(remainingDuration()== 0) state = FlexState.ON_COOLDOWN;
			else state = FlexState.IN_USE;
		}
		
		
		
		public Flexibility getFlex() {
			return flex;
		}
		public FlexState getState() {
			return state;
		}
		public boolean canOrder() {
			return (state == FlexState.OFFERED) &&  //Right state
					!otherFlexInUseOrOnCooldown(); //No other flex of this ele in use
		}
		private boolean otherFlexInUseOrOnCooldown() {
			if(accessOtherFlex.get(this.getFlex().getElement()) == null) return false;
			return accessOtherFlex.get(this.getFlex().getElement()).stream().anyMatch(flexWrapper -> flexWrapper != this && (flexWrapper.getState() == FlexState.IN_USE || flexWrapper.getState() == FlexState.ON_COOLDOWN));
		}
		public boolean order() {
			if(canOrder()) {
				state=FlexState.IN_USE;
				allFlexesOrderedThisTimeStep.add(flex);
				durationEndTime = timeStep + flex.getDuration();
				coolDownEndTime = durationEndTime + flex.getCooldown();
				//accessFlexMap.values().stream().filter(flexWrapper -> (flexWrapper.getFlex().getElement() == flex.getElement() && flexWrapper != this)).forEach(otherFlex -> otherFlex.revalidateState());
				accessOtherFlex.get(this.getFlex().getElement()).stream().filter(flexWrapper -> (flexWrapper != this)).forEach(otherFlex -> otherFlex.revalidateState());
				return true;
			}
			return false;
		}
		public boolean cancel() {
			if(allFlexesOrderedThisTimeStep.contains(flex)) {
				state=FlexState.OFFERED;
				durationEndTime = -1;
				coolDownEndTime = -1;
				allFlexesOrderedThisTimeStep.remove(flex);
				accessOtherFlex.get(this.getFlex().getElement()).stream().filter(flexWrapper -> (flexWrapper != this)).forEach(otherFlex -> otherFlex.revalidateState());

				return true;
			}
			return false;
		}
		public int remainingTimeTillActivation() {
			return Math.max(0, coolDownEndTime - timeStep);
		}
		public int remainingDuration() {
			return Math.max(0, durationEndTime - timeStep);
		}
		
	}
	public void reset() {
		getAllFlexWrapper().forEach(flexWrapper -> flexWrapper.cancel());
	}
}
