package ui.model;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import classes.HolonElement;
import classes.HolonObject;
import ui.controller.FlexManager;
import ui.model.DecoratedCable.CableState;
import ui.model.DecoratedHolonObject.HolonObjectState;
import ui.model.Model.FairnessModel;

public class DecoratedNetwork {
	private ArrayList<Supplier> supplierList = new ArrayList<Supplier>();
	private ArrayList<Consumer> consumerList = new ArrayList<Consumer>();
	private ArrayList<Consumer> consumerSelfSuppliedList = new ArrayList<Consumer>();
	private ArrayList<Passiv> passivNoEnergyList = new ArrayList<Passiv>();
	private ArrayList<DecoratedCable> decoratedCableList = new ArrayList<DecoratedCable>();
	private int timestep;

	public DecoratedNetwork(MinimumNetwork minimumNetwork, int Iteration, FairnessModel actualFairnessModel, FlexManager flexManager){	
		this.timestep = Iteration;
		switch(actualFairnessModel) {
		case AllEqual:
			calculateAllEqualNetwork(minimumNetwork, Iteration, flexManager);
			break;
		case MininumDemandFirst:
		default:
			calculateMinimumDemandFirstNetwork(minimumNetwork, Iteration, flexManager);
			break;		
		}
	}


	//Getter:
	public ArrayList<Supplier> getSupplierList() {
		return supplierList;
	}


	public ArrayList<Consumer> getConsumerList() {
		return consumerList;
	}


	public ArrayList<Consumer> getConsumerSelfSuppliedList() {
		return consumerSelfSuppliedList;
	}


	public ArrayList<Passiv> getPassivNoEnergyList() {
		return passivNoEnergyList;
	}
	public ArrayList<DecoratedCable> getDecoratedCableList(){
		return decoratedCableList;
	}
	
	
	//Calculations:
	private void calculateMinimumDemandFirstNetwork(MinimumNetwork minimumNetwork, int Iteration, FlexManager flexManager) {
		categorize(minimumNetwork, Iteration, flexManager);
		//Sort SupplierList according to the EnergyToSupplyNetwork maximum first.
		//Sort ConsumerList according to the MinimumConsumingElementEnergy minimum first.
		supplierList.sort((Supplier lhs,Supplier rhs) -> -Float.compare(lhs.getEnergyToSupplyNetwork(), rhs.getEnergyToSupplyNetwork()));
		consumerList.sort((Consumer lhs,Consumer rhs) -> Float.compare(lhs.getMinimumConsumingElementEnergy()  , rhs.getMinimumConsumingElementEnergy()));
		//consumerList.forEach((con) -> System.out.println(con.getMinimumConsumingElementEnergy()));
		//consumerList.forEach((con) -> System.out.println("AfterSorting" + con));
		float energyToSupplyInTheNetwork = supplierList.stream().map(supplier -> supplier.getEnergyToSupplyNetwork() - supplier.getEnergySupplied()).reduce( 0.0f, (a, b) -> a + b);
		decorateCable(minimumNetwork, energyToSupplyInTheNetwork);
		
		outerLoop:
			for(Consumer con : consumerList)
			{
				//gehe Supplier list durch wer ihn supplien kann.
				for(Supplier sup : supplierList) {
					float  energyRdyToSupply = sup.getEnergyToSupplyNetwork() - sup.getEnergySupplied();
					if(energyRdyToSupply == 0.0f) continue;
					float energyNeededForMinimumConsumingElement=con.getMinimumConsumingElementEnergy()-con.getEnergyFromNetwork();
					if(energyNeededForMinimumConsumingElement>energyToSupplyInTheNetwork) {
						//Dont supply a minimumElement when you cant supply it fully
						break outerLoop;
					}
					if(energyRdyToSupply>=energyNeededForMinimumConsumingElement) {
						energyToSupplyInTheNetwork -= energyNeededForMinimumConsumingElement;
						supply(con, sup, energyNeededForMinimumConsumingElement);
						continue outerLoop;
					}else
					{
						energyToSupplyInTheNetwork -= energyRdyToSupply;
						supply(con, sup, energyRdyToSupply);
					}
				}
				//No more Energy in the network
				break;
			}
		//consumerList.forEach((con) -> System.out.println("AfterSuppliing MinimumDemand " + con));
		
		//Sort ConsumerList according to the EnergyNeeded to supply fully after minimum Demand First.
		consumerList.sort((Consumer lhs,Consumer rhs) -> Float.compare(lhs.getEnergyNeededFromNetwork()-lhs.getEnergyFromNetwork() , rhs.getEnergyNeededFromNetwork()-rhs.getEnergyFromNetwork() ));
		//Supply consumer fully 
		outerLoop:
			for(Consumer con : consumerList)
			{
				//gehe Supplier list durch wer ihn supplien kann.
				for(Supplier sup : supplierList) {
					float  energyRdyToSupply = sup.getEnergyToSupplyNetwork() - sup.getEnergySupplied();
					if(energyRdyToSupply == 0.0f) continue;
					float energyNeededForFullySupply = con.getEnergyNeededFromNetwork() - con.getEnergyFromNetwork();
					if(energyNeededForFullySupply == 0.0f) continue outerLoop;
					if(energyRdyToSupply>=energyNeededForFullySupply) {
						supply(con, sup, energyNeededForFullySupply);
						continue outerLoop;
					}else
					{
						supply(con, sup, energyRdyToSupply);
					}
				}
				//No more Energy in the network
				break;
			}
		//consumerList.forEach((con) -> System.out.println("AfterFullySuplieing" + con));
		//If Energy Left Supply all equal
		//Count EnergyLeft
		float energyLeft = supplierList.stream().map(supplier -> supplier.getEnergyToSupplyNetwork() - supplier.getEnergySupplied()).reduce( 0.0f, (a, b) -> a + b);
		//System.out.println("EnergyLeft: " +  energyLeft);
		if(energyLeft > 0.0f && (consumerList.size() + consumerSelfSuppliedList.size() != 0))
		{
			float equalAmountOfEnergyToSupply = energyLeft / ((float)(consumerList.size() + consumerSelfSuppliedList.size()));
			outerLoop:
				for(Consumer con : consumerList)
				{
					//gehe Supplier list durch wer ihn supplien kann.
					for(Supplier sup : supplierList) {
						float  energyRdyToSupply = sup.getEnergyToSupplyNetwork() - sup.getEnergySupplied();
						if(energyRdyToSupply == 0.0f) continue;
						float energyNeededToSupplyConsumerTheEqualAmount = equalAmountOfEnergyToSupply +con.getEnergyNeededFromNetwork()- con.getEnergyFromNetwork();
						if(energyRdyToSupply>=energyNeededToSupplyConsumerTheEqualAmount) {
							supply(con, sup, energyNeededToSupplyConsumerTheEqualAmount);
							continue outerLoop;
						}else
						{
							supply(con, sup, energyRdyToSupply);
						}
					}
					//No more Energy in the network
					break;
				}
			outerLoop:
				for(Consumer con : consumerSelfSuppliedList)
				{
					//gehe Supplier list durch wer ihn supplien kann.
					for(Supplier sup : supplierList) {
						float  energyRdyToSupply = sup.getEnergyToSupplyNetwork() - sup.getEnergySupplied();
						if(energyRdyToSupply == 0.0f) continue;
						float energyNeededToSupplyConsumerTheEqualAmount = equalAmountOfEnergyToSupply +con.getEnergyNeededFromNetwork()- con.getEnergyFromNetwork();
						if(energyRdyToSupply>=energyNeededToSupplyConsumerTheEqualAmount) {
							supply(con, sup, energyNeededToSupplyConsumerTheEqualAmount);
							continue outerLoop;
						}else
						{
							supply(con, sup, energyRdyToSupply);
						}
						
					}
					//No more Energy in the network
					break;
				}
		}
		//consumerList.forEach((con) -> System.out.println("AfterOverSuppleiing" + con));
		
		//consumerSelfSuppliedList.forEach((con) -> System.out.println("AfterOverSuppleiing" + con));
		
		calculateStates();
	}


	private void decorateCable(MinimumNetwork minimumNetwork, float energyToSupplyInTheNetwork) {
		//DecoratedCables
		//Minimum demand first:
		for(IntermediateCableWithState edge: minimumNetwork.getEdgeList()) {		
			decoratedCableList.add(new DecoratedCable(edge.getModel(), edge.getState(), (edge.getState() == CableState.Working) ? energyToSupplyInTheNetwork : 0.0f));
		}
	}



	private void calculateAllEqualNetwork(MinimumNetwork minimumNetwork, int Iteration, FlexManager flexManager) {
		categorize(minimumNetwork, Iteration, flexManager);
		float energyToSupplyInTheNetwork = supplierList.stream().map(supplier -> supplier.getEnergyToSupplyNetwork() - supplier.getEnergySupplied()).reduce( 0.0f, (a, b) -> a + b);
		float energyForEachConsumer = (consumerList.size() != 0) ? energyToSupplyInTheNetwork / consumerList.size() : 0.0f;
		decorateCable(minimumNetwork, energyToSupplyInTheNetwork);
		//Supply consumer equal 
		outerLoop:
		for(Consumer con : consumerList)
		{
			//gehe Supplier list durch wer ihn supplien kann.
			float energyNeededForEqualSupply = energyForEachConsumer;
			for(Supplier sup : supplierList) {
				float  energyRdyToSupply = sup.getEnergyToSupplyNetwork() - sup.getEnergySupplied();
				if(energyRdyToSupply == 0.0f) continue;
				if(energyRdyToSupply>=energyNeededForEqualSupply) {
					supply(con, sup, energyNeededForEqualSupply);
					continue outerLoop;
				}else
				{
					supply(con, sup, energyRdyToSupply);
					energyNeededForEqualSupply -= energyRdyToSupply;
				}
			}
			//No more Energy in the network
			break;
		}
		calculateStates();
	}

	private void calculateStates() {
		//CalculateStates:
		supplierList.forEach(sup -> sup.setState(HolonObjectState.PRODUCER));
		passivNoEnergyList.forEach(sup -> sup.setState(HolonObjectState.NO_ENERGY));
		for(Consumer con : this.consumerList)
		{
			setConsumerState(con);
		}
		for(Consumer con : this.consumerSelfSuppliedList)
		{
			setConsumerState(con);
		}
	}
	private void categorize(MinimumNetwork minimumNetwork, int Iteration, FlexManager flexManager) {
		//Categorize
		for(HolonObject hObject: minimumNetwork.getHolonObjectList()) {
			float energyNeeded = hObject.getEnergyNeededFromConsumingElementsWithFlex(Iteration, flexManager);
			float energySelfProducing = hObject.getEnergySelfProducingFromProducingElementsWithFlex(Iteration, flexManager);
			if(energyNeeded < energySelfProducing) {
				Supplier sup = new Supplier(hObject, energySelfProducing - energyNeeded, energyNeeded);
				supplierList.add(sup);
			} else if (energyNeeded > energySelfProducing) {
				Consumer con = new Consumer(hObject);
				con.setEnergyNeededFromNetwork(energyNeeded - energySelfProducing);
				con.setMinimumConsumingElementEnergy(hObject.getMinimumConsumingElementEnergyWithFlex(Iteration, flexManager));
				con.setEnergyFromConsumingElemnets(hObject.getEnergyNeededFromConsumingElementsWithFlex(Iteration, flexManager));
				con.setEnergySelfSupplied(hObject.getEnergySelfProducingFromProducingElementsWithFlex(Iteration, flexManager));
				consumerList.add(con);
			}else if(energyNeeded == energySelfProducing) {
				
				if (energySelfProducing == 0.0f) {
					Passiv pas = new Passiv(hObject);
					passivNoEnergyList.add(pas);
				} else {
					Consumer con = new Consumer(hObject);
					con.setEnergyNeededFromNetwork(0.0f);
					con.setMinimumConsumingElementEnergy(hObject.getMinimumConsumingElementEnergyWithFlex(Iteration, flexManager));
					con.setEnergyFromConsumingElemnets(hObject.getEnergyNeededFromConsumingElementsWithFlex(Iteration, flexManager));
					con.setEnergySelfSupplied(hObject.getEnergySelfProducingFromProducingElementsWithFlex(Iteration, flexManager));
					consumerSelfSuppliedList.add(con);
				}
				
			}
		}
	}
	
	
	private void setConsumerState(Consumer con) {
		if(con.getEnergySelfSupplied() + con.getEnergyFromNetwork() > con.getEnergyFromConsumingElemnets()) {
			con.setState(HolonObjectState.OVER_SUPPLIED);
		}else if(con.getEnergySelfSupplied() + con.getEnergyFromNetwork() == con.getEnergyFromConsumingElemnets()) {
			con.setState(HolonObjectState.SUPPLIED);
		}else if(con.getEnergySelfSupplied() + con.getEnergyFromNetwork() >= con.getMinimumConsumingElementEnergy()) {
			con.setState(HolonObjectState.PARTIALLY_SUPPLIED);
		}else {
			con.setState(HolonObjectState.NOT_SUPPLIED);
		}
	}
	
	
	/**
	 * No Checks.
	 * @param con
	 * @param sup
	 * @param energy
	 */
	private void supply(Consumer con, Supplier sup, float energy) {
		sup.getConsumerList().add(sup.new ConsumerListEntry(con , energy));
		sup.setEnergySupplied(sup.getEnergySupplied() + energy);
		con.getSupplierList().add(con.new SupplierListEntry(sup, energy));
		con.setEnergyFromNetwork(con.getEnergyFromNetwork() + energy);
	}


	
	public int  getAmountOfActiveElements() {
		
		return supplierList.stream().map(object -> object.getModel().getNumberOfActiveElements()).reduce(0, Integer::sum)+
				consumerList.stream().map(object -> object.getModel().getNumberOfActiveElements()).reduce(0, Integer::sum)+
				consumerSelfSuppliedList.stream().map(object -> object.getModel().getNumberOfActiveElements()).reduce(0, Integer::sum);
	}
	public int  getAmountOfElements() {
		
		return supplierList.stream().map(object -> object.getModel().getNumberOfElements()).reduce(0, Integer::sum)+
				consumerList.stream().map(object -> object.getModel().getNumberOfElements()).reduce(0, Integer::sum)+
				consumerSelfSuppliedList.stream().map(object -> object.getModel().getNumberOfElements()).reduce(0, Integer::sum);
	}
	
	
	
	
	
	
	
	public int getAmountOfConsumer() {
		return consumerList.size() + this.consumerSelfSuppliedList.size();
	}


	public int getAmountOfSupplier() {
		return supplierList.size();
	}


	public int getAmountOfConsumerWithState(HolonObjectState state) {
		return (int) (consumerList.stream().filter(con -> con.getState() == state).count() + consumerSelfSuppliedList.stream().filter(con -> con.getState() == state).count());
	}


	public int getAmountOfPassiv() {
		return passivNoEnergyList.size();
	}
	public int getAmountOfHolonObjects() {
		return getAmountOfConsumer() + getAmountOfSupplier() + getAmountOfPassiv();
	}
	
	
	public float getTotalConsumption() {
		float energy = consumerList.stream().map(con -> con.getEnergyFromConsumingElemnets()).reduce(0.f, Float::sum) + consumerSelfSuppliedList.stream().map(con -> con.getEnergyFromConsumingElemnets()).reduce(0.f, Float::sum);
		energy += supplierList.stream().map(sup -> sup.getEnergySelfConsuming()).reduce(0.f,  Float::sum);
		return energy;
	}
	
	public float getAverageConsumptionInNetworkForHolonObject(){
		return getTotalConsumption() / (float)getAmountOfHolonObjects();
	}
	
	public float getTotalProduction() {
		float energy = consumerList.stream().map(con -> con.getEnergySelfSupplied()).reduce(0.f, Float::sum) + consumerSelfSuppliedList.stream().map(con -> con.getEnergySelfSupplied()).reduce(0.f, Float::sum);
		energy += supplierList.stream().map(sup -> sup.getEnergyProducing()).reduce(0.f,  Float::sum);
		return energy;
	}
	
	public float getAverageProductionInNetworkForHolonObject() {
		return getTotalProduction() /  (float) getAmountOfHolonObjects();
	}
	
	/**
	 * returns the Varianz in Poduction
	 * @return
	 */
	public float getVarianzInProductionInNetworkForHolonObjects() {
		float average = getAverageProductionInNetworkForHolonObject();
		float sum = consumerList.stream().map(con -> squared(con.getEnergySelfSupplied() - average)).reduce(0.f, Float::sum) 
				+ consumerSelfSuppliedList.stream().map(con -> squared(con.getEnergySelfSupplied() - average)).reduce(0.f, Float::sum)
				+ supplierList.stream().map(sup -> squared(sup.getEnergyProducing() - average)).reduce(0.f,  Float::sum);
		return sum / (float) getAmountOfHolonObjects();
	}
	
	
	public float getDeviationInProductionInNetworkForHolonObjects() {
		return (float)Math.sqrt(getVarianzInProductionInNetworkForHolonObjects());
	}
	
	
	public float getVarianzInConsumptionInNetworkForHolonObjects() {
		float average = getAverageConsumptionInNetworkForHolonObject();
		float sum = consumerList.stream().map(con -> squared(con.getEnergyFromConsumingElemnets() - average)).reduce(0.f, Float::sum) 
				+ consumerSelfSuppliedList.stream().map(con -> squared(con.getEnergyFromConsumingElemnets() - average)).reduce(0.f, Float::sum)
				+ supplierList.stream().map(sup -> squared(sup.getEnergySelfConsuming() - average)).reduce(0.f,  Float::sum);
		return sum / (float) getAmountOfHolonObjects();
	}
	
	public float getDeviationInConsumptionInNetworkForHolonObjects() {
		return (float)Math.sqrt(getVarianzInConsumptionInNetworkForHolonObjects());
	}
	
	//HelperFunction
	/**
	 * 
	 * @return a list of energy
	 */
	public List<Float> getListOfEnergyThatIsOfferedByFlexibilitiesInThisNetwork() {
		List<HolonElement> eleList = consumerList.stream().flatMap(con-> con.getModel().getElements().stream()).collect(Collectors.toList());
		eleList.addAll(consumerSelfSuppliedList.stream().flatMap(con-> con.getModel().getElements().stream()).collect(Collectors.toList()));
		eleList.addAll(supplierList.stream().flatMap(con-> con.getModel().getElements().stream()).collect(Collectors.toList()));
		eleList.addAll(passivNoEnergyList.stream().flatMap(con-> con.getModel().getElements().stream()).collect(Collectors.toList()));
		return  eleList.stream().filter(ele -> (ele.flexList.stream().anyMatch(flex -> flex.offered && flex.fulfillsConstrains())) ).map(ele -> -ele.getEnergyAtTimeStep(timestep) ).collect(Collectors.toList()) ;
	}
	
	public List<Float> getListOfEnergyInProductionThatIsOfferedByFlexibilitiesInThisNetwork(){
		return getListOfEnergyThatIsOfferedByFlexibilitiesInThisNetwork().stream().filter(value -> (value > 0.f)).collect(Collectors.toList());
	}
	public List<Float> getListOfEnergyInConsumptionThatIsOfferedByFlexibilitiesInThisNetwork(){
		return getListOfEnergyThatIsOfferedByFlexibilitiesInThisNetwork().stream().filter(value -> (value < 0.f)).map(value -> -value).collect(Collectors.toList());
	}
	
	public float getFlexibilityProductionCapacity() {
		return getListOfEnergyInProductionThatIsOfferedByFlexibilitiesInThisNetwork().stream().reduce(0.f, Float::sum);
	}
	public float getFlexibilityConsumptionCapacity() {
		return getListOfEnergyInConsumptionThatIsOfferedByFlexibilitiesInThisNetwork().stream().reduce(0.f, Float::sum);
	}
	
	
	public int getAmountOfProductionFlexibilities() {
		return getListOfEnergyInProductionThatIsOfferedByFlexibilitiesInThisNetwork().size();
	}
	public int getAmountOfConsumptionFlexibilities() {
		return getListOfEnergyInConsumptionThatIsOfferedByFlexibilitiesInThisNetwork().size();
	}
	
	public float getAverageFlexibilityProduction() {
		int amount = getAmountOfProductionFlexibilities();
		 return (amount > 0)? getFlexibilityProductionCapacity() / (float)amount : 0.f;
	}
	
	public float getAverageFlexibilityConsumption() {
		int amount = getAmountOfConsumptionFlexibilities();
		 return (amount > 0)? getFlexibilityConsumptionCapacity() / (float)amount : 0.f;
	}
	
	public float getVarianzInFlexibilitieConsumption() {
		float average = getAverageFlexibilityConsumption();
		float sum = getListOfEnergyInConsumptionThatIsOfferedByFlexibilitiesInThisNetwork().stream().map(energy -> squared(energy - average)).reduce(0.f, Float::sum);
		int amountOfFlexibilities = getAmountOfConsumptionFlexibilities();
		return (amountOfFlexibilities > 0)? sum / (float) amountOfFlexibilities : 0.f;
	}
	
	
	public float getVarianzInFlexibilitieProduction() {
		float average = getAverageFlexibilityProduction();
		float sum = getListOfEnergyInProductionThatIsOfferedByFlexibilitiesInThisNetwork().stream().map(energy -> squared(energy - average)).reduce(0.f, Float::sum);
		int amountOfFlexibilities = getAmountOfProductionFlexibilities();
		return (amountOfFlexibilities > 0)? sum / (float) amountOfFlexibilities : 0.f;
	}
	
	public float getDiviationInFlexibilityConsumption() {
		return (float) Math.sqrt(getVarianzInFlexibilitieConsumption());
	}
	public float getDiviationInFlexibilityProduction() {
		return (float) Math.sqrt(getVarianzInFlexibilitieProduction());
	}
	
	//Help Function
	private float squared(float input) {
		return (float) Math.pow(input, 2);
	}
	
	
	
	
	
	
}
