package ui.controller;

import java.awt.Color;
import java.awt.Point;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.stream.Collectors;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import TypeAdapter.AbstractCpsObjectAdapter;
import TypeAdapter.ColorAdapter;
import TypeAdapter.PositionAdapter;
import classes.Category;
import classes.CpsEdge;
import classes.CpsUpperNode;
import classes.AbstractCpsObject;
import classes.HolonElement;
import classes.HolonObject;
import classes.HolonSwitch;
import classes.IdCounter;
import classes.IdCounterElem;
import classes.Position;
import ui.model.Model;

/**
 * Controller for the Loading.
 * 
 * @author Gruppe14
 */
public class LoadController {
	/**
	 * enum Mode.
	 */
	public enum MODE {
		COMPLETE, PARTIAL
	}

	public enum EDGETYPE {
		CANVAS, CONNECTION, NODE, OLD
	}

	public enum GRAPHTYPE {
		SWITCH, ELEMENT
	}

	private Model model;
	private CategoryController cgC;
	private CanvasController cvsC;
	private ObjectController objC;
	private NodeController uppC;
	private MultiPurposeController mpC;
	private Gson gson;
	private JsonParser parser;

	/**
	 * Constructor.
	 * 
	 * @param model
	 *            Model
	 * @param cg
	 *            CategoryController
	 * @param cvs
	 *            CanvasController
	 * @param obj
	 *            ObjectController
	 * @param mp
	 *            MultiPurposeController
	 */
	public LoadController(Model model, CategoryController cg, CanvasController cvs, ObjectController obj,
			NodeController uppC, MultiPurposeController mp) {
		this.model = model;
		this.cgC = cg;
		this.cvsC = cvs;
		this.objC = obj;
		this.uppC = uppC;
		this.mpC = mp;
		initGson();
	}

	/**
	 * Reads the the JSON File and load the state into the Model.
	 * 
	 * @param path
	 *            the Path
	 * @throws IOException
	 *             exception
	 */
	public void readJson(String path) throws IOException {

		JsonObject json = (JsonObject) parser.parse(new FileReader(path));
		// get all keys via stream
		List<String> keys = getKeys(json);
		List<String> edges = keys.stream().filter(key -> key.contains("EDGE"))
				.collect(Collectors.toCollection(ArrayList::new));

		HashMap<Integer, AbstractCpsObject> objDispatch = new HashMap<>();
		HashMap<Integer, HolonElement> eleDispatch = new HashMap<>();

		initialize(keys, json);
		forwardObjects(keys, json, objDispatch, eleDispatch);
		forwardEdges(edges, json, objDispatch);

	}

	/**
	 * distribute the Edges
	 * 
	 * @param edges
	 * @param json
	 * @param objDispatch
	 */
	private void forwardEdges(List<String> edges, JsonObject json, HashMap<Integer, AbstractCpsObject> objDispatch) {
		// TODO Auto-generated method stub

		List<String> conn = new ArrayList<>();

		for (String edge : edges) {
			if (edge.contains("CVSEDGE"))
				loadEdge(EDGETYPE.CANVAS, json.get(edge), objDispatch);
			if (edge.contains("CONNEDGE"))
				conn.add(edge);
			if (edge.contains("NODE"))
				loadEdge(EDGETYPE.NODE, json.get(edge), objDispatch);
			if (edge.contains("OLD"))
				loadEdge(EDGETYPE.OLD, json.get(edge), objDispatch);
		}

		for (String edge : conn) {
			loadEdge(EDGETYPE.CONNECTION, json.get(edge), objDispatch);
		}

	}

	/**
	 * Distribute the given keys for right processing
	 * 
	 * @param keys
	 * @param json
	 * @param objDispatch
	 * @param eleDispatch
	 */
	private void forwardObjects(List<String> keys, JsonObject json, HashMap<Integer, AbstractCpsObject> objDispatch,
			HashMap<Integer, HolonElement> eleDispatch) {
		// TODO Auto-generated method stub
		for (String key : keys) {
			if (key.contains("CATEGORY"))
				loadCategory(json.get(key));
			if (key.contains("CGOBJECT"))
				loadCategoryObject(json.get(key));
			if (key.contains("CGELEMENT"))
				loadCategoryElements(json.get(key));
			if (key.contains("CVSOBJECT"))
				loadCanvasObject(json.get(key), objDispatch);
			if (key.contains("CVSELEMENT"))
				loadCanvasElements(json.get(key), objDispatch, eleDispatch);
			if (key.contains("SWUNITGRAPH"))
				loadUnitGraph(GRAPHTYPE.SWITCH, json.get(key), objDispatch, null);
			if (key.contains("ELEUNITGRAPH"))
				loadUnitGraph(GRAPHTYPE.ELEMENT, json.get(key), null, eleDispatch);
		}

	}

	/**
	 * Init the Global Parameters
	 * 
	 * @param keys
	 * @param json
	 */
	private void initialize(List<String> keys, JsonObject json) {

		switch (MODE.valueOf(json.get("MODE").getAsString())) {
		case COMPLETE:
			model.setCgIdx(new HashMap<String, Integer>());
			model.setCategories(new ArrayList<Category>());
			model.setCvsObjIdx(new HashMap<Integer, Integer>());
			model.setObjectsOnCanvas(new ArrayList<AbstractCpsObject>());
			model.setEdgesOnCanvas(new ArrayList<CpsEdge>());
			model.setCanvasX(json.get("CANVAS_SIZE_X").getAsInt());
			model.setCanvasX(json.get("CANVAS_SIZE_Y").getAsInt());
			break;
		case PARTIAL:
			model.setCvsObjIdx(new HashMap<Integer, Integer>());
			model.setObjectsOnCanvas(new ArrayList<AbstractCpsObject>());
			model.setEdgesOnCanvas(new ArrayList<CpsEdge>());
			break;

		default:
			break;
		}
		IdCounter.setCounter(json.get("IDCOUNTER").getAsInt());
		IdCounterElem.setCounter(json.get("IDCOUNTERELEMENT").getAsInt());
	}

	/**
	 * Load a given Category
	 * 
	 * @param jsonElement
	 */
	private void loadCategory(JsonElement jsonElement) {
		// TODO Auto-generated method stub
		cgC.addCategory(new Category(jsonElement.getAsString()));
	}

	/**
	 * Load a given Object in Category by Deserialization
	 * 
	 * @param jsonElement
	 */
	private void loadCategoryObject(JsonElement jsonElement) {
		// TODO Auto-generated method stub
		AbstractCpsObject temp = gson.fromJson(jsonElement.getAsJsonObject(), AbstractCpsObject.class);
		initObjects(temp);

		cgC.addObject(mpC.searchCat(temp.getSav()), temp);
	}

	/**
	 * Load a given Element in Category by Deserialization
	 * 
	 * @param jsonElement
	 */
	private void loadCategoryElements(JsonElement jsonElement) {
		// TODO Auto-generated method stub
		HolonElement temp = gson.fromJson(jsonElement.getAsJsonObject().get("properties").getAsJsonObject(),
				HolonElement.class);
		initElements(temp);
		objC.addElementIntoCategoryObject(temp.getSav(), temp.getObj(), temp);
	}

	/**
	 * Load a given Object in Canvas by Deserialization
	 * 
	 * @param jsonElement
	 * @param objDispatch
	 */
	private void loadCanvasObject(JsonElement jsonElement, HashMap<Integer, AbstractCpsObject> objDispatch) {
		// TODO Auto-generated method stub
		AbstractCpsObject temp = gson.fromJson(jsonElement.getAsJsonObject(), AbstractCpsObject.class);
		initObjects(temp);
		// if its stored before on the canvas just put it there
		if (temp.getSav().equals("CVS")) {
			cvsC.addObject(temp);

		} else {
			// else look up the table and put it into the right Uppernode
			CpsUpperNode temp2 = (CpsUpperNode) objDispatch.get(Integer.parseInt(temp.getSav()));
			uppC.addObjectInUpperNode(temp, temp2);
		}

		objDispatch.put(temp.getId(), temp);

	}

	/**
	 * Load a given Element in Canvas by Deserialization
	 * 
	 * @param jsonElement
	 * @param objDispatch
	 * @param eleDispatch
	 */
	private void loadCanvasElements(JsonElement jsonElement, HashMap<Integer, AbstractCpsObject> objDispatch,
			HashMap<Integer, HolonElement> eleDispatch) {
		// TODO Auto-generated method stub

		JsonObject object = jsonElement.getAsJsonObject();

		HolonElement temp = gson.fromJson(object.get("properties"), HolonElement.class);
		initElements(temp);
		// id which Object it was stored before
		int stored = object.get("ID").getAsInt();
		// lookup that object
		HolonObject temp2 = (HolonObject) objDispatch.get(stored);
		// add it
		objC.addElement(temp2, temp);
		// store element also inside a table
		eleDispatch.put(temp.getId(), temp);
	}

	/**
	 * Load a given Edge by Deserialization
	 * 
	 * @param type
	 * @param jsonElement
	 * @param objDispatch
	 */
	private void loadEdge(EDGETYPE type, JsonElement jsonElement, HashMap<Integer, AbstractCpsObject> objDispatch) {
		// TODO Auto-generated method stub
		JsonObject object = jsonElement.getAsJsonObject();
		CpsEdge temp = gson.fromJson(object.get("properties"), CpsEdge.class);
		initCpsEdge(temp);
		// look for A and B inside the Table
		temp.setA(objDispatch.get(object.get("A").getAsInt()));
		temp.setB(objDispatch.get(object.get("B").getAsInt()));

		int sav = 0;

		switch (type) {
		case CANVAS:
			// if in canvas add it into the canvas but delete connection before
			model.getEdgesOnCanvas().add(temp);
			break;
		case CONNECTION:
			// if no duplicates in connection store them into the given A and B
			if (!uppC.lookforDuplicates(temp.getA(), temp.getB(), temp.getA().getConnections()))
				temp.getA().getConnections().add(temp);
			if (!uppC.lookforDuplicates(temp.getA(), temp.getB(), temp.getB().getConnections()))
				temp.getB().getConnections().add(temp);
			break;
		case NODE:
			// put it into given nodeofnode
			sav = object.get("ID").getAsInt();
			((CpsUpperNode) objDispatch.get(sav)).getNodeEdges().add(temp);
			break;
		case OLD:
			// same as above
			sav = object.get("ID").getAsInt();
			((CpsUpperNode) objDispatch.get(sav)).getOldEdges().add(temp);
			break;
		default:
			break;
		}

		if (object.get("connection").getAsBoolean() && !type.equals(EDGETYPE.CONNECTION)) {
			temp.getA().getConnections().add(temp);
			temp.getB().getConnections().add(temp);
		}

	}

	/**
	 * Load a Unitgraph by Deserialization
	 * 
	 * @param type
	 * @param jsonElement
	 * @param objDispatch
	 * @param eleDispatch
	 */
	private void loadUnitGraph(GRAPHTYPE type, JsonElement jsonElement, HashMap<Integer, AbstractCpsObject> objDispatch,
			HashMap<Integer, HolonElement> eleDispatch) {
		// TODO Auto-generated method stub

		JsonObject object = jsonElement.getAsJsonObject();
		List<String> keys = getKeys(object);
		String p = null;
		int mid, x, y = 0;

		LinkedList<Point> graphpoint = new LinkedList<>();
		int sav = 0;
		// foreach Point in the graph restore it
		for (String k : keys) {
			if (!k.equals("ID")) {
				p = object.get(k).getAsString();
				mid = p.indexOf(':');
				x = Integer.parseInt(p.substring(0, mid));
				y = Integer.parseInt(p.substring(mid + 1, p.length()));
				graphpoint.add(new Point(x, y));
			} else
				// else its an ID
				sav = object.get(k).getAsInt();

		}

		switch (type) {
		case SWITCH:
			HolonSwitch sw = (HolonSwitch) objDispatch.get(sav);
			sw.setGraphPoints(graphpoint);
			break;
		case ELEMENT:
			HolonElement ele = eleDispatch.get(sav);
			ele.setGraphPoints(graphpoint);
			break;
		default:
			break;
		}
	}

	/**
	 * Initialize the Gson with wanted parameters
	 */
	private void initGson() {
		// TODO Auto-generated method stub
		GsonBuilder builder = new GsonBuilder();
		builder.serializeNulls();
		builder.registerTypeAdapter(AbstractCpsObject.class, new AbstractCpsObjectAdapter());
		builder.registerTypeAdapter(Position.class, new PositionAdapter());
		builder.registerTypeAdapter(Color.class, new ColorAdapter());
		// use the builder and make a instance of the Gson
		this.gson = builder.create();
		this.parser = new JsonParser();
	}

	/**
	 * Init new Arrays which havent been serialized along the object
	 * 
	 * @param obj
	 */
	public void initObjects(AbstractCpsObject obj) {

		obj.setConnections(new ArrayList<CpsEdge>());
		obj.setTags(new ArrayList<Integer>());
		obj.setPseudoTags(new ArrayList<Integer>());

		if (obj instanceof HolonObject) {
			((HolonObject) obj).setElements(new ArrayList<HolonElement>());
			((HolonObject) obj).setEleIdx(new HashMap<String, Integer>());

			((HolonObject) obj).setTrackingProd(new float[100]);
			((HolonObject) obj).setTrackingCons(new float[100]);
		}

		if (obj instanceof HolonSwitch) {
			((HolonSwitch) obj).setActiveAt(true);
			((HolonSwitch) obj).setGraphPoints(new LinkedList<Point>());
		}

		if (obj instanceof CpsUpperNode) {
			((CpsUpperNode) obj).setNodes(new ArrayList<AbstractCpsObject>());
			((CpsUpperNode) obj).setNodeEdges(new ArrayList<CpsEdge>());
			((CpsUpperNode) obj).setOldEdges(new ArrayList<CpsEdge>());
			((CpsUpperNode) obj).setNodesIdx(new HashMap<Integer, Integer>());
		}
	}

	/**
	 * Init Elements
	 * 
	 * @param ele
	 */
	public void initElements(HolonElement ele) {

		ele.setEnergyAt(ele.getEnergy());
		ele.setGraphPoints(new LinkedList<Point>());
	}

	/**
	 * Init Edges
	 * 
	 * @param edge
	 */
	public void initCpsEdge(CpsEdge edge) {
		edge.setTags(new ArrayList<Integer>());
		edge.setPseudoTag(new ArrayList<Integer>());
		edge.setA(null);
		edge.setB(null);
	}

	/**
	 * Get Set of Keys
	 * 
	 * @param json
	 * @return
	 */
	public List<String> getKeys(JsonObject json) {
		return json.entrySet().stream().map(i -> i.getKey()).collect(Collectors.toCollection(ArrayList::new));
	}

}
