package ui.view;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.util.ArrayList;

import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.plaf.BorderUIResource.BevelBorderUIResource;

import classes.AbstractCpsObject;
import classes.Constants;
import classes.CpsUpperNode;
import classes.HolonBody;
import classes.HolonElement;
import classes.HolonObject;
import classes.SubNet;
import classes.Vector2d;
import ui.controller.Control;
import ui.controller.HolonCanvasController;
import ui.model.Model;

public class HolonCanvas extends JPanel implements MouseWheelListener, MouseListener, MouseMotionListener {

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

	// Rendering
	private Graphics2D g2;

	// Ball objects
	private ArrayList<HolonBody> bodies = new ArrayList<>();
	private int subCount;

	// Frames
	private int currentFrameRate;

	private long previousTime = System.currentTimeMillis();
	private long currentTime = previousTime;
	private long elapsedTime;
	private long totalElapsedTime = 0;
	private int frameCount = 0;
	private Dimension center;
	private ArrayList<SubNet> subnets;
	private HolonCanvasController hCController;

	private Control controller;
	private Model model;
	private JComboBox<String> combo = new JComboBox<>();
	private int comboChoice = 0;
	private String info;
	private int bodyNr;
	private final JLabel lblBodyInfo = new JLabel("Holon Info:");
	private JCheckBox chckbxSort = new JCheckBox("sort by size");
	private boolean sizeChange = false;
	private HolonBody toDrag;
	
	public HolonCanvas(Model mod, Control control) {
		// Wire up Events
		this.controller = control;
		this.model = mod;
		
		hCController = new HolonCanvasController(mod);

		add(lblBodyInfo);
		this.add(combo);
		subnets = controller.getSimManager().getSubNets();
		subCount = subnets.size();
		previousTime = System.currentTimeMillis();
		currentTime = previousTime;
		totalElapsedTime = 0;
		frameCount = 0;
		this.addMouseWheelListener(this);
		combo.addItem("ID");
		combo.addItem("Nr. of Objects");
		combo.addItem("Nr. of Edges");
		combo.addItem("Nr. of Switches");
		combo.addItem("Total Production");
		combo.addItem("Total Consumption");
		combo.addItem("Nr. of Elements");
		combo.addItem("Nr. of Producers");
		combo.addItem("Nr. of active Elements");

		combo.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				comboChoice = combo.getSelectedIndex();
			}
		});
		this.addMouseListener(this);
		this.addMouseMotionListener(this);

		chckbxSort.addChangeListener(new ChangeListener() {

			@Override
			public void stateChanged(ChangeEvent e) {
				sizeChange = true;
			}
		});
		add(chckbxSort);
	}

	// Start Render and Update Threads
	public void paintComponent(Graphics g) {
		super.paintComponent(g);

		// add new colors if necessary
		for (int i = 0; i < controller.getSimManager().getSubNets().size(); i++) {
			if (model.getSubNetColors().size() - 1 < i) {
				controller.addSubNetColor(new Color((int) (Math.random() * 255), (int) (Math.random() * 255),
						(int) (Math.random() * 255)));
			}
		}
		// check if subnets have changed
		if (!controller.getSimManager().getSubNets().equals(subnets)) {
			subnets = controller.getSimManager().getSubNets();
			subCount = subnets.size();
			calcCenter();
			hCController.addNewBodies(subCount,subnets, center, controller.getHolonBodyScale());
			sizeChange = true;
		}

		currentTime = System.currentTimeMillis();
		elapsedTime = (currentTime - previousTime); // elapsed time in seconds
		totalElapsedTime += elapsedTime;

		if (totalElapsedTime > 1000) {
			currentFrameRate = frameCount;
			frameCount = 0;
			totalElapsedTime = 0;
		}

		// check if HolonBodys should bes sorted after size
		if (chckbxSort.isSelected() && sizeChange) {
			hCController.rearrangeAfterSize();
			sizeChange = false;
		}
		hCController.updateBodies(elapsedTime / 1000f);

		render(g);

		try {
			// Thread.sleep(getFpsDelay(maxFrameRate));
			Thread.sleep(5);
		} catch (Exception e) {
			e.printStackTrace();
		}

		previousTime = currentTime;
		frameCount++;
		repaint();
	}	

	public void render(Graphics g) {
		this.g2 = (Graphics2D) g;
		this.g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

		// Render Background
		this.g2.setColor(Color.WHITE);
		this.g2.fillRect(0, 0, getWidth(), getHeight());

		bodies = hCController.getBodies();
		// Render Game Objects
		for (int i = 0; i < subCount; i++) {
			bodyNr = bodies.get(i).getId();
			switch (comboChoice) {
			case 0:
				info = "" + bodyNr;
				break;
			case 1:
				info = "" + subnets.get(bodyNr).getObjects().size();
				break;
			case 2:
				info = "" + subnets.get(bodyNr).getEdges().size();
				break;
			case 3:
				info = "" + subnets.get(bodyNr).getSwitches().size();
				break;
			case 4:
				info = "" + hCController.getTotalProduction(new ArrayList<AbstractCpsObject>(subnets.get(bodyNr).getObjects()));
				break;
			case 5:
				info = "" + hCController.getTotalConsumption(new ArrayList<AbstractCpsObject>(subnets.get(bodyNr).getObjects()));
				break;
			case 6:
				info = "" + hCController.getTotalElements(new ArrayList<AbstractCpsObject>(subnets.get(bodyNr).getObjects()));
				break;
			case 7:
				info = "" + hCController.getTotalProducers(new ArrayList<AbstractCpsObject>(subnets.get(bodyNr).getObjects()));
				break;
			case 8:
				info = "" + hCController.getActiveElements(new ArrayList<AbstractCpsObject>(subnets.get(bodyNr).getObjects()));
				break;
			default:
				info = "" + bodyNr;
				break;
			}
			bodies.get(i).setRadius((subnets.get(bodies.get(i).getId()).getObjects().size() * 5 + 10)
					* controller.getHolonBodyScale() / 100);
			bodies.get(i).draw(this.g2, info);
		}
	}

	
	// calc the center of the canvas
	public void calcCenter() {
		center = this.getSize();
		center.height /= 2;
		center.width /= 2;
	}

	@Override
	public void mouseWheelMoved(MouseWheelEvent e) {
		controller.setHolonBodyScale(model.getHolonBodyScale() + (-e.getScrollAmount() * e.getWheelRotation()));
	}

	@Override
	public void mouseClicked(MouseEvent e) {
		// TODO Auto-generated method stub

	}

	@Override
	public void mouseEntered(MouseEvent e) {
		// TODO Auto-generated method stub

	}

	@Override
	public void mouseExited(MouseEvent e) {
		// TODO Auto-generated method stub

	}

	@Override
	public void mousePressed(MouseEvent e) {
		// Body Selection
		for (int i = 0; i < subCount; i++) {
			float cx = bodies.get(i).position.getX();
			float cy = bodies.get(i).position.getY();
			float cr = bodies.get(i).getRadius();
			if (e.getX() - cr <= cx && e.getY() - cr <= cy && e.getX() + cr >= cx && e.getY() + cr >= cy) {
				controller.addSelectedHolonBody(bodies.get(i).getId());
				hCController.setBodyToDrag(bodies.get(i).getId());
				toDrag = bodies.get(i);
				break;
			} else {
				controller.addSelectedHolonBody(-1);
				toDrag = null;
			}
			hCController.setBodyToDrag(-1);
		}
	}

	@Override
	public void mouseReleased(MouseEvent e) {
		hCController.setDrag(false);
	}

	@Override
	public void mouseDragged(MouseEvent e) {
		hCController.setDrag(true);
		if (toDrag != null) {
			toDrag.position = new Vector2d(e.getX(), e.getY());
		}
		repaint();
	}

	@Override
	public void mouseMoved(MouseEvent e) {
		// TODO Auto-generated method stub
	}
}