package classes;

import com.google.gson.annotations.Expose;

import ui.controller.FlexManager;

import java.util.ArrayList;



/**
 * The class HolonObject represents any Object on the system which capability of
 * injecting or consuming energy on the network, for instance a house or a power
 * plant.
 *
 * @author Gruppe14
 */
public class HolonObject extends AbstractCanvasObject { 
    /* Array of all consumers */
    private ArrayList<HolonElement> elements;
    /* Array for tracking Production */
    private float[] trackingProd;
    /* Array for tracking Consumption */
    private float[] trackingCons;
    /* Total Flexibility */
    private float totalFlex;
    
    /**
     * Constructor Set by default the name of the object equals to the category
     * name, until the user changes it.
     *
     * @param objName name of the Object
     */
    public HolonObject(String objName) {
        super(objName);
        setElements(new ArrayList<>());
        setTrackingProd(new float[100]);
        setTrackingCons(new float[100]);
    }

    /**
     * Contructor of a copy of an Object.
     *
     * @param obj object to be copied
     */
    public HolonObject(AbstractCanvasObject obj) {
        super(obj);
        setElements(copyElements(((HolonObject) obj).getElements()));
        setTrackingProd(new float[100]);
        setTrackingCons(new float[100]);
    }

 

    /**
     * Getter for all Elements in the HolonObject.
     *
     * @return the elements ArrayList
     */
    public ArrayList<HolonElement> getElements() {
        return elements;
    }

    /**
     * Set a new ArrayList with HolonElements into the HolonObject.
     *
     * @param elements the elements to set
     */
    public void setElements(ArrayList<HolonElement> elements) {
        this.elements = elements;
    }

    /**
     * adds an Element to the Object.
     *
     * @param element the Element to add
     */
    public void addElement(HolonElement element) {
        elements.add(element);
    }

    /**
     * deletes Element at a given index.
     *
     * @param idx index
     */
    public void deleteElement(int idx) {
    	elements.remove(idx);
    }
    

    



    /**
     * String of all consumers in this HolonObject.
     *
     * @return all the names of this HolonObject separated by "," each object
     */
    public String toStringElements() {
        String objString = "Empty";
        for (HolonElement e : elements) {
            if (objString == "Empty") {
                objString = e.getEleName();
            } else {
                objString = objString + ", " + e.getEleName();
            }
        }
        return objString;
    }


    /**
     * Copy all Elements into a New Array.
     *
     * @param arr to copy
     * @return the copy of arr
     */
    public ArrayList<HolonElement> copyElements(ArrayList<HolonElement> arr) {
        ArrayList<HolonElement> newArr = new ArrayList<>();
        for (HolonElement t : arr) {
            newArr.add(new HolonElement(t));
        }
        return newArr;
    }


    /**
     * Search for the first element with the name.
     *
     * @param name name of the object to be searched
     * @return the searched HolonElement
     */
    public HolonElement searchElement(String name) {
        HolonElement ele = null;
        for (HolonElement e : getElements()) {
            if (e.getEleName().equals(name)) {
                ele = e;
                break;
            }
        }
        return ele;
    }

    /**
     * Search for the element with the id.
     *
     * @param id id of the element to be founded
     * @return the element
     */
    public HolonElement searchElementById(int id) {
        HolonElement ele = null;
        for (HolonElement e : getElements()) {
            if (e.getId() == id) {
                ele = e;
                break;
            }
        }
        return ele;
    }

  
    
    
    //New Methods:
    /**
     * This Method returns the smallest consuming HolonElement that is ACTIVE.
     * If the HolonObject has no Consumer its return null. 
     * @param timestep is the TimeStep to compare the HolonElements.
     * @return The smallest consuming HolonElement or null.
     */
    public HolonElement getMinimumConsumingElement(int timestep){
    	return getElements().stream().filter(element -> element.isActive() && (element.getEnergyAtTimeStep(timestep) < 0) ).max((lhs,rhs) -> Float.compare(lhs.getEnergyAtTimeStep(timestep), rhs.getEnergyAtTimeStep(timestep))).orElse(null);
    }
    /**
     * This Method returns the smallest consuming HolonElement'Energy that is ACTIVE.
     * If the HolonObject has no Consumer its return 0. 
     * @param timestep is the TimeStep to compare the HolonElements.
     * @return The smallest consuming HolonElement or 0.
     */
    public float getMinimumConsumingElementEnergy(int timestep){
    	return getElements().stream().filter(element -> element.isActive() && (element.getEnergyAtTimeStep(timestep) < 0) ).map(element -> -element.getEnergyAtTimeStep(timestep)).min((lhs,rhs) ->Float.compare(lhs, rhs)).orElse(0.0f);
    }
    public float getMinimumConsumingElementEnergyWithFlex(int timestep, FlexManager flexManager){
    	return getElements().stream().filter(element -> element.isOn(flexManager) && (element.getEnergyAtTimeStep(timestep) < 0) ).map(element -> -element.getEnergyAtTimeStep(timestep)).min((lhs,rhs) ->Float.compare(lhs, rhs)).orElse(0.0f);
    }
    /**
     * This Method returns the biggest consuming HolonElement'Energy that is ACTIVE.
     * If the HolonObject has no Consumer its return 0. 
     * @param timestep is the TimeStep to compare the HolonElements.
     * @return The biggest consuming HolonElement or 0.
     */
    public float getMaximumConsumingElementEnergy(int timestep){
    	return getElements().stream().filter(element -> element.isActive() && (element.getEnergyAtTimeStep(timestep) < 0) ).map(element -> -element.getEnergyAtTimeStep(timestep)).max((lhs,rhs) ->Float.compare(lhs, rhs)).orElse(0.0f);
    }
    /** 
     * This Method returns the Energy of a HolonObject. Its sums all Energies from the HolonElements of the HolonObject that are ACTIVE.
     * If the HolonObject have no HolonElement its return 0;
     * Is the returned Energy negative then the HolonObject need Energy because its consuming HolonElements need more Energy then the producing HolonElements.
     * Is the returned Energy positive its reversed.
     * @param timestep is the TimeStep to compare the HolonElements.
     * @return The Energy of the HolonObject.
     */
    public float getEnergyAtTimeStep(int timestep)
    {
    	return getElements().stream().filter(element -> element.isActive()).map(element -> element.getEnergyAtTimeStep(timestep)).reduce(0.0f, (a, b) -> a + b);
    }
    
    public float getEnergyAtTimeStepWithFlex(int timestep, FlexManager flexManager)
    {
    	return getElements().stream().filter(element -> element.isOn(flexManager)).map(element -> element.getEnergyAtTimeStep(timestep)).reduce(0.0f, (a, b) -> a + b);
    }
    
    
    public float getMaximumProductionPossible(int timestep) {
    	return elements.stream().filter(element -> element.getEnergyAtTimeStep(timestep) > 0).map(element -> element.getEnergyAtTimeStep(timestep)).reduce(0.0f, Float::sum);
    }
    public float getMaximumConsumptionPossible(int timestep) {
    	return elements.stream().filter(element -> element.getEnergyAtTimeStep(timestep) < 0).map(element -> -element.getEnergyAtTimeStep(timestep)).reduce(0.0f, Float::sum);
    }
    
    
    
    
    
    /** 
     * This Method returns the Energy that all HolonElements from the HolonObject produce by itself. Its sums all Energies from the HolonElements of the HolonObject that are ACTIVE and are Producer.
     * If the HolonObject have no HolonElement its return 0;
     * @param timestep is the TimeStep to compare the HolonElements.
     * @return The Energy of the producing HolonElements.
     */
    public float getEnergySelfProducingFromProducingElements(int timestep) {
    	return getElements().stream().filter(element -> element.isActive() && (element.getEnergyAtTimeStep(timestep) > 0)).map(element -> element.getEnergyAtTimeStep(timestep)).reduce(0.0f, (a, b) -> a + b);
    }
    public float getEnergySelfProducingFromProducingElementsWithFlex(int timestep, FlexManager flexManager) {
    	return getElements().stream().filter(element -> element.isOn(flexManager) && (element.getEnergyAtTimeStep(timestep) > 0)).map(element -> element.getEnergyAtTimeStep(timestep)).reduce(0.0f, (a, b) -> a + b);
    }
    /** 
     * This Method returns the Energy of all HolonElements from the HolonObject that are consuming. Its sums all Energies from the HolonElements of the HolonObject that are ACTIVE and are Consumer.
     * If the HolonObject have no HolonElement its return 0;
     * @param timestep is the TimeStep to compare the HolonElements.
     * @return The Energy of the consuming HolonElements.
     */
    public float getEnergyNeededFromConsumingElements(int timestep) {
    	return getElements().stream().filter(element -> element.isActive() && (element.getEnergyAtTimeStep(timestep) < 0)).map(element -> -element.getEnergyAtTimeStep(timestep)).reduce(0.0f, (a, b) -> a + b);
    }
    public float getEnergyNeededFromConsumingElementsWithFlex(int timestep, FlexManager flexManager) {
    	return getElements().stream().filter(element -> element.isOn(flexManager) && (element.getEnergyAtTimeStep(timestep) < 0)).map(element -> -element.getEnergyAtTimeStep(timestep)).reduce(0.0f, (a, b) -> a + b);
    }
    /**
     * This Method calculate the amount of HolonElements that are consuming Energy and are ACTIVE.
     * @param timestep is the TimeStep to compare the HolonElements.
     * @return The amount of HolonElements that are consuming Energy.
     */
    public int countConsumingElements(int timestep) {
    	return (int) getElements().stream().filter(element -> element.isActive() && (element.getEnergyAtTimeStep(timestep) < 0)).count();
    }
    /**
     * This Method calculate the amount of HolonElements that are producing Energy and are ACTIVE.
     * @param timestep is the TimeStep to compare the HolonElements.
     * @return The amount of HolonElements that are producing Energy.
     */
    public int countProducingElements(int timestep) {
    	return (int) getElements().stream().filter(element -> element.isActive() && (element.getEnergyAtTimeStep(timestep) > 0)).count();
    }
    



    /**
     * Get the Array Production
     */
    public float[] getTrackingProd() {
        return this.trackingProd;
    }

    /**
     * Set the Array Production
     */
    public void setTrackingProd(float[] arr) {
        this.trackingProd = arr;
    }

    /**
     * Get the Array Consumption
     */
    public float[] getTrackingCons() {
        return this.trackingCons;
    }

    /**
     * Set the Array Consumption
     */
    public void setTrackingCons(float[] arr) {
        this.trackingCons = arr;
    }

    /**
     * Get the Array Consumption
     */
    public float getTotalFlex() {
        return totalFlex;
    }

    /**
     * Set the Array Consumption
     */
    public void setTotalFlex(float totalFlex) {
        this.totalFlex = totalFlex;
    }




    /**
     * If the user track any HolonObject the tracking information will be
     * updated. (If the HolonObject enters into the untracked state, the array
     * will be reseted)
     */

    public void updateTrackingInfo() {
        float[] tempProd = new float[100];
        float[] tempCons = new float[100];
        for (int i = 0; i < 100; i++) {
            float valueProd = 0;
            float valueCons = 0;
            for (HolonElement e : getElements()) {
                if (e.isActive() && e.isProducer()) {
                    valueProd += e.getOverallEnergyAtTimeStep(i);
                }
                if (e.isActive() && e.isConsumer()) {
                    valueCons += e.getOverallEnergyAtTimeStep(i);
                }
            }
            tempProd[i] = valueProd;
            tempCons[i] = valueCons;
        }
        this.trackingProd = tempProd;
        this.trackingCons = tempCons;
    }


    public String toString() {
        StringBuilder sb = new StringBuilder();

        sb.append("[HolonObject: ");
        sb.append("id=").append(id)
                .append(", name=").append(name)
                .append(", state=");
        sb.append(", elements=[");
        for (int i = 0; i < getElements().size(); i++) {
            HolonElement el = getElements().get(i);
            if (i != 0) {
                sb.append(", ");
            }
            sb.append(el.getEleName());
        }
        sb.append("]]");

        return sb.toString();
    }
    
    @Override
	public HolonObject makeCopy(){
		return new HolonObject(this);
	}

	public int getNumberOfActiveElements() {
		return (int) elements.stream().filter(ele -> ele.isActive()).count();
	}
	
	public int getNumberOfInActiveElements() {
		return (int) elements.stream().filter(ele -> !ele.isActive()).count();
	}
	
	public int getNumberOfElements() {
		return (int) elements.stream().count();
	}
}