package zero1hd.rhythmbullet.audio.visualizer;

import java.util.Comparator;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.Camera;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.Mesh;
import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.graphics.VertexAttribute;
import com.badlogic.gdx.graphics.VertexAttributes.Usage;
import com.badlogic.gdx.graphics.glutils.ShaderProgram;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.Disposable;

import zero1hd.rhythmbullet.RhythmBullet;

public class CircularVisualizer implements Disposable {
	private PCMSystem pcm;
	private int centerX, centerY;
	private int r;
	private int componentCount = 2 + 1;
	private Array<Vector2> circlePoints;
	private float[] vertComponents;
	private float color;
	private Mesh mesh;
	private ShaderProgram shader;
	private int barCount = 180;
	private float barHeightMultiplier = 1.5f;
	private float[] audioSpectrum;
	private Camera camera;
	
	public CircularVisualizer(PCMSystem PCMSystem) {
		this.pcm = PCMSystem;
		shader = new ShaderProgram(Gdx.files.internal("shaders/mesh.vsh"), Gdx.files.internal("shaders/mesh.fsh"));
		if (!shader.isCompiled() || shader.getLog().length() != 0) {
			Gdx.app.debug("Circular visualizer shader", shader.getLog());
			Gdx.app.exit();
		}
		r = RhythmBullet.pixels_per_unit*RhythmBullet.SPAWN_CIRCLE_RADIUS;
	}
	
	/**
	 * Only should be called when all changes have been done.
	 */
	public void applyPositionChanges() {
		circlePoints = new Array<>();
		createCircleVertices(r);
		vertComponents = new float[circlePoints.size * componentCount];

		if (mesh != null) {
			mesh.dispose();
		}
		mesh = new Mesh(true, circlePoints.size, 0, new VertexAttribute(Usage.Position, 2, "a_position"), new VertexAttribute(Usage.ColorPacked, 4, "a_color"));
	}
	
	public void setCenter(int x, int y) {
		this.centerX = x;
		this.centerY = y;
	}
	
	public void drawVisualizer() {
		for (int circlePointIndex = 0; circlePointIndex < circlePoints.size; circlePointIndex++) {
			for (int comp = 0; comp < componentCount; comp++) {
				if (comp != 2) {
					vertComponents[circlePointIndex*componentCount + comp] = circlePoints.get(circlePointIndex).x + centerX;
					comp++;
					vertComponents[circlePointIndex*componentCount + comp] = circlePoints.get(circlePointIndex).y + centerY;
				} else {
					vertComponents[circlePointIndex*componentCount + comp] = color;
				}
			}
		}
		
		flush();
	}
	
	private void flush() {
		mesh.setVertices(vertComponents);
		Gdx.gl.glDepthMask(false);
		Gdx.gl.glEnable(GL20.GL_BLEND);
		Gdx.gl.glBlendFunc(GL20.GL_SRC_ALPHA, GL20.GL_ONE_MINUS_SRC_ALPHA);
		
		int vertexCount = circlePoints.size;
		((OrthographicCamera) camera).setToOrtho(false, Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
		
		shader.begin();
		mesh.render(shader, GL20.GL_LINE_STRIP, 0, vertexCount);
		shader.end();
		
		Gdx.gl.glDepthMask(true);
	}
	
	private void createCircleVertices(int radius) {
		int x = radius - 1;
		int y = 0;
		int dx = 1;
		int dy = 1;
		int err = dx - (radius << 1);
		circlePoints.clear();
		
	    while (x >= y) {
	    	circlePoints.add(new Vector2(+ x, + y));
	    	circlePoints.add(new Vector2(+ y, + x));
	    	circlePoints.add(new Vector2(- y, + x));
	    	circlePoints.add(new Vector2(- x, + y));
	    	circlePoints.add(new Vector2(- x, - y));
	    	circlePoints.add(new Vector2(- y, - x));
	    	circlePoints.add(new Vector2(+ y, - x));
	    	circlePoints.add(new Vector2(+ x, - y));
	    	
	        if (err <= 0) {
	            y++;
	            err += dy;
	            dy += 2;
	        } else {
	            x--;
	            dx += 2;
	            err += dx - (radius << 1);
	        }
	    }
	    
    	circlePoints.sort(new Comparator<Vector2>() {
			@Override
			public int compare(Vector2 o1, Vector2 o2) {
				double deg1;
				if (o1.x != 0 || o1.y != 0) {
					deg1 = Math.atan(Math.abs(o1.y)/Math.abs(o1.x));
					if (o1.x < 0) {
						if (o1.y < 0) {
							deg1 += 180;
						} else {
							deg1 = 180 - deg1;
						}
					} else {
						if (o1.y < 0) {
							deg1 = 360 - deg1;
						}
					}
					
				} else {
					if (o1.x != 0) {
						if (o1.x > 0) {
							deg1 = 0;
						} else {
							deg1 = 180;
						}
						deg1 = 0;
					} else {
						if (o1.y > 0) {
							deg1 = 90;
						} else {
							deg1 = 270;
						}
					}
				}
				
				double deg2;
				if (o2.x != 0 || o2.y != 0) {
					deg2 = Math.atan(Math.abs(o2.y)/Math.abs(o2.x));
					if (o2.x < 0) {
						if (o2.y < 0) {
							deg2 += 180;
						} else {
							deg2 = 180 - deg2;
						}
					} else {
						if (o2.y < 0) {
							deg2 = 360 - deg2;
						}
					}
				} else {
					if (o2.x != 0) {
						if (o2.x > 0) {
							deg2 = 0;
						} else {
							deg2 = 180;
						}
						deg2 = 0;
					} else {
						if (o2.y > 0) {
							deg2 = 90;
						} else {
							deg2 = 270;
						}
					}
				}
				
				if ((deg1 - deg2) > 0) {
					return 1;
				} else if (deg1 - deg2 == 0) {
					return 0;
				} else {
					return -1;
				}
					
			}
		});
	}
	
	public void setColor(float color) {
		this.color = color;
	}

	@Override
	public void dispose() {
		mesh.dispose();
		shader.dispose();
	}
	
	/**
	 * set the maximum radius
	 * @param r
	 */
	public void setR(int r) {
		this.r = r;
	}
	
	/**
	 * get the maximum radius
	 * @return
	 */
	public int getR() {
		return r;
	}
	
	public void setCamera(Camera camera) {
		this.camera = camera;
	}
	
	public Camera getCamera() {
		return camera;
	}
}
