package gui.simulator;

import gui.simulator.Images.TextPaint;
import gui.simulator.SimulatorPane.Objects;
import gui.simulator.facilities.SimulatorStage;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.TexturePaint;
import java.awt.Toolkit;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.awt.geom.AffineTransform;
import java.awt.geom.Area;
import java.awt.geom.NoninvertibleTransformException;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

import javax.swing.JDialog;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;

import agenda.Act;
import agenda.Agenda;


public class Terrain extends JPanel {

	private List<DrawEngine> entities;
	private Point2D oldPosition;
	private double oldRotation, oldScale;
	private DrawEngine dragObject, selectedObject;
	private static TextPaint background;
	private Sidebar sidebar;
	private Topbar topbar;

	private int festivalheight, festivalwidth;
	private int sideBarWidth = 200;

	private int pathgenerate, maxvisitors; 
	private WalkingPath currentpath;

	Point2D cameraPoint = new Point2D.Double(festivalwidth/2,festivalheight/2);
	float cameraScale = 1;
	private boolean grid;
	private ArrayList<Visitor> visitors;
	private WalkingPathArrayList paths;
	Cursor rotate, pathpoint;

	Point2D lastClickPosition;
	Point lastMousePosition;
	Point2D lastMovedMousePosition;
	TexturePaint p;

	private Agenda agenda;

	public enum Location{NORTH, EAST, SOUTH, WEST};

	public Terrain(int length, int width, SimulatorPane.Terrains terrain, Agenda agenda, SimulatorPane simulator){
		//get values
		maxvisitors = 30;
		setFestivalHeight(length);
		setFestivalWidth(width);
		visitors = new ArrayList<Visitor>();
		paths = new WalkingPathArrayList();

		createCustomCursors();
		//initialize sidebar & topbar    	
		sidebar = new Sidebar(sideBarWidth, 3000, this);
		topbar = new Topbar(this, simulator, agenda);
		addMouseListener(topbar);

		//set terrainbackground
		switch(terrain){
		case BEACH: 
			background = Images.TextPaint.TPSand;
			break;
		case GRASS:
			background = Images.TextPaint.TPGrass;
			break;
		case URBAN:
			background = Images.TextPaint.TPStreet;
			break;
		}

		this.entities = new LinkedList();
		this.initEntities();
		this.listeners();
		this.agenda = agenda;
	}

	private void listeners(){
		addMouseListener(new MouseAdapter() {
			@Override
			public void mousePressed(MouseEvent e) {
				Point2D clickPoint = getClickPoint(e.getPoint());
				lastClickPosition = clickPoint;
				lastMousePosition = e.getPoint();
				//if selected in sidebar
				sidebar.getFacility(e);
				//else selected in terrain
				if(e.getX() > sideBarWidth){
					for(DrawEngine drawObject: entities){
						if(drawObject.contains(clickPoint)){
							if(pathgenerate  == 1){
								currentpath.addPoint(new Point((int)drawObject.getCenter().getX(), (int)drawObject.getCenter().getY()));
								currentpath.setObject1(drawObject);
								pathgenerate = 2;
								return;
							}else if(pathgenerate > 1){
								currentpath.addPoint(new Point((int)drawObject.getCenter().getX(), (int)drawObject.getCenter().getY()));
								currentpath.setObject2(drawObject);
								paths.add(currentpath);
								pathgenerate = 0;
								return; 
							}else{
								setCursor(new Cursor(Cursor.MOVE_CURSOR));
								dragObject = drawObject;
								selectedObject = dragObject;	
								oldPosition = new Point2D.Double(selectedObject.getX(), selectedObject.getY());
								oldRotation = selectedObject.getScale();
								oldScale = selectedObject.getScale();

								//double click
								if(e.getClickCount() == 2){
									//TODO open dialog
									JDialog dialog = new StageDialog(drawObject, agenda);

									//position
									Toolkit toolkit = Toolkit.getDefaultToolkit();
									Dimension screenSize = toolkit.getScreenSize();

									int x = (int) ((screenSize.getWidth() - dialog.getWidth()) / 2);
									int y = (int) ((screenSize.getHeight() - dialog.getHeight()) / 2);

									Point center = new Point(x, y);

									dialog.setLocation(center);
									dialog.pack();
									dialog.setVisible(true);
								}
								return;
							}
						}
					}
					if(pathgenerate == 1){
						if(SwingUtilities.isRightMouseButton(e)){
							pathgenerate = 0;
						}
					}else if(pathgenerate > 1){
						if(SwingUtilities.isRightMouseButton(e)){
							currentpath.removeLastPoint();
							if(currentpath.getPath().size() == 0){
								pathgenerate = 0;
							}
						}else{
							currentpath.addPoint(new Point((int)getClickPoint(e.getPoint()).getX(), (int)getClickPoint(e.getPoint()).getY()));
						}
					}
				}
				repaint();
			}

			@Override
			public void mouseReleased(MouseEvent e) {
				if(selectedObject != null){
					for(DrawEngine drawObject: entities){
						if(drawObject == selectedObject){
							continue;
						}
						if(intersection(selectedObject, drawObject)){
							//move back
							selectedObject.setX(oldPosition.getX());
							selectedObject.setY(oldPosition.getY());
							selectedObject.setRotation(oldRotation);
							selectedObject.setScale(oldScale);
						}
					}
					for(WalkingPath p:paths){
						p.reCalculate();
					}
				}
				setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
				selectedObject = null;
				dragObject = null;
				repaint();
			}
		});

		addMouseMotionListener(new MouseMotionAdapter() {
			public void mouseMoved(MouseEvent e){
				lastMovedMousePosition = getClickPoint(e.getPoint());
				if(e.getX() < sidebar.getSideBarWidth()){
					setCursor(new Cursor(Cursor.HAND_CURSOR));
				}else if(pathgenerate >0){
					setCursor(pathpoint);
				}else if(topbar.containsMouse(e.getPoint())){
					setCursor(new Cursor(Cursor.HAND_CURSOR));
				}else{
					for(DrawEngine drawObject: entities){
						if(drawObject.contains(lastMovedMousePosition)){
							setCursor(new Cursor(Cursor.HAND_CURSOR));
							return;
						}
					}
					setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
				}
			}
			@Override
			public void mouseDragged(MouseEvent e) {
				Point2D clickPoint = getClickPoint(e.getPoint());
				if(dragObject != null){
					if(SwingUtilities.isLeftMouseButton(e) && (dragObject instanceof AccessPoint || dragObject instanceof ExitPoint)){    
						if(e.getX()<200){
							dragObject.changeLocation(Location.WEST);
							dragObject.setX(-75);
							dragObject.setRotation(90);
							if(dragObject.getY()<80){
								dragObject.setY(80);
							} else if(dragObject.getY()>getFestivalHeight()-150){
								dragObject.setY(getFestivalHeight()-150);
							} else {
								dragObject.setY(dragObject.getY() - (lastClickPosition.getY() - clickPoint.getY()));    
							}   
						} else if (e.getX()>200 && e.getX()< getFestivalWidth()-200){
							if(e.getY()<(getFestivalHeight()/2)){
								dragObject.changeLocation(Location.NORTH);
								dragObject.setY(0);
								dragObject.setRotation(180);

								if(dragObject.getX()<100){
									dragObject.setX(100);
								} else if(dragObject.getX()>getFestivalWidth()-200){
									dragObject.setY(getFestivalWidth()-200);
								} else {
									dragObject.setX(dragObject.getX() - (lastClickPosition.getX() - clickPoint.getX()));  
								}   

							} else if(e.getY()>(getFestivalHeight()/2)){
								dragObject.changeLocation(Location.SOUTH);
								dragObject.setY(getFestivalHeight()-75);
								dragObject.setRotation(180);

								if(dragObject.getX()<100){
									dragObject.setX(100);
								} else if(dragObject.getX()>getFestivalWidth()-200){
									dragObject.setY(getFestivalWidth()-200);
								} else {
									dragObject.setX(dragObject.getX() - (lastClickPosition.getX() - clickPoint.getX()));  
								}    
							}
						} else if(e.getX()>getFestivalWidth()-200){
							dragObject.changeLocation(Location.EAST);
							dragObject.setX(getFestivalWidth()-125);
							dragObject.setRotation(90);
							if(dragObject.getY()<80){
								dragObject.setY(80);
							} else if(dragObject.getY()>getFestivalHeight()-200){
								dragObject.setY(getFestivalHeight()-150);
								dragObject.setRotation(90);
							} else {
								dragObject.setY(dragObject.getY() - (lastClickPosition.getY() - clickPoint.getY()));    
							}
						}

					}else if(SwingUtilities.isLeftMouseButton(e)){
						setCursor(new Cursor(Cursor.MOVE_CURSOR));
						dragObject.setX(dragObject.getX() - (lastClickPosition.getX() - clickPoint.getX()));
						dragObject.setY(dragObject.getY() - (lastClickPosition.getY() - clickPoint.getY()));
					}else if(SwingUtilities.isRightMouseButton(e)){
						setCursor(rotate);
						dragObject.setRotation(dragObject.getRotation() + (lastClickPosition.getX() - clickPoint.getX() + lastClickPosition.getY() - clickPoint.getY()));
					}
					for(WalkingPath p:paths){
						p.reCalculate();
					}
				}
				else
				{
					setCursor(new Cursor(Cursor.MOVE_CURSOR));
					cameraPoint = new Point2D.Double(
							cameraPoint.getX() + (lastMousePosition.getX() - e.getX()),
							cameraPoint.getY() + (lastMousePosition.getY() - e.getY())
							);
				}
				repaint();
				lastMousePosition = e.getPoint();
				lastClickPosition = clickPoint;
			}
		});

		addMouseWheelListener(new MouseWheelListener() {
			@Override
			public void mouseWheelMoved(MouseWheelEvent e) {
				Point2D clickPoint = getClickPoint(e.getPoint());
				for(DrawEngine object: entities){
					//if object selected, scale object
					if(object.contains(clickPoint)){
						double oldscale  = object.getScale();
						if(oldscale < 0.3){
							if((e.getPreciseWheelRotation()/10.0) > 0){
								object.setScale(object.getScale() * 1 + (e.getPreciseWheelRotation()/10.0));
							} 
						} else {
							object.setScale(object.getScale() * 1 + (e.getPreciseWheelRotation()/10.0));
						}

						//intersect
						for(DrawEngine draw: entities){
							if(draw != object){
								if(intersection(draw, object)){
									object.setScale(oldscale);
								}
							}
						}

						repaint();
						return;
					}
				}
				//scale camera
				cameraScale *= 1 - (e.getPreciseWheelRotation()/10.0);
				repaint();
			}
		});
	}

	/*
	 * Add facilities on initializing
	 */
	private void initEntities(){
		//this.entities.add(new Stage(300, 10, 0.5, 10));
		//this.entities.add(new Stage(400, 40, 2, 10));
	}

	public void paintComponent(Graphics g) {
		super.paintComponent(g);
		Graphics2D g2 = (Graphics2D) g;
		g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);
		topbar.draw(g2, getWidth());
		sidebar.draw(g2);

		//draw simulation field
		Area a1 = new Area(new Rectangle2D.Double(sideBarWidth, 0, getWidth()-sideBarWidth, getHeight()));
		Area a2 = new Area(topbar.getBackground());
		a2.transform(new AffineTransform().getTranslateInstance(1, 1.2));
		a1.subtract(a2);  

		g2.setClip(a1);
		AffineTransform oldTransform = g2.getTransform();
		g2.translate(sideBarWidth,0);
		g2.setTransform(getCamera());


		g2.setPaint(Images.getTextPaint(background));
		g2.fill(new Rectangle2D.Double(0,0,festivalwidth,festivalheight));


		if(grid){
			g2.setColor(new Color(160,160,160,120));
			g2.setStroke(new BasicStroke(1));
			for(int i = 0; i < (getFestivalWidth()/10)+1; i++){
				g2.drawLine(i*10, 0, i*10, getFestivalHeight());
			};
			for(int i = 0; i < (getFestivalHeight()/10)+1; i++){
				g2.drawLine(0, i*10, getFestivalWidth(), i*10);
			};
		}

		if(pathgenerate > 1){
			currentpath.paint(g2);
			g2.drawLine((int)currentpath.get(currentpath.getPath().size()-1).getX(),(int)currentpath.get(currentpath.getPath().size()-1).getY(), (int)lastMovedMousePosition.getX(), (int)lastMovedMousePosition.getY());
		}
		for(WalkingPath path:paths){
			path.paint(g2);
		}
		for(Visitor v:visitors){
			v.paint(g2);
		}

		g2.setStroke(new BasicStroke(4));
		for(DrawEngine drawObject: entities){
			drawObject.draw(g2); 
			if(pathgenerate > 0){
				g2.setColor(new Color(180,100,100,120));
				g2.fill(new Rectangle((int)drawObject.getCenter().getX()-20, (int)drawObject.getCenter().getY()-20, 40,40));
			}
		}

		//draw collide boxes 
		if(this.selectedObject != null){
			Shape shape = this.selectedObject.getRect();
			g2.setColor(Color.RED);
			g2.draw(this.selectedObject.getAffineTransform().createTransformedShape(shape));

			boolean collide = false;
			for(DrawEngine drawEngine : entities){
				if(!drawEngine.equals(this.selectedObject)){
					if(intersection(this.selectedObject, drawEngine)){
						collide = true;
					}
				}
			}

			if(collide){
				g2.setColor(new Color(254, 0, 0, 128));
			}else{
				g2.setColor(new Color(0, 254, 0, 128));
			}
			g2.fill(this.selectedObject.getAffineTransform().createTransformedShape(this.selectedObject.getRect()));
			g2.setTransform(oldTransform);
		}
		g2.setClip(null);
	}

	private Rectangle drawDragRectangle(DrawEngine shape){
		Rectangle rectangle = null;
		rectangle = new Rectangle((int) (shape.getX() - shape.getDistanceToOtherObjects()), (int) (shape.getY() - shape.getDistanceToOtherObjects()), (int) (shape.getWidth() * shape.getScale() + (2 * shape.getDistanceToOtherObjects())), (int) (shape.getHeight() * shape.getScale() + (2 * shape.getDistanceToOtherObjects())));
		return rectangle;
	}

	private boolean intersection(DrawEngine d1, DrawEngine d2){
		Area area = new Area(d1.getAffineTransform().createTransformedShape(d1.getRect()));
		Area area2 = new Area(d2.getAffineTransform().createTransformedShape(d2.getRect()));

		area.intersect(area2);
		return !area.isEmpty();
	}

	public Point2D getClickPoint(Point point) {
		try {
			return getCamera().inverseTransform(point, null);
		} catch (NoninvertibleTransformException e1) {
			e1.printStackTrace();
		}
		return null;
	}

	private AffineTransform getCamera() {
		AffineTransform tx = new AffineTransform();
		//start field in top left corner, next to sidebar
		tx.translate(-cameraPoint.getX() + sideBarWidth, -cameraPoint.getY());
		//start field in middle of screen
		//tx.translate(-cameraPoint.getX() + getWidth()/2, -cameraPoint.getY() + getHeight()/2);
		tx.scale(cameraScale, cameraScale);
		return tx;
	}
	public void calculate(){
		for(Visitor v:visitors){
			v.update(visitors, entities,paths);
		}
		if(Math.random()*500 <100){
			Point2D startpoint = null;
			for(DrawEngine e:entities){
				List<AccessPoint> entrances = new ArrayList<AccessPoint>();
				if(e.type == Objects.ENTRANCE){
					entrances.add((AccessPoint)e);
				}
				if(!entrances.isEmpty())
					startpoint = entrances.get((int)(Math.random()*entrances.size())).getCenter();
			}
			if(startpoint == null || visitors.size() >= maxvisitors) return; //No entrance found OR to many visitors, stop adding people. 
			visitors.add(new Visitor(new Point2D.Double(-100-Math.random()*200, getFestivalHeight()-Math.random()*getFestivalHeight()), startpoint));
		}
		topbar.recalculate();
		stageloop:
			for(DrawEngine object:entities){
				if(object.type == SimulatorPane.Objects.STAGE){
					SimulatorStage stage = (SimulatorStage)object;
					for(Act act:agenda.getCurrentActs(topbar.getTime())){
						if(act.getStage() == stage.getStage()){
							stage.setPlayingact(act);
							continue stageloop;
						}
					}
					stage.setPlayingact(null);
				}
			}
	}

	public void setFestivalHeight(int height){
		this.festivalheight = height;
	}
	public void setFestivalWidth(int width){
		this.festivalwidth = width;
	}
	public int getFestivalHeight(){
		return festivalheight;
	}
	public int getFestivalWidth(){
		return festivalwidth;
	}

	//getters and setters for sidebar
	public void setDragObject(DrawEngine dragObject){
		this.dragObject = dragObject;
	}

	public DrawEngine getDragObject(){
		return dragObject;
	}

	public void addEntity(DrawEngine dragObject){
		entities.add(dragObject);
	}

	public void createCustomCursors(){
		Toolkit toolkit = Toolkit.getDefaultToolkit();  
		Images.ImageType image;

		image = Images.ImageType.RotateCursor;
		rotate = toolkit.createCustomCursor(Images.getImage(image), new Point(14,14), "Rotate");  

		image = Images.ImageType.PathCursor;
		pathpoint = toolkit.createCustomCursor(Images.getImage(image), new Point(14,14), "Pathpoint");  
	}
	public void toggleGrid(){
		grid = !grid;
	}
	public void newWalkingPath(Images.ImageType imagetype){
		if(pathgenerate == 0){
			currentpath = new WalkingPath(imagetype);
			pathgenerate = 1;
		}
	}
	public List<DrawEngine> getEntities() {
		return entities;
	}
	public void clearVisitors(){
		visitors.clear();
	}
	public void setMaxVisitors(int v){
		this.maxvisitors = v;
	}
	public int getMaxVisitors(){
		return maxvisitors;
	}
	public void removeWalkingPaths(DrawEngine object){
		for(WalkingPath p:paths.getWalkingPadObject(object)){
			paths.remove(p);
		}
	}

	public void setEntities(List<DrawEngine> entities) {
		this.entities = entities;
	}

	public void setPaths(WalkingPathArrayList paths) {
		this.paths = paths;
	}

	public WalkingPathArrayList getPaths() {
		return paths;
	}

}
