package speiger.src.ui;

import java.awt.Color;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseWheelEvent;
import java.awt.geom.AffineTransform;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;

import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;

import speiger.src.data.Collectable;
import speiger.src.data.CollectableType;
import speiger.src.data.Registry;

public class MapPanel extends JPanel {
	private static final long serialVersionUID = 7639401429824622357L;
	JFrame owner;
	Runnable progressUpdate;
    float zoom = 1F;
	Point offset = new Point(-568, -271);
	Point lastDrag = null;
	Point startPos;
	Point lastPos;
	Image map = new ImageIcon(MapPanel.class.getResource("/assets/images/map.png")).getImage();
	Image[] medal = new Image[] {
			new ImageIcon(MapPanel.class.getResource("/assets/images/medal.png")).getImage(),
			new ImageIcon(MapPanel.class.getResource("/assets/images/medal_disabled.png")).getImage()
	};
	Image[] panel = new Image[] {
			new ImageIcon(MapPanel.class.getResource("/assets/images/panel.png")).getImage(),
			new ImageIcon(MapPanel.class.getResource("/assets/images/panel_disabled.png")).getImage()
	};
	Image[] pswitch = new Image[] {
			new ImageIcon(MapPanel.class.getResource("/assets/images/pswitch.png")).getImage(),
			new ImageIcon(MapPanel.class.getResource("/assets/images/pswitch_disabled.png")).getImage()
	};
	Image[] chuck = new Image[] {
			new ImageIcon(MapPanel.class.getResource("/assets/images/chucks.png")).getImage(),
			new ImageIcon(MapPanel.class.getResource("/assets/images/chucks_disabled.png")).getImage()
	};
	List<Marker> markers = new ArrayList<Marker>();
	List<Marker> visibleMarkers = new ArrayList<Marker>();
	Marker lastMarker = null;
    
	
	private Image[] fromType(CollectableType type) {
		return switch(type) {
			case MEDAL -> medal;
			case PANEL -> panel;
			case PSWITCH -> pswitch;
			case CHUCKS -> chuck;
			default -> pswitch;
		};
	}
	
	public MapPanel(JFrame frame) {
		this.owner = frame;
		Registry.INSTANCE.load();
		for(Collectable entry : Registry.INSTANCE.collectables()) {
			Marker marker = new Marker(entry.id(), entry.type(), entry.name(), entry.position(), fromType(entry.type()));
			markers.add(marker);
			visibleMarkers.add(marker);
		}
		MouseAdapter adapter = new MouseAdapter() {
			@Override
			public void mouseWheelMoved(MouseWheelEvent e) {
				double lastZoom = zoom;
				zoom = (e.getPreciseWheelRotation() < 0 ? zoom * 1.1F : zoom / 1.1F);
		        Point mouse = e.getPoint();
		        double scaleDiff = zoom / lastZoom;
		        offset.x = (int) (mouse.x - scaleDiff * (mouse.x - offset.x));
		        offset.y = (int) (mouse.y - scaleDiff * (mouse.y - offset.y));
				repaint();
			}
			
			@Override
			public void mousePressed(MouseEvent e) {
				lastDrag = e.getPoint();
			}
			
			@Override
			public void mouseMoved(MouseEvent e) {
				lastPos = screenToMap(e.getPoint());
				if(startPos != null) repaint();
				Marker marker = findMarker(lastPos, zoom);
				if(marker == lastMarker) return;
				if(lastMarker != null) lastMarker.setHovered(false);
				lastMarker = marker;
				if(marker == null) return;
				marker.setHovered(true);
				repaint();
			}
			
			@Override
			public void mouseDragged(MouseEvent e) {
				if(lastDrag == null) return;
				offset.translate((int)(e.getX() - lastDrag.getX()), (int)(e.getY() - lastDrag.getY()));
				lastDrag = e.getPoint();
				repaint();
			}
			
			@Override
			public void mouseClicked(MouseEvent e) {
				if(startPos != null) {
					if(e.getButton() == 1) {
						int result = JOptionPane.showOptionDialog(frame, "What do you want to do", "Bulk Marking", JOptionPane.DEFAULT_OPTION, JOptionPane.QUESTION_MESSAGE, null, new Object[] {"Complete", "Uncomplete", "Cancel"}, "Complete");
						if(result == 2) return;
						List<Marker> markers = getMarkers(getMarkedArea(), zoom);
						if(markers.isEmpty()) return;
						boolean completion = result == 0;
						Registry.INSTANCE.setBulkOperation(true);
						markers.forEach(T -> T.setCompletion(completion));
						Registry.INSTANCE.setBulkOperation(false);
						startPos = null;
						repaint();
					}
					return;
				}
				
				Point hover = screenToMap(e.getPoint());
				Marker marker = findMarker(hover, zoom);
				if(marker == null) return;
				if(e.getButton() == 1) {
					marker.toggle();
					repaint();
					if(progressUpdate != null) progressUpdate.run();
				}
				else if(e.getButton() == 3) {
					Collectable element = Registry.INSTANCE.get(marker.id());
					new LocationDisplay(owner, element.name(), element.images());
				}
			}
		};
		addMouseListener(adapter);
		addMouseWheelListener(adapter);
		addMouseMotionListener(adapter);
	}
	
	public void setProgressListener(Runnable run) {
		this.progressUpdate = run;
	}
	
	public void startBulkMarking(Point point) {
		this.startPos = this.screenToMap(point);
	}
	
	public void onImported() {
		markers.forEach(Marker::updateCompletion);
		EventQueue.invokeLater(this::repaint);
	}
	
	public void resetZoom() {
		zoom = 1F;
		EventQueue.invokeLater(this::repaint);		
	}
	
	public void resetMap() {
	    int imgWidth = map.getWidth(this);
	    int imgHeight = map.getHeight(this);

	    int panelWidth = getWidth();
	    int panelHeight = getHeight();
	    if (imgWidth > 0 && imgHeight > 0 && panelWidth > 0 && panelHeight > 0) {
	    	offset.setLocation((panelWidth - (int)(imgWidth * zoom)) / 2, (panelHeight - (int)(imgHeight * zoom)) / 2);
	    }
		EventQueue.invokeLater(this::repaint);
	}
	
	public void updateFilter(EnumSet<CollectableType> types) {
		visibleMarkers.clear();
		markers.forEach(T -> {
			if(types.contains(T.type())) visibleMarkers.add(T);
		});
		EventQueue.invokeLater(this::repaint);
	}
	
	public void setCompletionState(boolean value) {
		Registry.INSTANCE.setBulkOperation(true);
		visibleMarkers.forEach(T -> T.setCompletion(value));
		Registry.INSTANCE.setBulkOperation(false);
		EventQueue.invokeLater(this::repaint);
		if(progressUpdate != null) progressUpdate.run();
	}
	
	@Override
	public void paint(Graphics g) {
		super.paint(g);
        Graphics2D g2 = (Graphics2D) g;
        
        g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
        AffineTransform at = new AffineTransform();
        at.translate(offset.x, offset.y);
        at.scale(zoom, zoom);
        g2.setTransform(at);
        
        g2.drawImage(map, 0, 0, this);
        for(Marker marker : visibleMarkers) {
        	marker.draw(g2, (float)zoom);
        }   
        for(Marker marker : visibleMarkers) {
        	marker.drawHover(g2, (float)zoom);
        }
        if(startPos != null && lastPos != null) {
        	Point start = new Point(startPos);
        	Point end = new Point(lastPos);
        	if(start.x > end.x) {
        		int x = start.x;
        		start.x = end.x;
        		end.x = x;
        	}
        	if(start.y > end.y) {
        		int y = start.y;
        		start.y = end.y;
        		end.y = y;
        	}
        	g2.setColor(Color.BLUE);
        	g2.drawRect(start.x, start.y, end.x-start.x, end.y-start.y);
        }
	}
	
	private Rectangle getMarkedArea() {
    	Point start = new Point(startPos);
    	Point end = new Point(lastPos);
    	if(start.x > end.x) {
    		int x = start.x;
    		start.x = end.x;
    		end.x = x;
    	}
    	if(start.y > end.y) {
    		int y = start.y;
    		start.y = end.y;
    		end.y = y;
    	}
    	return new Rectangle(start.x, start.y, end.x-start.x, end.y-start.y);
	}
	
	private List<Marker> getMarkers(Rectangle rect, float scale) {
		List<Marker> markers = new ArrayList<>();
		for(int i = 0,m=visibleMarkers.size();i<m;i++) {
			Marker marker = visibleMarkers.get(i);		
			if(marker.collides(rect, scale)) markers.add(marker);
		}
		return markers;
	}
	
	private Marker findMarker(Point point, float scale) {
		for(int i = 0,m=visibleMarkers.size();i<m;i++) {
			Marker marker = visibleMarkers.get(i);
			if(marker.isHovered(point, scale)) return marker;
		}
		return null;
	}
	
    private Point screenToMap(Point screenPoint) {
        int mapX = (int) ((screenPoint.x - offset.x) / zoom);
        int mapY = (int) ((screenPoint.y - offset.y) / zoom);
        return new Point(mapX, mapY);
    }
}
