package cz.vutbr.feec.gui;

import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.HeadlessException;
import java.awt.Image;
import java.awt.Paint;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.Stroke;
import java.awt.Toolkit;
import java.awt.Transparency;
import java.awt.event.MouseEvent;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.PixelGrabber;
import java.io.IOException;
import java.util.AbstractList;
import java.util.Iterator;

import javax.swing.ImageIcon;
import javax.swing.JPanel;
import javax.swing.event.MouseInputAdapter;

import cz.vutbr.feec.iodata.ReadConfig;
import cz.vutbr.feec.math.GNPConfig;
import cz.vutbr.feec.session.gnp.LandmarkInfo;

/**
 * GUI component - panel with drag & drop capabilities.
 * 
 * @author Radim Burget
 */
abstract public class ImageSelectPanelDraggable extends JPanel {
	
	/** The is changed. */
	private boolean isChanged = true;

	/** The Constant serialVersionUID. */
	private static final long serialVersionUID = 7849099744283091833L;

	/** Sem je vykreslovan obrazek. */
	private BufferedImage img = null;

	/** The dragging enabled. */
	private boolean draggingEnabled = true;

	/** The drag shape. */
	private LandmarkGui dragShape = null;

	/** The config. */
	protected GNPConfig config;
	
	protected LandmarkInfo[] lmListFromConfig = null; 
	
	/**
	 * Adds the landmark to the panel.
	 * 
	 * @param o the o
	 */
	public void addLandmark(LandmarkGui o) {
		config.getLandmarks().add(o);
	}
	
	/**
	 * Removes the landmark.
	 * 
	 * @param o the landmark
	 */
	public void removeLandmark(LandmarkGui o) {
		config.getLandmarks().remove(o);
	}

	/**
	 * Gets the default configuration.
	 * 
	 * @return the default configuration
	 */
	private static GraphicsConfiguration getDefaultConfiguration() {
		GraphicsEnvironment ge = GraphicsEnvironment
				.getLocalGraphicsEnvironment();
		GraphicsDevice gd = ge.getDefaultScreenDevice();
		return gd.getDefaultConfiguration();
	}

	/**
	 * Gets the scaled instance.
	 * 
	 * @param height the height
	 * @param width the width
	 * @param gc the gc
	 * @param image the image
	 * 
	 * @return the scaled instance
	 */
	private static BufferedImage getScaledInstance(BufferedImage image,
			int width, int height, GraphicsConfiguration gc) {
		if (gc == null)
			gc = getDefaultConfiguration();
		int transparency = image.getColorModel().getTransparency();
		return copy(image, gc
				.createCompatibleImage(width, height, transparency));
	}

	/**
	 * Copy.
	 * 
	 * @param source the source
	 * @param target the target
	 * 
	 * @return the buffered image
	 */
	private static BufferedImage copy(BufferedImage source, BufferedImage target) {
		Graphics2D g2 = target.createGraphics();
		g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
				RenderingHints.VALUE_INTERPOLATION_BICUBIC);
		double scalex = (double) target.getWidth() / source.getWidth();
		double scaley = (double) target.getHeight() / source.getHeight();
		AffineTransform xform = AffineTransform
				.getScaleInstance(scalex, scaley);
		g2.drawRenderedImage(source, xform);
		g2.dispose();
		return target;
	}

	/**
	 * Sets image.
	 * 
	 * @param imgPath
	 */
	public void setImage(String imgPath) {
		Toolkit toolkit = getToolkit();
		Image tmp = toolkit.createImage(imgPath);
		img = toBufferedImage(tmp);
		GraphicsConfiguration gc = getDefaultConfiguration();
		img = getScaledInstance(img, width, height, gc);
		setPreferredSize(new Dimension(width, height));
		repaint();
	}

	/**
	 * Get buffered image.
	 * 
	 * @return Image
	 */
	public BufferedImage getBufferedImage() {
		return img;
	}

	private int height;

	private int width;


	/**
	 * The Constructor.
	 * 
	 * @param lmList the lm list
	 * @param height the height
	 * @param imgPath the img path
	 * @param width the width
	 */
	public ImageSelectPanelDraggable(int width, int height, String imgPath,
			GNPConfig conf) {
		this.config = conf;
		init(width, height, imgPath);
	}

	/**
	 * Creates instance of class.
	 * 
	 * @param width
	 *            Width of panel
	 * @param height
	 *            Height of panel
	 * @param imgPath
	 *            Path to image
	 */
	public void init(int width, int height, String imgPath) {
		this.width = width;
		this.height = height;

		MouseHandler h = new MouseHandler();
		addMouseListener(h);
		addMouseMotionListener(h);
		// toolkit is an interface to the environment
		Toolkit toolkit = getToolkit();
		// create the image using the toolkit
		Image tmp = toolkit.createImage(imgPath);
		img = toBufferedImage(tmp);
		// resize
		GraphicsConfiguration gc = getDefaultConfiguration();
		img = getScaledInstance(img, width, height, gc);
		setPreferredSize(new Dimension(width, height));
	}

	/**
	 * Dragging enabled.
	 * 
	 * @param enabled the enabled
	 */
	public void draggingEnabled(boolean enabled) {
		this.draggingEnabled = enabled;
	}

	/**
	 * Paints component.
	 * 
	 * @param g the graphics
	 */
	public void paintComponent(Graphics g) {
		super.paintComponent(g);

		Graphics2D g2d = ((Graphics2D) g);
		g2d.drawImage(img, null, 0, 0);
		Paint p = g2d.getPaint();
		Stroke s = g2d.getStroke();
		AbstractList<LandmarkGui> landmarkList = config.getLandmarks();
		for (Iterator<LandmarkGui> iterator = landmarkList.iterator(); iterator
				.hasNext();) {
			LandmarkGui landmark = iterator.next();
			drawLandmark(g2d, landmark);
		}

		// lines between landmarks
		AlphaComposite ac = AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
				0.5f);
		g2d.setComposite(ac);
		g2d.setPaint(Color.YELLOW);
		synchronized (landmarkList) {
			int size = landmarkList.size();
			for (int i = 0; i < size; i++) {
				for (int j = i + 1; j < size; j++) {
				//	g2d.drawLine(landmarkList.get(i).getPosition().x + 10,
					//			landmarkList.get(i).getPosition().y + 5,
					//			landmarkList.get(j).getPosition().x + 10,
					//		landmarkList.get(j).getPosition().y + 5);
				}
			}
		}
		g2d.setPaint(p);
		g2d.setStroke(s);
	}

	/**
	 * Draws landmark.
	 * 
	 * @param g2d
	 *            the g2d
	 * @param lm
	 *            the lm
	 */
	abstract public void drawLandmark(Graphics2D g2d, LandmarkGui lm);

	/**
	 * Gets the landmark position x.
	 * 
	 * @param lm
	 *            the lm
	 * 
	 * @return the lm position x
	 */
	abstract public int getLmPositionX(LandmarkGui lm);

	/**
	 * Gets the landmark position y.
	 * 
	 * @param lm
	 *            the lm
	 * 
	 * @return the lm position y
	 */
	abstract public int getLmPositionY(LandmarkGui lm);

	/**
	 * Handles mouse operations.
	 * 
	 * @author Radim Burget
	 */
	private final class MouseHandler extends MouseInputAdapter {
		@Override
		public void mouseMoved(MouseEvent e) {
			Point p = new Point();
			boolean found = false;
			for (Iterator<LandmarkGui> iterator = config.getLandmarks().iterator(); iterator
					.hasNext();) {
				LandmarkGui landmark = (LandmarkGui) iterator.next();
				Point cursor = e.getPoint();
				p.x = cursor.x - getLmPositionX(landmark);
				p.y = cursor.y - getLmPositionY(landmark);
				if (!found && landmark.contains(p)) {
					landmark.isOver(true);
					found = true;
				} else {
					landmark.isOver(false);
				}
			}
			repaint();
		}

		private Point lastPoint = null;

		/* (non-Javadoc)
		 * @see java.awt.event.MouseAdapter#mousePressed(java.awt.event.MouseEvent)
		 */
		public void mousePressed(MouseEvent e) {
			// mark as known possition
			if (e.getClickCount() == 2) {
				LandmarkGui landmark = getObjectOver(e.getPoint());
				if(landmark != null) {
					landmark.setFixedPosition(!landmark.isFixedPosition());
				}
			}
			if (!draggingEnabled) {
				return;
			}

			lastPoint = e.getPoint();
			Point p = new Point();

			// start dragging
			for (Iterator<LandmarkGui> iterator = config.getLandmarks().iterator(); iterator
					.hasNext();) {
				LandmarkGui landmark = (LandmarkGui) iterator.next();
				p.x = lastPoint.x - getLmPositionX(landmark);
				p.y = lastPoint.y - getLmPositionY(landmark);
				if (landmark.contains(p)) {
					dragShape = landmark;
					landmark.setDragged(true);
					break;
				} else {
					// System.out.print("FALSE ");
					// System.out.println("X:" + location.x + " Y:" +
					// location.y);
				}
			}
			repaint();
		}

		private LandmarkGui getObjectOver(Point point) {
			Point p = new Point();
			for (Iterator<LandmarkGui> iterator = config.getLandmarks().iterator(); iterator
					.hasNext();) {
				LandmarkGui landmark = (LandmarkGui) iterator.next();
				p.x = point.x - getLmPositionX(landmark);
				p.y = point.y - getLmPositionY(landmark);
				if (landmark.contains(p)) {
					return landmark;
				} else {
				}
			}
			return null;
		}

		public void mouseReleased(MouseEvent e) {
			if (dragShape != null) {
				dragShape.setDragged(false);
				isChanged = true;
			}
			dragShape = null;
			this.lastPoint = null;
			repaint();
		}

		public void mouseDragged(MouseEvent e) {
			if (dragShape != null) {
				Point location = dragShape.getPosition();
				location.x += (e.getX() - lastPoint.x);
				location.y += (e.getY() - lastPoint.y);

				// korekce rozsahu mimo hranice X
				if (location.x < 0) {
					location.x = 0;
				}
				// korekce rozsahu mimo hranice Y
				if (location.y < 0) {
					location.y = 0;
				}

				// korekce rozsahu mimo hranice X
				if (location.x > getWidth() - dragShape.getWidth()) {
					location.x = getWidth() - dragShape.getWidth();
				}

				// korekce rozsahu mimo hranice Y
				if (location.y > getHeight() - dragShape.getHeight()) {
					location.y = getHeight() - dragShape.getHeight();
				}

				this.lastPoint = e.getPoint();
				dragShape.setPosition(location.x, location.y);

				repaint();
			}
		}
	}

	// This method returns a buffered image with the contents of an image
	public static BufferedImage toBufferedImage(Image image) {
		if (image instanceof BufferedImage) {
			return (BufferedImage) image;
		}

		// This code ensures that all the pixels in the image are loaded
		image = new ImageIcon(image).getImage();

		// Determine if the image has transparent pixels; for this method's
		// implementation, see e661 Determining If an Image Has Transparent
		// Pixels
		boolean hasAlpha = hasAlpha(image);

		// Create a buffered image with a format that's compatible with the
		// screen
		BufferedImage bimage = null;
		GraphicsEnvironment ge = GraphicsEnvironment
				.getLocalGraphicsEnvironment();
		try {
			// Determine the type of transparency of the new buffered image
			int transparency = Transparency.OPAQUE;
			if (hasAlpha) {
				transparency = Transparency.BITMASK;
			}

			// Create the buffered image
			GraphicsDevice gs = ge.getDefaultScreenDevice();
			GraphicsConfiguration gc = gs.getDefaultConfiguration();
			bimage = gc.createCompatibleImage(image.getWidth(null), image
					.getHeight(null), transparency);
		} catch (HeadlessException e) {
			// The system does not have a screen
		}

		if (bimage == null) {
			// Create a buffered image using the default color model
			int type = BufferedImage.TYPE_INT_RGB;
			if (hasAlpha) {
				type = BufferedImage.TYPE_INT_ARGB;
			}
			bimage = new BufferedImage(image.getWidth(null), image
					.getHeight(null), type);
		}

		// Copy image to buffered image
		Graphics g = bimage.createGraphics();

		// Paint the image onto the buffered image
		g.drawImage(image, 0, 0, null);
		g.dispose();

		return bimage;
	}

	// This method returns true if the specified image has transparent pixels
	public static boolean hasAlpha(Image image) {
		// If buffered image, the color model is readily available
		if (image instanceof BufferedImage) {
			BufferedImage bimage = (BufferedImage) image;
			return bimage.getColorModel().hasAlpha();
		}

		// Use a pixel grabber to retrieve the image's color model;
		// grabbing a single pixel is usually sufficient
		PixelGrabber pg = new PixelGrabber(image, 0, 0, 1, 1, false);
		try {
			pg.grabPixels();
		} catch (InterruptedException e) {
		}

		// Get the image's color model
		ColorModel cm = pg.getColorModel();
		return cm.hasAlpha();
	}

	/**
	 * Nastav libovolnou pozici landmarkuù
	 */
/*	public void randomizeLMs() {
		for (Iterator<LandmarkGui> iterator = config.getLandmarks().iterator(); iterator
				.hasNext();) {
			LandmarkGui lm = iterator.next();
			Random rnd = new Random();
	//		lm.getPosition().x = rnd.nextInt(getWidth() - 20);
	//		lm.getPosition().y = rnd.nextInt(getHeight() - 20);
		}
		landmarkPositionInit();
	}
*/	
	public void readLmsFromConf() {
		try {
			lmListFromConfig = ReadConfig.readLmList("landmarks_list.conf");
		} catch (IOException e) {
			e.printStackTrace();
		}
		int i = 0;
		for (Iterator<LandmarkGui> iterator = config.getLandmarks().iterator(); iterator
		.hasNext();) {
			LandmarkGui lm = iterator.next();
			Point bod = MappingCoordinates.getPositionXY(lmListFromConfig[i].getLongitude(), 
					lmListFromConfig[i].getLatitude());
			lm.getPosition().x = bod.x;
			lm.getPosition().y = bod.y;
			lm.setHostname(lmListFromConfig[i].getHostName());
			i++;
		}
		landmarkPositionInit();
	}
	

	public void landmarkPositionInit() {
		int i=0;
		for (Iterator<LandmarkGui> iterator = config.getLandmarks().iterator(); iterator
				.hasNext();) {
			LandmarkGui lm = iterator.next();
			if(lm.isFixedPosition()) {	//xpospi37
				lm.setComputedX(lm.getPosition().x);
				lm.setComputedY(lm.getPosition().y);
			}
			else {
				lm.setComputedX(0);
				lm.setComputedY(0);
			}
			i++;
		}
	}
	/**
	 * Checks if is landmarks possition was changed.
	 * 
	 * @return true, if is changed
	 */
	public boolean isChanged() {
		return isChanged;
	}

	/**
	 * Sets if the landmarks position was changed.
	 * 
	 * @param isChanged the changed
	 */
	public void setChanged(boolean isChanged) {
		this.isChanged = isChanged;
	}
}
