package ui.model;

import TypeAdapter.AbstractCpsObjectAdapter;
import TypeAdapter.ColorAdapter;
import TypeAdapter.PairAdapter;
import TypeAdapter.PositionAdapter;
import classes.*;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonObject;
import interfaces.CategoryListener;
import interfaces.GraphListener;
import interfaces.ObjectListener;
import ui.view.*;

import javax.swing.*;
import java.awt.*;
import java.util.*;
import java.util.List;

/**
 * The Class Model is the class where everything is saved. All changes made to
 * the Data is managed via a controller.
 *
 * @author Gruppe14
 */
public class Model {

    private static final int ITERATIONS = 100;
    // Global Variables
    private static int sCALE = 50; // Picture Scale
    private static int sCALEdIV2 = sCALE / 2;
    private static int holonBodysCALE = 100; // Picture Scale
    public String[] colNames = {"Field", "Information"};
    // Canvas Attributes
    private String imgPath = "";
    private int backgroundMode = 0;
    private int backgroundWidth = 0;
    private int backgroundHeight = 0;
    private int canvasX = 1000;
    private int canvasY = 1000;
    private int curIteration = 0;
    private LinkedList<Color> subNetColors = new LinkedList<>();
    // ID of the Selected Object
    private AbstractCpsObject selectedCpsObject = null;
    private HolonElement selectedHolonElement;
    private CpsEdge selectedEdge;
    private ArrayList<AbstractCpsObject> selectedObjects = new ArrayList<>();
    private ArrayList<AbstractCpsObject> clipboardObjects = new ArrayList<>();
    private Console console;
    private HashMap<Integer, ArrayList<HolonElement>> eleToDelete;
    // Capacity for Edge
    private float maxCapacity;
    // Table for HolonElements --> all cells are editable
    private JTable tableHolonElement;
    private ArrayList<GraphListener> graphListeners = new ArrayList();
    // Iteration Speed
    private int timerSpeed = 1000;
    private int selectedID = 0;
    // number of the current autosave
    private int autoSaveNr = -1;
    // number of max simultaneous autosaves
    private int numberOfSaves = 35;
    // whether the simulation is running and has not been reseted
    private boolean isSimRunning = false;
    // whether the console log of the program should be displayed
    private boolean showConsoleLog = true;
    // whether the console log of the program should be displayed
    private boolean useFlexibleDevices = true;

    /*
     * Array of all categories in the model. It is set by default with the
     * categories ENERGY, BUILDINGS and COMPONENTS
     */
    private ArrayList<Category> categories;
    /*
     * Array of all HolonObj and HolonSwitches, that should be tracked through
     * out the statistics tab
     */
    private ArrayList<AbstractCpsObject> trackedObjects;
    /*
     * Array of all CpsObjects in our canvas. It is set by default as an empty
     * list.
     */
    private ArrayList<AbstractCpsObject> objectsOnCanvas;
    private HashMap<String, Integer> cgIdx;
    private HashMap<Integer, Integer> cvsObjIdx;
    /*
     * Array of all CpsObjects in our canvas. It is set by default as an empty
     * list.
     */
    private ArrayList<CpsEdge> edgesOnCanvas;
    /*
     * Array for all Listeners
     */
    private List<CategoryListener> categoryListeners;
    private List<ObjectListener> objectListeners;
    private PropertyTable tableModelHolonElementMulti;
    private PropertyTable tableModelHolonElementSingle;
    private DefaulTable tableModelProperties;
    /*
     * Object that runs the Algorithm
     */
    private Object algorithm = null;
    private int selectedHolonBody;

    // Statistic Graph Data
    private Hashtable<String, StatisticGraphPanel> statisticGraphTable = new Hashtable<>();
    private HashMap<Integer, CpsUpperNode> hashcodeMap = new HashMap<>();
    private ArrayList<JsonObject> statisticData = new ArrayList<>();

    private Gson gson;

    private StatisticPanel statPanel;

    /**
     * Constructor for the model. It initializes the categories and
     * objectsOnCanvas by default values. Listeners are also initialized by
     * default values.
     */
    public Model() {
        setCategories(new ArrayList<>());
        setObjectsOnCanvas(new ArrayList<>());
        setEdgesOnCanvas(new ArrayList<>());
        setCategoryListeners(new LinkedList<>());
        setObjectListeners(new LinkedList<>());
        setCgIdx(new HashMap<>());
        setCvsObjIdx(new HashMap<>());
        setClipboradObjects(new ArrayList<>());
        setTrackingObj(new ArrayList<>());
        setEleToDelete(new HashMap<>());
        setSingleTable(new PropertyTable());
        setMultiTable(new PropertyTable());
        setPropertyTable(new DefaulTable(1000, colNames.length));
        getPropertyTable().setColumnIdentifiers(colNames);
        setTableHolonElement(new JTable());
        initGson();
    }

    /**
     * Returns all Categories.
     *
     * @return the categories
     */
    public ArrayList<Category> getCategories() {
        return categories;
    }

    /**
     * Sets all Categories.
     *
     * @param categories the categories to set
     */
    public void setCategories(ArrayList<Category> categories) {
        this.categories = categories;
    }

    /**
     * Transform the Arraylist of categories into a string of all objectName
     * with a separation (',') between each name.
     *
     * @return String of all names separeted by ','
     */
    public String toStringCat() {
        String text = "";
        for (int i = 0; i < categories.size(); i++) {
            if (text.equals("")) {
                text = categories.get(i).getName();
            } else {
                text = text + ", " + categories.get(i).getName();
            }
        }
        return text;
    }

    /**
     * Returns all Objects on the Canvas.
     *
     * @return the objectsOnCanvas
     */
    public ArrayList<AbstractCpsObject> getObjectsOnCanvas() {
        return objectsOnCanvas;
    }

    /**
     * Sets all Objects on the Canvas.
     *
     * @param objectsOnCanvas the objectsOnCanvas to set
     */
    public void setObjectsOnCanvas(ArrayList<AbstractCpsObject> objectsOnCanvas) {
        this.objectsOnCanvas = objectsOnCanvas;
    }

    /**
     * Get all Edges on the Canvas.
     *
     * @return the objectsOnCanvas
     */
    public ArrayList<CpsEdge> getEdgesOnCanvas() {
        return edgesOnCanvas;
    }

    /**
     * Sets the edges on the Canvas.
     *
     * @param arrayList the edgesOnCanvas to set
     */
    public void setEdgesOnCanvas(ArrayList<CpsEdge> arrayList) {
        this.edgesOnCanvas = arrayList;
    }

    /**
     * Adds an Edge to The Canvas.
     *
     * @param edge the edgesOnCanvas to add
     */
    public void addEdgeOnCanvas(CpsEdge edge) {
        this.edgesOnCanvas.add(edge);
    }

    /**
     * Remove an edge from the Canvas.
     *
     * @param edge the edge to remove
     */
    public void removeEdgesOnCanvas(CpsEdge edge) {
        this.edgesOnCanvas.remove(edge);
    }

    /**
     * Returns the ObjectListener.
     *
     * @return the objectListeners
     */
    public List<ObjectListener> getObjectListeners() {
        return objectListeners;
    }

    /**
     * Sets the ObjectListener.
     *
     * @param linkedList the objectListeners to set
     */
    public void setObjectListeners(LinkedList<ObjectListener> linkedList) {
        this.objectListeners = linkedList;
    }

    /**
     * Returns the CategorieListener.
     *
     * @return the categoryListeners
     */
    public List<CategoryListener> getCategoryListeners() {
        return categoryListeners;
    }

    /**
     * Sets the CategorieListener.
     *
     * @param linkedList the categoryListeners to set
     */
    public void setCategoryListeners(LinkedList<CategoryListener> linkedList) {
        this.categoryListeners = linkedList;
    }

    /**
     * Returns the ID of the selected Object 0 = no Object is selected.
     *
     * @return ID
     */
    public int getSelectedObjectID() {
        return selectedID;
    }

    /**
     * Set the ID of the selected Object 0 = no Object is selected.
     *
     * @param id the ID
     */
    public void setSelectedObjectID(int id) {
        this.selectedID = id;
    }

    /**
     * Returns the Selected Cps Object.
     *
     * @return selected Cps Object
     */
    public AbstractCpsObject getSelectedCpsObject() {
        return selectedCpsObject;
    }

    /**
     * Set the Selected Objecs.
     *
     * @param selectedCpsObject Objects that are selected
     */
    public void setSelectedCpsObject(AbstractCpsObject selectedCpsObject) {
        this.selectedCpsObject = selectedCpsObject;
    }

    /**
     * Returns all selected Objects on the Canvas.
     *
     * @return The selected Objects
     */
    public ArrayList<AbstractCpsObject> getSelectedCpsObjects() {
        return selectedObjects;
    }

    /**
     * Returns all selected Objects on the Canvas.
     *
     * @return The selected Objects
     */
    public void setSelectedCpsObjects(ArrayList<AbstractCpsObject> arr) {
        this.selectedObjects = arr;
    }

    /**
     * Returns the Selected Holon Element.
     *
     * @return selected Holon Element
     */
    public HolonElement getSelectedHolonElement() {
        return selectedHolonElement;
    }

    /**
     * Sets the Selecte HolonElement.
     *
     * @param selectedHolonElement that is Selected
     */
    public void setSelectedHolonElement(HolonElement selectedHolonElement) {
        this.selectedHolonElement = selectedHolonElement;
    }

    /**
     * Returns the sCale (Scale for the Images).
     *
     * @return sCALE
     */
    public int getScale() {
        return sCALE;
    }

    /**
     * Sets the Image Scale.
     *
     * @param scale for the image
     */
    public void setScale(int scale) {
        sCALE = scale;
        if ((sCALE & 1) == 0)
            sCALEdIV2 = sCALE / 2;
        else
            sCALEdIV2 = (sCALE + 1) / 2;
    }

    /**
     * Returns sCALEdIV2 (The Scale divided by 2).
     *
     * @return sCALEdIV2
     */
    public int getScaleDiv2() {
        return sCALEdIV2;
    }

    /**
     * Returns ITERATIONS.
     *
     * @return ITERATIONS
     */
    public int getIterations() {
        return ITERATIONS;
    }

    private void notifyGraphListeners() {
        for (GraphListener gl : graphListeners) {
            gl.repaintTree();
        }

    }

    /**
     * Returns cURiTERATION.
     *
     * @return cURiTERATION
     */
    public int getCurIteration() {
        return curIteration;
    }

    /**
     * sets the current Iteration.
     *
     * @param curIT the current Iteration
     */
    public void setCurIteration(int curIT) {
        this.curIteration = curIT;
        notifyGraphListeners();
    }

    /**
     * Returns the selected Edge.
     *
     * @return selectedEdge
     */
    public CpsEdge getSelectedEdge() {
        return selectedEdge;
    }

    /**
     * Set the selected Edge.
     *
     * @param edge that is selected
     */
    public void setSelectedEdge(CpsEdge edge) {
        this.selectedEdge = edge;
    }

    /**
     * Returns the Categorie Index.
     *
     * @return the cgIdx
     */
    public HashMap<String, Integer> getCgIdx() {
        return cgIdx;
    }

    /**
     * Sets the Categorie Index.
     *
     * @param cgIdx the cgIdx to set
     */
    public void setCgIdx(HashMap<String, Integer> cgIdx) {
        this.cgIdx = cgIdx;
    }

    /**
     * Returns the CanvasObject Index.
     *
     * @return the cvsObjIdx
     */
    public HashMap<Integer, Integer> getCvsObjIdx() {
        return cvsObjIdx;
    }

    /**
     * Sets the CanvasObject Index.
     *
     * @param cvsObjIdx the cvsObjIdx to set
     */
    public void setCvsObjIdx(HashMap<Integer, Integer> cvsObjIdx) {
        this.cvsObjIdx = cvsObjIdx;
    }

    /**
     * Returns the auto save Number.
     *
     * @return the auto save Number
     */
    public int getAutoSaveNr() {
        return autoSaveNr;
    }

    /**
     * Sets the auto save Number.
     *
     * @param autoSaveNr the auto save number
     */
    public void setAutoSaveNr(int autoSaveNr) {
        this.autoSaveNr = autoSaveNr;
    }

    /**
     * Returns the Number of Saves.
     *
     * @return the numberOfSaves
     */
    public int getNumberOfSaves() {
        return numberOfSaves;
    }

    /**
     * Set the Number of Saves.
     *
     * @param numberOfSaves the numberOfSaves to set
     */
    public void setNumberOfSaves(int numberOfSaves) {
        this.numberOfSaves = numberOfSaves;
    }

    /**
     * Returns all Objects in the Clipboard.
     *
     * @return Objects in the Clipboard
     */
    public ArrayList<AbstractCpsObject> getClipboradObjects() {
        return clipboardObjects;
    }

    /**
     * Sets the ClipboardObjects.
     *
     * @param c Array of Objects
     */
    public void setClipboradObjects(ArrayList<AbstractCpsObject> c) {
        this.clipboardObjects = c;
    }

    /**
     * Returns the Console.
     *
     * @return console the console
     */
    public Console getConsole() {
        return console;
    }

    /**
     * Sets the console.
     *
     * @param console the console
     */
    public void setConsole(Console console) {
        this.console = console;
    }

    /**
     * @return the maxCapacity
     */
    public float getMaxCapacity() {
        return maxCapacity;
    }

    /**
     * @param maxCapacity the maxCapacity to set
     */
    public void setMaxCapacity(float maxCapacity) {
        this.maxCapacity = maxCapacity;
    }

    /**
     * get the Interval in ms between each Iteration.
     *
     * @return timerSpeed speed for the Iterations
     */
    public int getTimerSpeed() {
        return this.timerSpeed;
    }

    /**
     * Sets the Interval in ms between each Iteration.
     *
     * @param t speed for the Iterations
     */
    public void setTimerSpeed(int t) {
        this.timerSpeed = t;
    }

    /**
     * Get Canvas X Size.
     *
     * @return the cANVAS_X
     */
    public int getCanvasX() {
        return canvasX;
    }

    /**
     * Set Canvas X Size.
     *
     * @param canvasX the cANVAS_X to set
     */
    public void setCanvasX(int canvasX) {
        this.canvasX = canvasX;
    }

    /**
     * get Canvas Y size.
     *
     * @return the cANVAS_Y
     */
    public int getCanvasY() {
        return canvasY;
    }

    /**
     * Set Canvas Y size.
     *
     * @param canvasY the cANVAS_Y to set
     */
    public void setCanvasY(int canvasY) {
        this.canvasY = canvasY;
    }

    /**
     * get the Algorithm.
     *
     * @return the Algorithm
     */
    public Object getAlgorithm() {
        return algorithm;
    }

    /**
     * Set the Algorithm.
     *
     * @param obj the Algorithm
     */
    public void setAlgorithm(Object obj) {
        this.algorithm = null;
        this.algorithm = obj;
    }

    /**
     * Add a SubNetColor.
     *
     * @param c the Color
     */
    public void addSubNetColor(Color c) {
        this.subNetColors.add(c);
    }

    /**
     * Get the SubNetColors.
     *
     * @return SubNetColors
     */
    public LinkedList<Color> getSubNetColors() {
        return this.subNetColors;
    }

    public ArrayList<AbstractCpsObject> getTrackingObj() {
        return trackedObjects;
    }

    public void setTrackingObj(ArrayList<AbstractCpsObject> toTrack) {
        trackedObjects = toTrack;
    }

    public void addGraphListener(GraphListener gl) {
        graphListeners.add(gl);
    }

    public HashMap<Integer, ArrayList<HolonElement>> getEleToDelete() {
        return this.eleToDelete;
    }

    public void setEleToDelete(HashMap<Integer, ArrayList<HolonElement>> theHash) {
        this.eleToDelete = theHash;
    }

    public PropertyTable getSingleTable() {
        return this.tableModelHolonElementSingle;
    }

    public void setSingleTable(PropertyTable pt) {
        this.tableModelHolonElementSingle = pt;
    }

    public PropertyTable getMultiTable() {
        return this.tableModelHolonElementMulti;
    }

    public void setMultiTable(PropertyTable pt) {
        this.tableModelHolonElementMulti = pt;
    }

    public void addObjectsToGraphListeners() {
        for (GraphListener gl : graphListeners) {
            gl.addTrackedObject(trackedObjects);
            gl.repaintTree();
        }
    }

    public DefaulTable getPropertyTable() {
        return this.tableModelProperties;
    }

    public void setPropertyTable(DefaulTable pt) {
        this.tableModelProperties = pt;
    }

    public JTable getTableHolonElement() {
        return tableHolonElement;
    }

    public void setTableHolonElement(JTable tableHolonElement) {
        this.tableHolonElement = tableHolonElement;
    }

    /**
     * Returns the sCale (Scale for the Images).
     *
     * @return sCALE
     */
    public int getHolonBodyScale() {
        return holonBodysCALE;
    }

    /**
     * Sets the HolonBody Scale.
     *
     * @param scale for the HolonBody
     */
    public void setHolonBodyScale(int scale) {
        holonBodysCALE = scale;
    }

    /**
     * Returns the ID of the selected HolonBody
     *
     * @return selectedHolonBody
     */
    public int getSelectedHolonBody() {
        return selectedHolonBody;
    }

    /**
     * Sets the ID of the selected HolonBody
     *
     * @param i int
     */
    public void setSelectedHolonBody(int i) {
        selectedHolonBody = i;
    }

    /**
     * get all Switches
     */
    public ArrayList<HolonSwitch> getSwitches() {
        ArrayList<HolonSwitch> switches = new ArrayList<>();
        for (AbstractCpsObject obj : getObjectsOnCanvas()) {
            if (obj instanceof HolonSwitch) {
                switches.add((HolonSwitch) obj);
            } else if (obj instanceof CpsUpperNode) {
                getSwitchesRec(((CpsUpperNode) obj).getNodes(), switches);
            }
        }
        return switches;
    }

    /**
     * get the Amount of Switches help function
     *
     * @param objects  objects
     * @param switches List of switches
     */
    private ArrayList<HolonSwitch> getSwitchesRec(ArrayList<AbstractCpsObject> objects,
                                                  ArrayList<HolonSwitch> switches) {
        for (AbstractCpsObject obj : objects) {
            if (obj instanceof HolonSwitch) {
                switches.add((HolonSwitch) obj);
            } else if (obj instanceof CpsUpperNode) {
                getSwitchesRec(((CpsUpperNode) obj).getNodes(), switches);
            }
        }
        return switches;
    }

    /**
     * Returns the Path for the background Image of the Canvas.
     *
     * @return imgPath the Path
     */
    public String getCanvasImagePath() {
        return imgPath;
    }

    /**
     * Set the Path for the background Image of the Canvas.
     *
     * @param path the Path
     */
    public void setCanvasImagePath(String path) {
        imgPath = path;
    }

    /**
     * Returns the mode for the background Image of the Canvas.
     * <p>
     * 0 take size of the Image 1 stretch the Image 2 Custom Image size
     *
     * @return backgroundMode the mode
     */
    public int getCanvasImageMode() {
        return backgroundMode;
    }

    /**
     * Set the mode for the background Image of the Canvas.
     * <p>
     * 0 take size of the Image, 1 stretch the Image, 2 Custom Image size
     *
     * @param mode the backgroundMode
     */
    public void setCanvasImageMode(int mode) {
        backgroundMode = mode;
    }

    /**
     * Returns the Custom width of the background Image of the Canvas.
     *
     * @return backgroundWidth the Width
     */
    public int getCanvasImageWidth() {
        return backgroundWidth;
    }

    /**
     * Set the Custom width of the background Image of the Canvas.
     *
     * @param width the Width
     */
    public void setCanvasImageWidth(int width) {
        backgroundWidth = width;
    }

    /**
     * Returns the Custom height of the background Image of the Canvas.
     *
     * @return backgroundHeight the height
     */
    public int getCanvasImageHeight() {
        return backgroundHeight;
    }

    /**
     * Set the Custom height of the background Image of the Canvas.
     *
     * @param height the height
     */
    public void setCanvasImageHeight(int height) {
        backgroundHeight = height;
    }

    /**
     * Returns the graphtable for Statistic Graphs.
     */
    public Hashtable<String, StatisticGraphPanel> getGraphTable() {
        return statisticGraphTable;
    }

    /**
     * Set the graphtable for Statistic Graphs
     */
    public void setGraphTable(Hashtable<String, StatisticGraphPanel> gT) {
        statisticGraphTable = gT;
    }

    /**
     * Returns if the Simulation is running.
     */
    public boolean getIsSimRunning() {
        return isSimRunning;
    }

    /**
     * Sets isSimRunning.
     *
     * @param isRunning
     */
    public void setIsSimRunning(boolean isRunning) {
        isSimRunning = isRunning;
    }

    /**
     * @return the statisticData
     */
    public ArrayList<JsonObject> getStatisticData() {
        return statisticData;
    }

    /**
     * @param statisticData the statisticData to set
     */
    public void setStatisticData(ArrayList<JsonObject> statisticData) {
        this.statisticData = statisticData;
    }

    /**
     * Returns showConsoleLog.
     */
    public boolean getShowConsoleLog() {
        return this.showConsoleLog;
    }

    /**
     * Sets showConsoleLog.
     *
     * @param showConsoleLog
     */
    public void setShowConsoleLog(boolean showConsoleLog) {
        this.showConsoleLog = showConsoleLog;
    }


    public boolean useFlexibleDevices() {
        return this.useFlexibleDevices;
    }

    public void setUseFlexibleDevices(boolean useFlexibleDevices) {
        this.useFlexibleDevices = useFlexibleDevices;
    }

    /**
     * Initialize the Gson with wanted parameters
     */
    private void initGson() {
        GsonBuilder builder = new GsonBuilder();
        builder.serializeNulls();
        builder.excludeFieldsWithoutExposeAnnotation();
        builder.setPrettyPrinting();
        builder.registerTypeAdapter(AbstractCpsObject.class, new AbstractCpsObjectAdapter());
        builder.registerTypeAdapter(Position.class, new PositionAdapter());
        builder.registerTypeAdapter(Color.class, new ColorAdapter());
        builder.registerTypeAdapter(Pair.class, new PairAdapter());
        // use the builder and make a instance of the Gson
        this.setGson(builder.create());

    }

    /**
     * @return the gson
     */
    public Gson getGson() {
        return gson;
    }

    /**
     * @param gson the gson to set
     */
    public void setGson(Gson gson) {
        this.gson = gson;
    }

    public StatisticPanel getStatPanel() {
        return statPanel;
    }

    public void setStatPanel(StatisticPanel sP) {
        statPanel = sP;
    }

    /**
     * @return the hashcodeMap
     */
    public HashMap<Integer, CpsUpperNode> getHashcodeMap() {
        return hashcodeMap;
    }

    /**
     * @param hashcodeMap the hashcodeMap to set
     */
    public void setHashcodeMap(HashMap<Integer, CpsUpperNode> hashcodeMap) {
        this.hashcodeMap = hashcodeMap;
    }
}
