package walltest;

import java.awt.AWTEvent;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.event.KeyEvent;
import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.List;

import javax.swing.JApplet;

public class WallTest extends JApplet {

	private static final long serialVersionUID = 1L;
	
	static final Color[] colors = {Color.red.brighter(), Color.green.brighter(), Color.orange};

	protected void processKeyEvent(KeyEvent e) {
		if (e.getID() != KeyEvent.KEY_RELEASED) {
			return;
		}
		int kcode = e.getKeyCode();
		
		if (kcode == 38 /*up*/ || kcode == 40 /*down*/) {
			double moveStep = 0.18*(kcode == 38 ? 1 : -1);
			double newx = px+Math.cos(Math.toRadians(pa)) * moveStep;
			double newy = py+Math.sin(Math.toRadians(pa)) * moveStep;

			System.out.println(">>MOVE>> FROM >> x=" + px + ", y=" + py + " >> " +
					"TO >> newx=" + newx + ", newy=" + newy + " >>");
			
			px = newx;
			py = newy;
		}
		
		if (kcode == 37 /*left*/ || kcode == 39 /*right*/) {
			pa += (kcode == 37 ? -1 : 1)*6;
		}
		
		render();
	}
	
	public void init() {
		enableEvents(AWTEvent.KEY_EVENT_MASK);
		setSize(24+mapWidth*scale+24+320, 24+mapHeight*scale);
		setFocusable(true);
		requestFocusInWindow();
		
		stripe = new Stripe[320];
		for (int i = 0; i < stripe.length; i++) {
			stripe[i] = new Stripe();
		}
		
		render();
	}

	public void paint(Graphics g) {
		int xo = 12, yo = 12;
		
		// Clear
		g.setColor(Color.white);
		g.fillRect(0, 0, getWidth(), getHeight());
		
		// 2D-MAP-VIEW
		
		// Draw
		for (int i = 0; i < map.length; i++) {
			for (int j = 0; j < map[i].length; j++) {
				g.setColor(map[i][j] > 0 ? new Color(0x999999) : new Color(0xcccccc));
				g.fillRect(xo+j*scale, yo+i*scale, scale, scale);
				
				g.setColor(new Color(0x666666));
				g.drawRect(xo+j*scale, yo+i*scale, scale, scale);
				
				g.setColor(new Color(0xdddddd));
				g.drawString(j + "|" + i, xo+j*scale+2, yo+i*scale+19);
			}
		}
		
		// Draw trace lines
		g.setColor(Color.blue);
		for (Point2D point : lines) {
			g.drawLine(round(xo+px*scale), round(yo+py*scale), // Player base
					xo+round(point.getX()*scale), 
					yo+round(point.getY()*scale));
		}
		
		// Draw player
		g.setColor(Color.red);
		g.drawLine(round(xo+px*scale), round(yo+py*scale), // Player base
				xo+round((px+Math.cos(Math.toRadians(pa))*2)*scale), 
				yo+round((py+Math.sin(Math.toRadians(pa))*2)*scale));
		g.fillRect(round(xo+px*scale-2), round(yo+py*scale-2), 4, 4);
		
		// PSEUDO 3D-VIEW
		
		// Draw the view window
		xo = xo+mapWidth*scale+12;
		yo = 12;
		
		// Draw the background
		g.setColor(new Color(0x999999));
		g.fillRect(xo, yo, 320, 200);
		
		g.setColor(new Color(0xbbbbbb));
		g.fillRect(xo, yo, 320, 100);
		
		// Draw the stripes
		for (int i = 0; i < stripe.length; i++) {
			g.setColor(stripe[i].color);
			g.drawLine(xo+i, yo+stripe[i].top, xo+i, yo+stripe[i].top+stripe[i].height);
		}
		
		// Draw the frame
		g.setColor(Color.black);
		g.drawRect(xo, yo, 320-1, 200-1);
	}
	
	int[][] map = {
			{1, 1, 1, 1, 1, 1, 1, 1, 1, 3},
			{1, 0, 0, 0, 0, 0, 1, 0, 0, 3},
			{1, 0, 3, 0, 0, 0, 1, 0, 0, 3},
			{1, 1, 0, 0, 0, 0, 1, 0, 0, 3},
			{1, 0, 0, 0, 0, 0, 0, 0, 0, 3},
			{1, 0, 0, 0, 0, 0, 0, 0, 0, 3},
			{1, 0, 0, 0, 0, 0, 0, 0, 1, 3},
			{1, 0, 0, 0, 0, 0, 0, 2, 0, 3},
			{1, 0, 0, 0, 0, 0, 0, 0, 0, 3},
			{1, 0, 0, 0, 0, 2, 0, 0, 0, 3},
			{1, 3, 0, 0, 1, 0, 0, 0, 0, 3},
			{2, 2, 2, 2, 2, 2, 2, 2, 2, 3},
	};
	
	int scale = 30, mapWidth = map[0].length, mapHeight = map.length;
	
	double px = 2.8, py = 7.5640;
	int pa = -132; // Attention: in degree
	
	private Stripe[] stripe;
	
	static class Stripe {
		int height, top;
		public Color color;
	}
	
	List<Point2D> lines = new ArrayList<Point2D>();
	
	private void render() {
		lines.clear();
		
		long totalTime = 0, tmp = 0;
		int cnt = 0;
		for (double i = pa-30; i < pa+30; i += 60d/320d) {
			tmp = System.nanoTime();
			calc(i, cnt++);
			totalTime += System.nanoTime()-tmp;
		}
		System.out.println("Render cycle took " + totalTime + " ns");
		
		repaint();
	}
	
	private void calc(double a, int s) {
		a = (360-(a%360))%360;
		
		boolean up = a < 180, right = a < 90 | a > 270;
		a = Math.toRadians(a);
		
		
		// ---
		double x = 0, y = 0;
		
		// ceil => nächst höhere Ganzzahl
		// floor => nächst neiderigere Ganzzahl
		
		// ---
		double aSin = Math.sin(a), aCos = Math.cos(a), aTan = Math.abs(aSin/aCos);
		double xo0 = right ? Math.ceil(px)-px : px-Math.floor(px), 
				yo0 = up ? py-Math.floor(py) : Math.ceil(py)-py;
		
		int wallX = -1, wallY = -1;
		
		double dist = Double.MAX_VALUE;
		int hitx = 0, hity = 0;
		
		for (int i = 0; i < 40; i += 1) {
			
			// Vertical
			
			double ii = xo0+i;
			
			x = px+(right ? ii : -ii);
			y = py+(up ? -ii : ii)*aTan;
			
			wallX = round(Math.floor(x + (right ? 0 : -1)));
			wallY = round(Math.floor(y));
			
			
			if (-1 < wallX && -1 < wallY && wallX < mapWidth && wallY < mapHeight) {
				if (map[wallY][wallX] != 0) {
					double tmp = Math.sqrt(Math.pow(x-px, 2)+Math.pow(y-py, 2));
					if (tmp < dist) {
						dist = tmp;
						hitx = wallX;
						hity = wallY;
					}
					
				}
			}
			
			// Horizontal
			
			double iii = yo0+i;
			y = py+(up ? -iii : iii);
			x = px+(right ? 1 : -1)*(iii/aTan); // vgl.:
			// bel. Punkt K_i=(i/m|i) mit m Steigung
			
			wallX = round(Math.floor(x));
			wallY = round(Math.floor(y + (up ? -1 : 0)));
			
			
			if (-1 < wallX && -1 < wallY && wallX < mapWidth && wallY < mapHeight) {
				if (map[wallY][wallX] != 0) {
					double tmp = Math.sqrt(Math.pow(x-px, 2)+Math.pow(y-py, 2));
					if (tmp < dist) {
						dist = tmp;
						hitx = wallX;
						hity = wallY;
					}
					
				}
				
			}
			
		}
		
		
		lines.add(new Point2D.Double(px+Math.cos(a)*dist,
				py-Math.sin(a)*dist));
		
		
		
		Stripe str = stripe[s];
		str.color = colors[map[hity][hitx]%colors.length];
		dist = dist*Math.cos(Math.toRadians(pa)+a);
		
		double viewDist = 277d;//~: (width/2)/tan(60/2)=480/sqrt(3)
		str.height = round(viewDist/dist);
		str.top = round((200 - str.height) / 2);
		
		
	}
	
	
	
	
	

	
	
	

	
	public static int round(double num) {
		return (int) Math.round(num);
	}
	
}
