package ui.view;

import classes.*;
import ui.controller.Control;
import ui.model.Model;

import javax.swing.*;
import java.awt.*;
import java.awt.geom.GeneralPath;
import java.util.ArrayList;

public class StatisticGraph extends JPanel {

    /**
     *
     */
    private static final long serialVersionUID = 1L;

    // Maximum y Value
    private float maximum = 0;

    // is the Simulation running?
//	private boolean isSimRunning;
    private GeneralPath path = new GeneralPath();
    // model and controller
    private Model model;
    private Control controller;
    // Graphics2D
    private Graphics2D g2;

    // Data
    private ArrayList<TrackedDataSet> dataSets = new ArrayList<>();

    private boolean showGrid = true;

    private float DISTANCE_IN_GRAPH = 1.0f;

//	private JTable table = new JTable();
//	private ArrayList<Line2D> gridLines = new ArrayList<>();

    /**
     * Constructor.
     *
     * @param model   the Model
     * @param control the Controller
     */
    StatisticGraph(final Model model, Control control) {
        this.controller = control;
        this.model = model;
        this.setBackground(Color.WHITE);
    }

    /**
     * Paints all Components on the Canvas.
     *
     * @param g Graphics
     */
    public void paintComponent(Graphics g) {
        super.paintComponent(g);

        // Graphics2D init
        g2 = (Graphics2D) g;
        RenderingHints rh = new RenderingHints(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        g2.setRenderingHints(rh);

        // Paint the Grid if it should currently be shown
        if (showGrid) {
            g2.setStroke(new BasicStroke(0));

            g2.setColor(Color.BLACK);
            for (int i = 0; i <= this.getWidth(); i += 10) {
                g2.drawLine(i, 0, i, this.getHeight());
            }
            for (int i = 0; i <= this.getHeight(); i += 5) {
                g2.drawLine(0, i, this.getWidth(), i);
            }
        }

        g2.setStroke(new BasicStroke(3));

        // Calculate the Maximum
        // calcMaximum();

        // Calculate values for each set and add them
        // addValues();

        // Create Paths and draw them
        for (TrackedDataSet set : dataSets) {
            path.reset();
            switch (set.getProperty()) {
                case TrackedDataSet.CONSUMPTION:
                case TrackedDataSet.PRODUCTION:
                case TrackedDataSet.ACTIVATED_ELEMENTS:
                case TrackedDataSet.TOTAL_PRODUCTION:
                case TrackedDataSet.TOTAL_CONSUMPTION:
                case TrackedDataSet.WASTED_ENERGY:
                case TrackedDataSet.AMOUNT_HOLONS:
                case TrackedDataSet.GROUP_CONSUMPTION:
                case TrackedDataSet.GROUP_PRODUCTION:
                case TrackedDataSet.AMOUNT_CLOSED_SWITCHES:
                case TrackedDataSet.AVG_AMOUNT_OBJECTS_IN_HOLONS:
                case TrackedDataSet.AVG_AMOUNT_ELEMENTS_IN_HOLONS:
                case TrackedDataSet.AVG_AMOUNT_PRODUCERS_IN_HOLONS:
                case TrackedDataSet.AVG_CONSUMED_ENERGY_IN_HOLONS:
                case TrackedDataSet.AVG_WASTED_ENERGY_IN_HOLONS:
                case TrackedDataSet.AMOUNT_BROKEN_EDGES:
                case TrackedDataSet.AVG_AMOUNT_CLOSED_SWITCHES_IN_HOLONS:
                case TrackedDataSet.AVG_AMOUNT_ACTIVE_ELEMENTS_IN_HOLONS:
                case TrackedDataSet.AVG_AMOUNT_INACTIVE_ELEMENTS_IN_HOLONS:
                case TrackedDataSet.AVG_PRODUCED_ENERGY_IN_HOLONS:
                    createPathFloats(set);
                    break;
                case TrackedDataSet.ON_OFF:
                    createPathBooleans(set);
                    break;
                case TrackedDataSet.PERCENT_SUPPLIED:
                case TrackedDataSet.PERCENT_NOT_SUPPLIED:
                case TrackedDataSet.PERCENT_PARTIAL_SUPPLIED:
                case TrackedDataSet.RATIO_PRODUCERS_CONSUMERS:
                    createPathPercent(set);
                    break;
                default:
                    break;
            }
            g2.setColor(set.getColor());
            g2.draw(path);
        }
    }

    /**
     * Add an Object to the Graph if the maximum has not reached yet.
     *
     * @param set the Object to add
     */
    public void addObject(TrackedDataSet set) {
        dataSets.add(set);
    }

    /**
     * Removes an Object from the Graph.
     *
     * @param id the id of the Object to remove
     */
    public void removeObject(int id) {
        dataSets.remove(id);
    }

    /**
     * converts the number to fit the canvas.
     *
     * @param d the number to convert
     * @return the converted number
     */
    private double convertToCanvasY(float d) {
        return Math.abs((this.getHeight() - (d * (this.getHeight() / maximum))));
    }

    /**
     * Does take into account which timestep is watched, calculates the max
     * values.
     *
     * @return the currentEnergy
     */
    public float getEnergyAtCurrentTimeStep(HolonObject obj) {
        float temp = 0;
        for (HolonElement e : obj.getElements()) {
            if (e.isActive()) {
                temp = temp + e.getAvailableEnergyPerElementAt()[model.getCurIteration()];
            }
        }
        return temp;
    }

    /**
     * Calculate the Max Value of the Graph
     */
    void calcMaximum() {
        maximum = 0;
        for (TrackedDataSet set : dataSets) {
            float val = 0;
            switch (set.getProperty()) {
                case TrackedDataSet.CONSUMPTION:
                    for (HolonElement h : ((HolonObject) set.getCpsObject()).getElements()) {
                        if (h.getEnergyPerElement() < 0) {
                            val += (h.getEnergyPerElement() + h.getFlexibleEnergyAvailablePerElement()) * h.getAmount();
                        }
                    }
                    val *= -1;
                    break;
                case TrackedDataSet.PRODUCTION:
                    for (HolonElement h : ((HolonObject) set.getCpsObject()).getElements()) {
                        if (h.getEnergyPerElement() > 0) {
                            val += (h.getEnergyPerElement() + h.getFlexibleEnergyAvailablePerElement()) * h.getAmount();
                        }
                    }
                    break;
                case TrackedDataSet.ACTIVATED_ELEMENTS:
                    for (HolonElement h : ((HolonObject) set.getCpsObject()).getElements()) {
                        val += h.getAmount();
                    }
                    break;
                case TrackedDataSet.ON_OFF:
                    val = 1;
                    break;
                case TrackedDataSet.TOTAL_PRODUCTION:
                    val = getMaxTotalProduction(model.getObjectsOnCanvas());
                    break;
                case TrackedDataSet.TOTAL_CONSUMPTION:
                    val = getMaxTotalConsumption(model.getObjectsOnCanvas());
                    val *= -1;
                    break;
                case TrackedDataSet.WASTED_ENERGY:
                    val = getMaxWastedEnergy(model.getObjectsOnCanvas());
                    break;
                case TrackedDataSet.PERCENT_SUPPLIED:
                case TrackedDataSet.PERCENT_NOT_SUPPLIED:
                case TrackedDataSet.PERCENT_PARTIAL_SUPPLIED:
                    val = 1;
                    break;
                case TrackedDataSet.GROUP_PRODUCTION:
                    val = getMaxTotalProduction(((CpsUpperNode) set.getCpsObject()).getNodes());
                    break;
                case TrackedDataSet.GROUP_CONSUMPTION:
                    val = getMaxTotalConsumption(((CpsUpperNode) set.getCpsObject()).getNodes());
                    val *= -1;
                    break;
                case TrackedDataSet.AMOUNT_HOLONS:
                    val = controller.getSimManager().getSubNets().size();
                    for (int i = 0; i < model.getCurIteration(); i++) {
                        if (val < set.getValues()[i]) {
                            val = set.getValues()[i];
                        }
                    }
                    break;
                case TrackedDataSet.AMOUNT_CLOSED_SWITCHES:
                    val = model.getSwitches().size();
                    break;
                case TrackedDataSet.AVG_AMOUNT_OBJECTS_IN_HOLONS:
                    for (SubNet sub : controller.getSimManager().getSubNets()) {
                        if (val < sub.getObjects().size()) {
                            val = sub.getObjects().size();
                        }
                    }
                    break;
                case TrackedDataSet.AVG_AMOUNT_ELEMENTS_IN_HOLONS:
                case TrackedDataSet.AVG_AMOUNT_ACTIVE_ELEMENTS_IN_HOLONS:
                case TrackedDataSet.AVG_AMOUNT_INACTIVE_ELEMENTS_IN_HOLONS:
                    float eCount = 0;
                    for (SubNet sub : controller.getSimManager().getSubNets()) {
                        for (HolonObject obj : sub.getObjects()) {
                            eCount += obj.getElements().size();
                        }
                        if (val < eCount) {
                            val = eCount;
                        }
                        eCount = 0;
                    }
                    break;
                case TrackedDataSet.AVG_AMOUNT_PRODUCERS_IN_HOLONS:
                    for (SubNet sub : controller.getSimManager().getSubNets()) {
                        if (val < sub.getObjects().size()) {
                            val = sub.getObjects().size();
                        }
                    }
                    break;
                case TrackedDataSet.AVG_CONSUMED_ENERGY_IN_HOLONS:
                    for (SubNet sub : controller.getSimManager().getSubNets()) {
                        float tempVal = -getMaxTotalConsumption(new ArrayList<>(sub.getObjects()));
                        if (val < tempVal) {
                            val = tempVal;
                        }
                    }
                    break;
                case TrackedDataSet.AVG_WASTED_ENERGY_IN_HOLONS:
                    for (SubNet sub : controller.getSimManager().getSubNets()) {
                        float tempVal = getMaxTotalProduction(new ArrayList<>(sub.getObjects()));
                        if (val < tempVal) {
                            val = tempVal;
                        }
                    }
                    break;
                case TrackedDataSet.AMOUNT_BROKEN_EDGES:
                    for (SubNet sub : controller.getSimManager().getSubNets()) {
                        val += sub.getSwitches().size();
                    }
                    break;
                case TrackedDataSet.RATIO_PRODUCERS_CONSUMERS:
                    val = 1;
                    break;
                case TrackedDataSet.AVG_AMOUNT_CLOSED_SWITCHES_IN_HOLONS:
                    for (SubNet sub : controller.getSimManager().getSubNets()) {
                        float tempVal = sub.getSwitches().size();
                        if (val < tempVal) {
                            val = tempVal;
                        }
                    }
                    break;
                case TrackedDataSet.AVG_PRODUCED_ENERGY_IN_HOLONS:
                    for (SubNet sub : controller.getSimManager().getSubNets()) {
                        float tempVal = getMaxTotalProduction(new ArrayList<>(sub.getObjects()));
                        if (val < tempVal) {
                            val = tempVal;
                        }
                    }
                    break;
                default:
                    maximum = 0;
                    break;
            }
            if (val > maximum) {
                maximum = val;
            }
        }
        ((StatisticGraphPanel) this.getParent().getParent()).setMaximumLabel(maximum);
    }

    /**
     * Add the Current Values to each set
     */
    void addValues() {
        for (TrackedDataSet set : dataSets) {
            float val = 0;
            switch (set.getProperty()) {
                case TrackedDataSet.CONSUMPTION:
                    for (HolonElement h : ((HolonObject) set.getCpsObject()).getElements()) {
                        if (h.getOverallEnergyAtTimeStep(model.getCurIteration()) < 0 && h.isActive()) {
                            val += Math.abs(h.getOverallEnergyAtTimeStep(model.getCurIteration()));
                        }
                        set.setValAt(val, model.getCurIteration());
                    }
                    break;
                case TrackedDataSet.PRODUCTION:
                    for (HolonElement h : ((HolonObject) set.getCpsObject()).getElements()) {
                        if (h.getOverallEnergyAtTimeStep(model.getCurIteration()) > 0 && h.isActive()) {
                            val += Math.abs(h.getOverallEnergyAtTimeStep(model.getCurIteration()));
                        }
                        set.setValAt(val, model.getCurIteration());
                    }

                    break;
                case TrackedDataSet.ACTIVATED_ELEMENTS:
                    for (HolonElement h : ((HolonObject) set.getCpsObject()).getElements()) {
                        if (h.isActive()) {
                            val += h.getAmount();
                        }
                        set.setValAt(val, model.getCurIteration());
                    }
                    break;
                case TrackedDataSet.ON_OFF:
                    if (((HolonSwitch) set.getCpsObject()).getManualMode()) {
                        if (((HolonSwitch) set.getCpsObject()).getActiveManual()) {
                            set.setValAt(1, model.getCurIteration());
                        } else {
                            set.setValAt(0, model.getCurIteration());
                        }
                    } else {
                        if (((HolonSwitch) set.getCpsObject()).getActiveAt()[model.getCurIteration()]) {
                            set.setValAt(1, model.getCurIteration());
                        } else {
                            set.setValAt(0, model.getCurIteration());
                        }
                    }
                    break;
                case TrackedDataSet.TOTAL_PRODUCTION:
                    set.setValAt(getTotalProductionAt(model.getObjectsOnCanvas(), model.getCurIteration()),
                            model.getCurIteration());
                    break;
                case TrackedDataSet.TOTAL_CONSUMPTION:
                    set.setValAt(-getTotalConsumptionAt(model.getObjectsOnCanvas(), model.getCurIteration()),
                            model.getCurIteration());
                    break;
                case TrackedDataSet.WASTED_ENERGY:
                    float wasted = getTotalWastedEnergyAt(model.getObjectsOnCanvas(), model.getCurIteration());
                    set.setValAt(wasted, model.getCurIteration());
                    break;
                case TrackedDataSet.PERCENT_SUPPLIED:
                    set.setValAt(getPercentState(model.getObjectsOnCanvas(), HolonObject.SUPPLIED),
                            model.getCurIteration());
                    break;
                case TrackedDataSet.PERCENT_NOT_SUPPLIED:
                    set.setValAt(getPercentState(model.getObjectsOnCanvas(), HolonObject.NOT_SUPPLIED),
                            model.getCurIteration());
                    break;
                case TrackedDataSet.PERCENT_PARTIAL_SUPPLIED:
                    set.setValAt(getPercentState(model.getObjectsOnCanvas(), HolonObject.PARTIALLY_SUPPLIED),
                            model.getCurIteration());
                    break;
                case TrackedDataSet.GROUP_PRODUCTION:
                    set.setValAt(
                            getTotalProductionAt(((CpsUpperNode) set.getCpsObject()).getNodes(), model.getCurIteration()),
                            model.getCurIteration());
                    break;
                case TrackedDataSet.GROUP_CONSUMPTION:
                    set.setValAt(
                            -getTotalConsumptionAt(((CpsUpperNode) set.getCpsObject()).getNodes(), model.getCurIteration()),
                            model.getCurIteration());
                    break;
                case TrackedDataSet.AMOUNT_HOLONS:
                    set.setValAt(controller.getSimManager().getSubNets().size(), model.getCurIteration());
                    break;
                case TrackedDataSet.AMOUNT_CLOSED_SWITCHES:
                    for (HolonSwitch s : model.getSwitches()) {
                        if (s.getState(model.getCurIteration())) {
                            val++;
                        }
                    }
                    set.setValAt(val, model.getCurIteration());
                    break;
                case TrackedDataSet.AVG_AMOUNT_OBJECTS_IN_HOLONS:
                    for (SubNet sub : controller.getSimManager().getSubNets()) {
                        val += sub.getObjects().size();
                    }
                    val /= controller.getSimManager().getSubNets().size();
                    set.setValAt(val, model.getCurIteration());
                    break;
                case TrackedDataSet.AVG_AMOUNT_ELEMENTS_IN_HOLONS:
                    for (SubNet sub : controller.getSimManager().getSubNets()) {
                        for (HolonObject obj : sub.getObjects()) {
                            val += obj.getElements().size();
                        }
                    }
                    val /= controller.getSimManager().getSubNets().size();
                    set.setValAt(val, model.getCurIteration());
                    break;
                case TrackedDataSet.AVG_AMOUNT_PRODUCERS_IN_HOLONS:
                    for (SubNet sub : controller.getSimManager().getSubNets()) {
                        for (HolonObject obj : sub.getObjects()) {
                            if (obj.getCurrentEnergyAtTimeStep(model.getCurIteration()) > 0) {
                                val++;
                            }
                        }
                    }
                    val /= controller.getSimManager().getSubNets().size();
                    set.setValAt(val, model.getCurIteration());
                    break;
                case TrackedDataSet.AVG_CONSUMED_ENERGY_IN_HOLONS:
                    for (SubNet sub : controller.getSimManager().getSubNets()) {
                        val += -getTotalConsumptionAt(new ArrayList<>(sub.getObjects()),
                                model.getCurIteration());
                    }
                    val /= controller.getSimManager().getSubNets().size();
                    set.setValAt(val, model.getCurIteration());
                    break;
                case TrackedDataSet.AVG_WASTED_ENERGY_IN_HOLONS:
                    for (SubNet sub : controller.getSimManager().getSubNets()) {
                        val += (getTotalProductionAt(new ArrayList<>(sub.getObjects()),
                                model.getCurIteration())
                                + getTotalConsumptionAt(new ArrayList<>(sub.getObjects()),
                                model.getCurIteration()));
                    }
                    val /= controller.getSimManager().getSubNets().size();
                    set.setValAt(val, model.getCurIteration());
                    break;
                case TrackedDataSet.AMOUNT_BROKEN_EDGES:
                    for (SubNet sub : controller.getSimManager().getSubNets()) {
                        for (HolonSwitch s : sub.getSwitches()) {
                            if (s.getState()) {
                                val++;
                            }
                        }
                    }
                    set.setValAt(val, model.getCurIteration());
                    break;
                case TrackedDataSet.RATIO_PRODUCERS_CONSUMERS:
                    float prod = 0;
                    float cons = 0;
                    for (SubNet sub : controller.getSimManager().getSubNets()) {
                        for (HolonObject obj : sub.getObjects()) {
                            if (obj.getCurrentEnergyAtTimeStep(model.getCurIteration()) > 0) {
                                prod++;
                            } else if (obj.getCurrentEnergyAtTimeStep(model.getCurIteration()) < 0) {
                                cons++;
                            }
                        }
                    }
                    val = prod / (prod + cons);
                    set.setValAt(val, model.getCurIteration());
                    break;
                case TrackedDataSet.AVG_AMOUNT_CLOSED_SWITCHES_IN_HOLONS:
                    for (SubNet sub : controller.getSimManager().getSubNets()) {
                        val += sub.getSwitches().size();
                    }
                    val /= controller.getSimManager().getSubNets().size();
                    set.setValAt(val, model.getCurIteration());
                    break;
                case TrackedDataSet.AVG_AMOUNT_ACTIVE_ELEMENTS_IN_HOLONS:
                    for (SubNet sub : controller.getSimManager().getSubNets()) {
                        for (HolonObject obj : sub.getObjects()) {
                            for (HolonElement ele : obj.getElements()) {
                                if (ele.isActive()) {
                                    val++;
                                }
                            }
                        }
                    }
                    val /= controller.getSimManager().getSubNets().size();
                    set.setValAt(val, model.getCurIteration());
                    break;
                case TrackedDataSet.AVG_AMOUNT_INACTIVE_ELEMENTS_IN_HOLONS:
                    for (SubNet sub : controller.getSimManager().getSubNets()) {
                        for (HolonObject obj : sub.getObjects()) {
                            for (HolonElement ele : obj.getElements()) {
                                if (!ele.isActive()) {
                                    val++;
                                }
                            }
                        }
                    }
                    val /= controller.getSimManager().getSubNets().size();
                    set.setValAt(val, model.getCurIteration());
                    break;
                case TrackedDataSet.AVG_PRODUCED_ENERGY_IN_HOLONS:
                    for (SubNet sub : controller.getSimManager().getSubNets()) {
                        val += getTotalProductionAt(new ArrayList<>(sub.getObjects()),
                                model.getCurIteration());
                    }
                    val /= controller.getSimManager().getSubNets().size();
                    set.setValAt(val, model.getCurIteration());
                    break;
                default:
                    break;
            }
        }
    }

    /**
     * create Path with floats
     *
     * @param set tracked data
     */
    private void createPathFloats(TrackedDataSet set) {
        int range = model.getCurIteration(); //to which iteration
        if (!model.getIsSimRunning()) {
            range = model.getIterations() - 1;
        }
        if (set.getValues()[0] != -1) {
            path.moveTo(0, convertToCanvasY(set.getValues()[0]));
        } else {
            path.moveTo(DISTANCE_IN_GRAPH * this.getWidth() / model.getIterations(), convertToCanvasY(set.getValues()[1]));
        }
        for (int i = 0; i < range; i++) {
            if (set.getValues()[i + 1] != -1) {
                path.lineTo((i + 1) * this.getWidth() / model.getIterations(),
                        convertToCanvasY(set.getValues()[i + 1]));
            } else {
                if (i + 2 < range) {
                    path.moveTo((i + 2) * this.getWidth() / model.getIterations(),
                            convertToCanvasY(set.getValues()[i + 2]));
                }
            }
        }
    }

    /**
     * create Path with booleans(0 and 1)
     *
     * @param set tracked data
     */
    private void createPathBooleans(TrackedDataSet set) {
        if (set.getValues()[0] != -1) {
            path.moveTo(0, convertToCanvasY((set.getValues()[0] * (maximum / 3 * 2)) + (maximum / 6)));
        } else {
            path.moveTo(DISTANCE_IN_GRAPH * this.getWidth() / model.getIterations(),
                    convertToCanvasY((set.getValues()[1] * (maximum / 3 * 2)) + (maximum / 6)));
        }
        for (int i = 0; i < model.getCurIteration(); i++) {
            controller.addTextToConsole(set.getValues()[i] + "");
            if (set.getValues()[i + 1] != -1) {
                path.lineTo((i + 1) * this.getWidth() / model.getIterations(),
                        convertToCanvasY((set.getValues()[i + 1] * (maximum / 3 * 2)) + (maximum / 6)));
            } else {
                if (i + 2 < model.getCurIteration()) {
                    path.moveTo((i + 2) * this.getWidth() / model.getIterations(),
                            convertToCanvasY((set.getValues()[i + 2] * (maximum / 3 * 2)) + (maximum / 6)));
                }
            }
        }
    }

    /**
     * create Path for percent values
     *
     * @param set tracked data
     */
    private void createPathPercent(TrackedDataSet set) {
        if (set.getValues()[0] != -1) {
            path.moveTo(0, convertToCanvasY(set.getValues()[0] * maximum));
        } else {
            path.moveTo(DISTANCE_IN_GRAPH * this.getWidth() / model.getIterations(), convertToCanvasY(set.getValues()[1] * maximum));
        }
        for (int i = 0; i < model.getCurIteration(); i++) {
            if (set.getValues()[i + 1] != -1) {
                path.lineTo((i + 1) * this.getWidth() / model.getIterations(),
                        convertToCanvasY(set.getValues()[i + 1] * maximum));
            } else {
                if (i + 2 < model.getCurIteration()) {
                    path.moveTo((i + 2) * this.getWidth() / model.getIterations(),
                            convertToCanvasY(set.getValues()[i + 2] * maximum));
                }
            }

        }
    }

    /**
     * get the max total production of the given Objects
     */
    private float getMaxTotalProduction(ArrayList<AbstractCpsObject> objects) {
        float val = 0;

        for (AbstractCpsObject obj : objects) {
            if (obj instanceof HolonObject) {
                for (HolonElement ele : ((HolonObject) obj).getElements()) {
                    if (ele.getEnergyPerElement() > 0) {
                        val += (ele.getEnergyPerElement() + ele.getFlexibleEnergyAvailablePerElement()) * ele.getAmount();
                    }
                }
            } else if (obj instanceof CpsUpperNode) {
                val += getMaxTotalProduction(((CpsUpperNode) obj).getNodes());
            }
        }
        return val;
    }

    /**
     * get the max total consumption of the given Objects
     */
    private float getMaxTotalConsumption(ArrayList<AbstractCpsObject> objects) {
        float val = 0;

        for (AbstractCpsObject obj : objects) {
            if (obj instanceof HolonObject) {
                for (HolonElement ele : ((HolonObject) obj).getElements()) {
                    if (ele.getEnergyPerElement() < 0) {
                        val += (ele.getEnergyPerElement() + ele.getFlexibleEnergyAvailablePerElement()) * ele.getAmount();
                    }
                }
            } else if (obj instanceof CpsUpperNode) {
                val += getMaxTotalConsumption(((CpsUpperNode) obj).getNodes());
            }
        }
        return val;
    }


    /**
     * get the max total wasted energy of the given Objects
     * if it is smaller than 0, return 0
     */
    private float getMaxWastedEnergy(ArrayList<AbstractCpsObject> objects) {
        float val = 0;

        for (AbstractCpsObject obj : objects) {
            if (obj instanceof HolonObject) {
                for (HolonElement ele : ((HolonObject) obj).getElements()) {
                    val += (ele.getEnergyPerElement() + ele.getFlexibleEnergyAvailablePerElement()) * ele.getAmount();
                }
            } else if (obj instanceof CpsUpperNode) {
                val += getMaxWastedEnergy(((CpsUpperNode) obj).getNodes());
            }
        }

        return val < 0 ? 0 : val;
    }


    /**
     * get the max total production of the given objects at the given timestep
     */
    private float getTotalProductionAt(ArrayList<AbstractCpsObject> objects, int tStep) {
        float val = 0;

        for (AbstractCpsObject obj : objects) {
            if (obj instanceof HolonObject) {
                for (HolonElement ele : ((HolonObject) obj).getElements()) {
                    if (ele.getOverallEnergyAtTimeStep(tStep) > 0 && ele.isActive()) {
                        val += ele.getOverallEnergyAtTimeStep(tStep);
                    }
                }
            } else if (obj instanceof CpsUpperNode) {
                val += getTotalProductionAt(((CpsUpperNode) obj).getNodes(), tStep);
            }
        }
        return val;
    }

    /**
     * get the total consumption of the given objects at the given timestep
     */
    private float getTotalConsumptionAt(ArrayList<AbstractCpsObject> objects, int tStep) {
        float val = 0;

        for (AbstractCpsObject obj : objects) {
            if (obj instanceof HolonObject) {
                for (HolonElement ele : ((HolonObject) obj).getElements()) {
                    if (ele.getAvailableEnergyPerElementAt()[tStep] < 0 && ele.isActive()) {
                        val += ele.getOverallEnergyAtTimeStep(tStep);
                    }
                }
            } else if (obj instanceof CpsUpperNode) {
                val += getTotalConsumptionAt(((CpsUpperNode) obj).getNodes(), tStep);
            }
        }
        return val;
    }

    /**
     * get the total wasted energy of the given objects at the given timestep
     */
    private float getTotalWastedEnergyAt(ArrayList<AbstractCpsObject> objects, int tStep) {
        float val = 0;

        for (AbstractCpsObject obj : objects) {
            if (obj instanceof HolonObject) {
                for (HolonElement ele : ((HolonObject) obj).getElements()) {
                    if (ele.isActive()) {
                        val += ele.getOverallEnergyAtTimeStep(tStep);
                    }
                }
            } else if (obj instanceof CpsUpperNode) {
                val += getTotalWastedEnergyAt(((CpsUpperNode) obj).getNodes(), tStep);
            }
        }

        return val;
    }


    /**
     * get the Percentage of how many objects with the given state are in the
     * given List
     *
     * @param objects List of Objects
     */
    private float getPercentState(ArrayList<AbstractCpsObject> objects, int state) {
        float count = controller.getNumberHolonObjects(objects);
        float stateObjectss = controller.getNumberStateObjects(objects, state);

        return stateObjectss / count;
    }

    /**
     * Return all TrackedDataSets
     *
     * @return ArrayList of TrackedDataSet
     */
    public ArrayList<TrackedDataSet> getDataSets() {
        return dataSets;
    }

    /**
     * Reset the Graph. Delete all calculated values.
     */
    void resetGraph() {
        for (TrackedDataSet s : dataSets) {
            s.resetValues();
        }
    }

    void hideGrid() {
        Graphics g = this.getGraphics();
        showGrid = false;
        // clear the area
        g.clearRect(0, 0, this.getWidth(), this.getHeight());
        paintComponent(g);
    }

    void showGrid() {
        Graphics g = this.getGraphics();
        showGrid = true;
        paintComponent(g);
    }


}