package de.pspaeth.thmer;

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.image.BufferedImage;

import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;

public class Horseshoe {
	private final static int PIX_SIZE = 1024;
	private final static double C2 = 0.1; // half the height of C
	private final static double alpha = 0.49; // shrink factor, must be < 0.5
	private final static int MAX_ITER = 20;
	
	class Point {
		double x;
		double y;
		public Point(double x, double y) {
			this.x = x;
			this.y = y;
		}
		public Point() {
		}
	}
	
	class MyWindowAdapter extends WindowAdapter {
		public void windowClosed(WindowEvent e) {
			//frame.dispose();
		}
	}

	class CanvasComponent extends JComponent {
		private static final long serialVersionUID = -6988216194347303275L;

		public void paintComponent(Graphics g) {
			super.paintComponent(g);
			if (pixbuff != null) {
				g.drawImage(pixbuff, 0, 0, null);
			}
		}

		public Dimension getPreferredSize() {
			return new Dimension(PIX_SIZE, PIX_SIZE);
		}
	}
	
	private BufferedImage pixbuff;
	private static JFrame frame;
	private CanvasComponent component;
	private JPanel controller;

	public static void main(String[] args) {
		try {
			new Horseshoe().go();
			Thread.sleep(20_000_000);
			//frame.dispose();
		}catch(Exception e) {
			e.printStackTrace(System.err);
		}
	}
	
	private void go() throws Exception {		
		pixbuff = new BufferedImage(PIX_SIZE, PIX_SIZE, BufferedImage.TYPE_INT_ARGB);
	
		controller = new JPanel();
		controller.setLayout(new BorderLayout(0,0));
		controller.setBackground(java.awt.Color.BLACK);

		component = new CanvasComponent();

		frame = new JFrame();
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		frame.setLayout(new BorderLayout(0, 0));
		frame.add(component, BorderLayout.CENTER);
		frame.add(controller, BorderLayout.NORTH);
		frame.pack();
		frame.setLocation(0, 0);
		frame.setVisible(true);
		frame.addWindowListener(new MyWindowAdapter());
		
		paint();
		
		for(int c = 0;c < PIX_SIZE * PIX_SIZE * 10;c++) {
			Point p0 = randomPoint(-1 + 2 * (c%2));
			//Point p0 = new Point(-1.0+2.0*((int)(c/1024))/1024.0, -1.0+2.0*((int)(c%1024))/1024.0);
			Point p = p0;
			int fugitive = -1;
			for(int i = 0; i < MAX_ITER;i++) {
				p = mapHorseshoe(p);
				if(p == null) {
					fugitive = i;
					break;
				}
			}
			//drawPoint(p);
			drawEscape(p0, fugitive);
			paint();
		}
	}
	
	@SuppressWarnings("unused")
	private void drawPoint(Point p) {
		if(p == null) return;
		int x = (int)( (p.x + 1) * PIX_SIZE / 2 );
		int y = (int)( (p.y + 1) * PIX_SIZE / 2 );
		if(x >= 0 && x < PIX_SIZE && y >= 0 && y < PIX_SIZE) {
			pixbuff.setRGB(x, y, 0xFFFF0000);
		}
	}

	private void drawEscape(Point p0, int fugitive) {
		int x = (int)( (p0.x + 1) * PIX_SIZE / 2 );
		int y = (int)( (p0.y + 1) * PIX_SIZE / 2 );
		double v = 1.0 * fugitive / MAX_ITER;
		int r = (int)(255 * v);
		int g = (int)(255 * v);
		int b = (int)(255 * v);
		if(x >= 0 && x < PIX_SIZE && y >= 0 && y < PIX_SIZE) {
			pixbuff.setRGB(x, y, 0xFF000000 | b | (g<<8) | (r<<16));
		}
	}

	private Point randomPoint(int i) {
		if(i<0) {
			return new Point(-1.0 + 2 * Math.random(), -1.0 + Math.random());
		} else {
			return new Point(-1.0 + 2 * Math.random(), Math.random());			
		}
	}

	private Point mapHorseshoe(Point p) {
		// C gets lost
		if(p.y > -C2 && p.y < C2)
			return null;
		
		double bdSize = 1 - C2;
		double bShift = C2 + 0.5 * bdSize;
		double dShift = -bShift;
		
		// outside f(B) gets lost
		if(p.y < 0 && (p.y+bShift) / alpha < -1.0)
			return null;
		if(p.y < 0 && (p.y+bShift) / alpha > 1.0)
			return null;

		// outside f(D) gets lost
		if(p.y > 0 && (p.y+dShift) / alpha > 1.0)
			return null;
		if(p.y > 0 && (p.y+dShift) / alpha < -1.0)
			return null;

		Point res = new Point();
		if(p.y < 0) {
			res.x = -0.5 + alpha * p.x;
			res.y  = (p.y+bShift) / bdSize / alpha;
		} else {
			res.x = 0.5 - alpha * p.x;
			res.y = - (p.y+dShift) / bdSize / alpha;
		}
		
		return res;
	}

	private void paint() throws Exception {
		component.repaint();
	}
}
