package classes;

import com.google.gson.annotations.Expose;

import java.awt.*;
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 AbstractCpsObject {
    /*
     * the constants showing a HolonObject's current state
     * (whether it needs no energy, whether it is not supplied, fully supplied,
     * a producer, partially supplied or over-supplied)
     */
    public final static int NO_ENERGY = 0;
    public final static int NOT_SUPPLIED = 1;
    public final static int SUPPLIED = 2;
    public final static int PRODUCER = 3;
    public final static int PARTIALLY_SUPPLIED = 4;
    public final static int OVER_SUPPLIED = 5;
    /*
     * Color of the actual state (red = no supplied, yellow = partially supplied
     * and green = supplied)
     */
    @Expose
    private Color stateColor;
    /* Array of all consumers */
    private ArrayList<HolonElement> elements;
    /* Total of consumption */
    @Expose
    private float currentEnergy;
    /* Array for tracking Production */
    private float[] trackingProd;
    /* Array for tracking Consumption */
    private float[] trackingCons;
    /* Total Flexibility */
    private float totalFlex;
    @Expose
    private int state = 0;

    /**
     * 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<>());
        setState();
        setTrackingProd(new float[100]);
        setTrackingCons(new float[100]);
    }

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

    /**
     * sets the State, whether object is a producer, zero Energy, supplied,
     * partially or over-supplied
     */
    public void setState() {
        if (getCurrentEnergy() > 0) {
            setState(PRODUCER);
            stateColor = Color.lightGray;
        } else {
            if (getCurrentEnergy() == 0) {
                setState(NO_ENERGY);
                stateColor = Color.WHITE;
            } else {
                if (checkIfPartiallySupplied(0)) {
                    stateColor = Color.yellow;
                } else {
                    stateColor = new Color(230, 120, 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);
    }

    /**
     * Doesn't take into account which timestep is watched, calculates the max
     * values.
     *
     * @return the currentEnergy
     */
    public float getCurrentEnergy() {
        float temp = 0;
        for (HolonElement e : getElements()) {
            if (e.isActive()) {
                temp += e.getOverallEnergy();
            }
        }
        currentEnergy = temp;
        return currentEnergy;
    }

    /**
     * Getter for the current energy at a given timestep.
     *
     * @param x timestep
     * @return corresponding energy
     */
    public float getCurrentEnergyAtTimeStep(int x) {
        float temp = 0;
        for (HolonElement e : getElements()) {
            if (e.isActive()) {
                temp += e.getOverallEnergyAtTimeStep(x);
            }
        }
        currentEnergy = temp;
        return currentEnergy;
    }

    /**
     * Getter for the current energy at a given timestep.
     *
     * @param x timestep
     * @return corresponding energy
     */
    public float getCurrentEnergyAtTimeStepWithoutFlexiblesAndResetFlexibles(int x) {
        float temp = 0;
        for (HolonElement e : getElements()) {
            if (e.isActive() && !e.isFlexible()) {
                temp += e.getOverallEnergyAtTimeStep(x);
            } else if (e.isFlexible()) {
                e.setEnergyPerElement(0);
            }
        }
        currentEnergy = temp;
        return currentEnergy;
    }

    /**
     * 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;
    }

    /**
     * Get the state of the Object.
     *
     * @return state the State of the Element
     */
    public int getState() {
        return this.state;
    }

    /**
     * Set the state of the Object.
     *
     * @param state boolean if the Object is fully supplied
     */
    public void setState(int state) {
        this.state = state;
        switch (state) {
            case NO_ENERGY:
                stateColor = Color.WHITE;
                break;

            case NOT_SUPPLIED:
                stateColor = new Color(230, 120, 100);
                break;

            case SUPPLIED:
                stateColor = Color.GREEN;
                break;

            case PRODUCER:
                stateColor = Color.lightGray;
                break;

            case PARTIALLY_SUPPLIED:
                stateColor = Color.YELLOW;
                break;

            case OVER_SUPPLIED:
                // find different purple-tones at
                // http://www.rapidtables.com/web/color/purple-color.htm
                stateColor = new Color(138, 43, 226);
                break;
        }
    }

    /**
     * Search for the 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;
            }
        }
        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;
            }
        }
        return ele;
    }

    /**
     * Check if Partially Supplied.
     *
     * @param x current Iteration
     * @return boolean is partially supplied
     */
    public boolean checkIfPartiallySupplied(int x) {
        if (getElements().size() == 0) {
            return false;
        }
        float minConsum = getElements().get(0).getOverallEnergyAtTimeStep(x);
        float prod = 0;
        for (HolonElement e : getElements()) {
            if (e.isActive()) {
                float overallEnergy = e.getOverallEnergyAtTimeStep(x);
                if (overallEnergy > 0) {
                    prod = prod + overallEnergy;
                }
                if (minConsum < 0 && (overallEnergy > minConsum && overallEnergy < 0)) {
                    minConsum = overallEnergy;
                } else if (minConsum >= 0 && overallEnergy < minConsum) {
                    minConsum = overallEnergy;
                }
            }
        }
        // System.out.println("minCons: " + minConsum + " prod: " + prod);
        if (minConsum < 0 && prod >= -minConsum) {
            return true;
        } else {
            return false;
        }
    }
    
    /**
     * Calculates the minimumEnergy Needed to turn on atleast on device
     * of this HolonObject
     * @param x Timestep of the calculation
     * @return minEnergy, -inf if no Devices are consuming power
     */
    public float getMinEnergy(int x) {
        if (getElements().size() == 0) {
            return Float.NEGATIVE_INFINITY;
        }
        float minConsum = Float.NEGATIVE_INFINITY;
        for (HolonElement e : getElements()) {
            if (e.isActive()) {
                float overallEnergy = e.getOverallEnergyAtTimeStep(x);
                if (minConsum < 0 && (overallEnergy > minConsum && overallEnergy < 0)) {
                    minConsum = overallEnergy;
                } 
            }
        }
        return minConsum;
    }

    /**
     * Get the Color.
     *
     * @return stateColor the Color
     */
    public Color getColor() {
        return stateColor;
    }

    /**
     * Set the State Color.
     *
     * @param color the Color
     */
    public void setColor(Color color) {
        stateColor = color;
    }

    /**
     * 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;
    }

    /**
     * Update the totalFlex
     */
    public void updateTotalFlex() {
        float tempFlex = 0;
        for (HolonElement e : getElements()) {
            if (e.isFlexible()) {
                tempFlex += e.getFlexibleEnergyAvailablePerElement() * e.getAmount();
            }
        }
        this.totalFlex = tempFlex;
    }

    /**
     * calculates total flexible Production
     */
    public float getFlexProd() {
        float tempFlex = 0;
        for (HolonElement e : getElements()) {
            if (e.getFlexibleEnergyAvailablePerElement() > 0) {
                tempFlex += e.getFlexibleEnergyAvailablePerElement() * e.getAmount();
            }
        }
        return tempFlex;
    }

    /**
     * calculates total flexible Concumption
     */
    public float getFlexCons() {
        float tempFlex = 0;
        for (HolonElement e : getElements()) {
            if (e.getFlexibleEnergyAvailablePerElement() < 0) {
                tempFlex += e.getFlexibleEnergyAvailablePerElement() * e.getAmount();
            }
        }
        return tempFlex;
    }

    /**
     * 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.getSign() == '+') {
                    valueProd = valueProd + e.getOverallEnergyAtTimeStep(i);
                }
                if (e.isActive() && e.getSign() == '-') {
                    valueCons = 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=");
        switch (state) {
            case NO_ENERGY:
                sb.append("NO_ENERGY");
                break;
            case NOT_SUPPLIED:
                sb.append("NOT_SUPPLIED");
                break;
            case SUPPLIED:
                sb.append("SUPPLIED");
                break;
            case PRODUCER:
                sb.append("PRODUCER");
                break;
            case PARTIALLY_SUPPLIED:
                sb.append("PARTIALLY_SUPPLIED");
                break;
            case OVER_SUPPLIED:
                sb.append("OVER_SUPPLIED");
                break;
        }
        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();
    }
}