package ui.model;

import java.util.ArrayList;

import classes.HolonObject;
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>();

	public DecoratedNetwork(MinimumNetwork minimumNetwork, int Iteration, FairnessModel actualFairnessModel){	
		switch(actualFairnessModel) {
		case AllEqual:
			calculateAllEqualNetwork(minimumNetwork, Iteration);
			break;
		case MininumDemandFirst:
		default:
			calculateMinimumDemandFirstNetwork(minimumNetwork, Iteration);
			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) {
		categorize(minimumNetwork, Iteration);
		//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) {
		categorize(minimumNetwork, Iteration);
		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) {
		//Categorize
		for(HolonObject hObject: minimumNetwork.getHolonObjectList()) {
			float energyNeeded = hObject.getEnergyNeededFromConsumingElements(Iteration);
			float energySelfProducing = hObject.getEnergySelfProducingFromProducingElements(Iteration);
			if(energyNeeded < energySelfProducing) {
				Supplier sup = new Supplier(hObject, energySelfProducing - energyNeeded);
				supplierList.add(sup);
			} else if (energyNeeded > energySelfProducing) {
				Consumer con = new Consumer(hObject);
				con.setEnergyNeededFromNetwork(energyNeeded - energySelfProducing);
				con.setMinimumConsumingElementEnergy(hObject.getMinimumConsumingElementEnergy(Iteration));
				con.setEnergyFromConsumingElemnets(hObject.getEnergyNeededFromConsumingElements(Iteration));
				con.setEnergySelfSupplied(hObject.getEnergySelfProducingFromProducingElements(Iteration));
				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.getMinimumConsumingElementEnergy(Iteration));
					con.setEnergyFromConsumingElemnets(hObject.getEnergyNeededFromConsumingElements(Iteration));
					con.setEnergySelfSupplied(hObject.getEnergySelfProducingFromProducingElements(Iteration));
					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);
	}
}
