package com.framsticks.net.client3D;

import javax.media.opengl.GL;
import javax.media.opengl.GLAutoDrawable;
import javax.media.opengl.GLEventListener;
import javax.media.opengl.glu.GLU;

import pl.vorg.mowa.core.graphics.BoundingBox;
import pl.vorg.mowa.core.graphics.SkyBox;
import pl.vorg.mowa.core.graphics.Vec3;

import com.sun.opengl.util.FPSAnimator;

/**
 * Klasa odpowiedzialna za renderowanie framstickow i swiata
 * 
 * @author vorg
 */
public class Renderer implements GLEventListener {
	public static final float AUTO_ROTATION_STEP = 0.5f;
	private float rotationY = 0;
	private float rotationX = 0;
	private float translation = -1.0f;
	private FPSAnimator animator;
	private World world;
	private Creature[] creatures;
	private Creature.ModelType modelType = Creature.ModelType.MechParts;
	private cgfxEffect style;
	private SkyBox skyBox;
	

	
	public SkyBox getSkyBox()
	{
		return skyBox;
	}
	
	public void setSkyBox(SkyBox box)
	{
		skyBox = box;
	}
	
	public cgfxEffect getStyle() {
		return style;
	}

	/**
	 * Ustawia aktualnie stosowany styl renderowania framstickow 
	 * @param style
	 */
	public void setStyle(cgfxEffect style) {
		this.style = style;
	}

	public Creature[] getCreatures() {
		return creatures;
	}
	
	/**
	 * Ustawia liste stworow do wyswietlenia. Jezeli chcemy wyswietlic jednego stwora na zblizeniu nalezy tutaj przekazac 
	 * tablice jednoelementowa a modelType ustawic na Creature.ModelType.Parts
	 * @param creatures
	 */
	public void setCreatures(Creature[] creatures) {
		this.creatures = creatures;
	}
	
	public World getWorld() {
		return world;
	}

	/**
	 * Ustawia swiat do wyswietlenia
	 * @param world
	 */
	public void setWorld(World world) {
		this.world = world;
	}
	
	public Creature.ModelType getModelType() {
		return modelType;
	}

	/**
	 * Ustawia jak beda renderowane framsticki
	 * @param modelType
	 * Creature.ModelType.Parts - tak jak zostaly stworzone
	 * Creature.ModelType.MechParts - tak jak aktualnie wygladaja w swoim swiecie (zgodnie z prawami fizyki)
	 */
	public void setModelType(Creature.ModelType modelType) {
		this.modelType = modelType;
	}
	
	public float getTranslation()
	{
		return translation;
	}
	
	public void setTranslation(float t)
	{
		translation = t;
		if (translation < 0.1)
			translation = 0.1f;
	}

	public void resetTranslation()
	{
		translation = -1.0f;
	}

	
	public float getRotationX() {
		return rotationX;
	}
	
	public float getRotationY() {
		return rotationY;
	}

	public void setRotationX(float rotation) {
		this.rotationX = rotation;
		if (this.rotationX > 85.0) this.rotationX = 85.0f;
		else if (this.rotationX < 0) this.rotationX = 0.0f;
	}
	
	public void setRotationY(float rotation) {
		this.rotationY = rotation;
	}

	public void display(GLAutoDrawable drawable) {
		style.setSkyBox(skyBox);
		GL gl = drawable.getGL();
				
		gl.glLoadIdentity();
		gl.glClear(GL.GL_DEPTH_BUFFER_BIT | GL.GL_COLOR_BUFFER_BIT);
		
		if ((creatures != null) && (creatures.length == 1) && (modelType == Creature.ModelType.Parts)) {
			//renderowanie pojedynczego framsticka
			
			drawCreature(gl);
		}
		else {
			//renderowanie calaego swiata wraz z framstickami z nim zyjacymi 
			drawWorld(gl);
		}

		gl.glFlush();
	}
	
	public void drawWorld(GL gl) {
		
		//szescian otaczajacy swiat
		//wyliczamy go po to aby ustawic kamere tak aby byla w stanie objac wszystko
		BoundingBox bbox;
		if ((world != null) && (world.getGeometry() != null)) {
			bbox = world.getGeometry().getBoundingBox();
			
		}
		else {
			bbox = new BoundingBox();
			if (creatures != null) {
				for(Creature c : creatures) {
					bbox.add(c.getBoundingBox(Creature.ModelType.MechParts));
				}
			}
		}
		
		Vec3 bboxSize = bbox.getSize();
		Vec3 bboxCenter = Vec3.mul(0.5f, Vec3.add(bbox.getMax(), bbox.getMin()));
		
		if (translation < 0 || translation > 100000000.0f)
		{
			translation = (float)Math.sqrt(bboxSize.getX()*bboxSize.getX()/4+bboxSize.getZ()*bboxSize.getZ()/4) * 1.5f;
		}
		
		
		
		gl.glTranslatef(0, 0, -1.0f*translation);
		//gl.glRotatef(30, 1, 0, 0);
		
		gl.glRotatef(rotationX, 1, 0, 0);
		gl.glRotatef(rotationY, 0, 1, 0);
		gl.glTranslatef(-bboxCenter.getX(), -bboxCenter.getY()-2, -bboxCenter.getZ());
		if (skyBox != null)
			skyBox.Draw(gl, 1000.0f, new Vec3(rotationX,rotationY,0));
		//renderowanie swiata
		if ((world != null) && (world.getGeometry() != null) && !world.getGeometry().getBoundingBox().isEmpty()) {
			BoundingBox wrldBox = world.getGeometry().getBoundingBox();
			Vec3 wmin = wrldBox.getMin();
			Vec3 wmax = wrldBox.getMax();
			
			drawWorldGeometry(gl);
			drawWater(gl, wmin, wmax);
			if (world.getBoundaries() == 0) {
				drawWorldNoBounduaries(gl, wmin, wmax);
			}
			else if (world.getBoundaries() == 1) {
				drawWorldFence(gl, wmin, wmax);
			}
			else if (world.getBoundaries() == 2) {
				drawWorldTeleports(gl, wmin, wmax);
			}
			
		}
		
		//renderowanie stworow
		if ((creatures != null) && (style != null)) {
			if (!style.isInitialized()) {
				style.Init();
				if (!style.isInitialized()) {
					setStyle(null);		
					return;
				} 
			}
			style.render(gl, creatures, Creature.ModelType.MechParts);
		}
	}

	/**
	 * Renderowanie powierchni swiata
	 * @param gl
	 */
	private void drawWorldGeometry(GL gl) {
		gl.glEnable(GL.GL_LIGHTING);
		gl.glEnable(GL.GL_LIGHT0);
		gl.glColor4f(0.9f, 0.8f, 0.2f, 0.0f);   
		gl.glEnable(GL.GL_COLOR_MATERIAL);
		gl.glDisable(GL.GL_BLEND);
		world.getGeometry().display(gl);
		gl.glDisable(GL.GL_COLOR_MATERIAL);
		gl.glDisable(GL.GL_LIGHTING);
		//gl.glEnable(GL.GL_CULL_FACE);

		gl.glColor4f(0.7f, 0.7f, 0.7f, 0.1f);
		gl.glPolygonMode(GL.GL_FRONT_AND_BACK, GL.GL_LINE);
		gl.glEnable(GL.GL_LINE_SMOOTH);
		gl.glEnable(GL.GL_BLEND);
		//gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA);
		gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_DST_COLOR);
		gl.glHint(GL.GL_LINE_SMOOTH_HINT, GL.GL_DONT_CARE);
		gl.glLineWidth(2.5f);
		world.getGeometry().display(gl);
		gl.glDisable(GL.GL_BLEND);
		gl.glDisable(GL.GL_LINE_SMOOTH);
		gl.glPolygonMode(GL.GL_FRONT_AND_BACK, GL.GL_FILL);
	}

	/**
	 * Renderowanie brzegow swiata otoczonego teleportami (wchodzisz z jednej strony swiata a wychodzisz po przeciwleglej) 
	 * @param gl
	 * @param wmin
	 * @param wmax
	 */
	private void drawWorldTeleports(GL gl, Vec3 wmin, Vec3 wmax) {
		gl.glEnable(GL.GL_BLEND);
		gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA);
		gl.glColor4f(0.0f, 0.4f, 0.9f, 0.5f);
		gl.glBegin(GL.GL_TRIANGLES);
		for(float z=-wmin.getZ(); z<wmax.getZ(); z+=1) {
			gl.glVertex3f(wmin.getX(), 0, z);
			gl.glVertex3f(wmin.getX(), 0, z+1);
			gl.glVertex3f(wmin.getX(), 1, z+0.5f);
		}
		for(float z=-wmin.getZ(); z<wmax.getZ(); z+=1) {
			gl.glVertex3f(wmax.getX(), 0, z);
			gl.glVertex3f(wmax.getX(), 0, z+1);
			gl.glVertex3f(wmax.getX(), 1, z+0.5f);
		}
		for(float x=-wmin.getX(); x<wmax.getX(); x+=1) {
			gl.glVertex3f(x  , 0, wmin.getZ());
			gl.glVertex3f(x+1, 0, wmin.getZ());
			gl.glVertex3f(x+0.5f, 1, wmin.getZ());
		}
		for(float x=-wmin.getX(); x<wmax.getX(); x+=1) {
			gl.glVertex3f(x  , 0, wmax.getZ());
			gl.glVertex3f(x+1, 0, wmax.getZ());
			gl.glVertex3f(x+0.5f, 1, wmax.getZ());
		}
		gl.glEnd();
		gl.glDisable(GL.GL_BLEND);
	}

	/**
	 * Rysowanie brzegow swiata otoczonego przez plot
	 * @param gl
	 * @param wmin
	 * @param wmax
	 */
	private void drawWorldFence(GL gl, Vec3 wmin, Vec3 wmax) {
		gl.glEnable(GL.GL_BLEND);
		gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA);
		gl.glColor4f(1.0f, 0.2f, 0.0f, 0.5f);
		gl.glBegin(GL.GL_QUADS);
		for(float z=-wmin.getZ(); z<wmax.getZ(); z+=1) {
			gl.glVertex3f(wmin.getX(), 0, z);
			gl.glVertex3f(wmin.getX(), 0, z+1);
			gl.glVertex3f(wmin.getX(), 1, z+1);
			gl.glVertex3f(wmin.getX(), 1, z);
		}
		for(float z=-wmin.getZ(); z<wmax.getZ(); z+=1) {
			gl.glVertex3f(wmax.getX(), 0, z);
			gl.glVertex3f(wmax.getX(), 0, z+1);
			gl.glVertex3f(wmax.getX(), 1, z+1);
			gl.glVertex3f(wmax.getX(), 1, z);
		}
		for(float x=-wmin.getX(); x<wmax.getX(); x+=1) {
			gl.glVertex3f(x  , 0, wmin.getZ());
			gl.glVertex3f(x+1, 0, wmin.getZ());
			gl.glVertex3f(x+1, 1, wmin.getZ());
			gl.glVertex3f(x  , 1, wmin.getZ());
		}
		for(float x=-wmin.getX(); x<wmax.getX(); x+=1) {
			gl.glVertex3f(x  , 0, wmax.getZ());
			gl.glVertex3f(x+1, 0, wmax.getZ());
			gl.glVertex3f(x+1, 1, wmax.getZ());
			gl.glVertex3f(x  , 1, wmax.getZ());
		}
		gl.glEnd();
		gl.glDisable(GL.GL_BLEND);
	}

	/**
	 * Rysowanie brzegow swiata nieograniczonego
	 * @param gl
	 * @param wmin
	 * @param wmax
	 */
	private void drawWorldNoBounduaries(GL gl, Vec3 wmin, Vec3 wmax) {
		gl.glEnable(GL.GL_BLEND);
		gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA);
		gl.glBegin(GL.GL_QUADS);
		for(float z=-wmin.getZ(); z<wmax.getZ(); z+=1) {
			gl.glColor4f(1.0f, 0.8f, 0.0f, 0.5f);
			gl.glVertex3f(wmin.getX(), 0, z);
			gl.glVertex3f(wmin.getX(), 0, z+1);
			gl.glColor4f(1.0f, 0.8f, 0.0f, 0.0f);
			gl.glVertex3f(wmin.getX()-2, 0, z+1);
			gl.glVertex3f(wmin.getX()-2, 0, z);
		}
		for(float z=-wmin.getZ(); z<wmax.getZ(); z+=1) {
			gl.glColor4f(1.0f, 0.8f, 0.0f, 0.5f);
			gl.glVertex3f(wmax.getX(), 0, z);
			gl.glVertex3f(wmax.getX(), 0, z+1);
			gl.glColor4f(1.0f, 0.8f, 0.0f, 0.0f);
			gl.glVertex3f(wmax.getX()+2, 0, z+1);
			gl.glVertex3f(wmax.getX()+2, 0, z);
		}
		for(float x=-wmin.getX(); x<wmax.getX(); x+=1) {
			gl.glColor4f(1.0f, 0.8f, 0.0f, 0.5f);
			gl.glVertex3f(x  , 0, wmin.getZ());
			gl.glVertex3f(x+1, 0, wmin.getZ());
			gl.glColor4f(1.0f, 0.8f, 0.0f, 0.0f);
			gl.glVertex3f(x+1, 0, wmin.getZ()-2);
			gl.glVertex3f(x  , 0, wmin.getZ()-2);
		}
		for(float x=-wmin.getX(); x<wmax.getX(); x+=1) {
			gl.glColor4f(1.0f, 0.8f, 0.0f, 0.5f);
			gl.glVertex3f(x  , 0, wmax.getZ());
			gl.glVertex3f(x+1, 0, wmax.getZ());
			gl.glColor4f(1.0f, 0.8f, 0.0f, 0.0f);
			gl.glVertex3f(x+1, 0, wmax.getZ()+2);
			gl.glVertex3f(x  , 0, wmax.getZ()+2);
		}
		gl.glEnd();
		
		gl.glBegin(GL.GL_TRIANGLES);
		
		gl.glColor4f(1.0f, 0.8f, 0.0f, 0.5f);
		gl.glVertex3f(wmin.getX(), 0, wmin.getX());				
		gl.glColor4f(1.0f, 0.8f, 0.0f, 0.0f);
		gl.glVertex3f(wmin.getX(), 0, wmin.getX()-2);
		gl.glVertex3f(wmin.getX()-2, 0, wmin.getX());
		
		gl.glColor4f(1.0f, 0.8f, 0.0f, 0.5f);
		gl.glVertex3f(wmax.getX(), 0, wmin.getX());				
		gl.glColor4f(1.0f, 0.8f, 0.0f, 0.0f);
		gl.glVertex3f(wmax.getX(), 0, wmin.getX()-2);
		gl.glVertex3f(wmax.getX()+2, 0, wmin.getX());
		
		gl.glColor4f(1.0f, 0.8f, 0.0f, 0.5f);
		gl.glVertex3f(wmax.getX(), 0, wmax.getX());				
		gl.glColor4f(1.0f, 0.8f, 0.0f, 0.0f);
		gl.glVertex3f(wmax.getX(), 0, wmax.getX()+2);
		gl.glVertex3f(wmax.getX()+2, 0, wmax.getX());
		
		gl.glColor4f(1.0f, 0.8f, 0.0f, 0.5f);
		gl.glVertex3f(wmin.getX(), 0, wmax.getX());				
		gl.glColor4f(1.0f, 0.8f, 0.0f, 0.0f);
		gl.glVertex3f(wmin.getX(), 0, wmax.getX()+2);
		gl.glVertex3f(wmin.getX()-2, 0, wmax.getX());
		
		gl.glEnd();
		gl.glDisable(GL.GL_BLEND);
	}

	/**
	 * Renderowanie poziomu wody
	 * @param gl
	 * @param wmin
	 * @param wmax
	 */
	private void drawWater(GL gl, Vec3 wmin, Vec3 wmax) {
		float wlev = world.getWaterLevel();
		gl.glEnable(GL.GL_BLEND);
		gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA);
		gl.glBegin(GL.GL_QUADS);
			gl.glColor4f(0.1f, 0.5f, 0.9f, 0.3f);
			gl.glVertex3f(wmin.getX()-5, wlev-0.02f, wmin.getZ()-5);
			gl.glColor4f(0.1f, 0.8f, 0.9f, 0.3f);
			gl.glVertex3f(wmin.getX()-5, wlev-0.02f, wmax.getZ()+5);
			gl.glColor4f(0.1f, 0.5f, 0.9f, 0.3f);
			gl.glVertex3f(wmax.getX()+5, wlev-0.02f, wmax.getZ()+5);
			gl.glColor4f(0.1f, 0.8f, 0.9f, 0.3f);
			gl.glVertex3f(wmax.getX()+5, wlev-0.02f, wmin.getZ()-5);
			
			gl.glColor4f(0.0f, 0.0f, 0.0f, 0.2f);
			gl.glVertex3f(wmin.getX()-0.5f, wlev-0.01f, wmin.getZ()-0.5f);
			gl.glVertex3f(wmin.getX()-0.5f, wlev-0.01f, wmax.getZ()+0.5f);
			gl.glVertex3f(wmax.getX()+0.5f, wlev-0.01f, wmax.getZ()+0.5f);
			gl.glVertex3f(wmax.getX()+0.5f, wlev-0.01f, wmin.getZ()-0.5f);
		gl.glEnd();
		gl.glDisable(GL.GL_BLEND);
	}
	
	/**
	 * Rysowanie pojedynczego stwora
	 * @param gl
	 */
	private void drawCreature(GL gl) {
		BoundingBox bbox = creatures[0].getBoundingBox(Creature.ModelType.Parts);
		
		Vec3 bboxSize = bbox.getSize();
		Vec3 bboxCenter = Vec3.mul(0.5f, Vec3.add(bbox.getMax(), bbox.getMin()));
		
		if (translation < 0)
		{
			translation = (float)Math.sqrt(bboxSize.getX()*bboxSize.getX()/4+bboxSize.getZ()*bboxSize.getZ()/4);
		}
		
		
		
		gl.glTranslatef(0, 0, -1.0f*translation-1);
		//gl.glRotatef(30, 1, 0, 0);
		gl.glRotatef(rotationX, 1, 0, 0);
		gl.glRotatef(rotationY, 0, 1, 0);
		gl.glTranslatef(-bboxCenter.getX(), -bboxCenter.getY(), -bboxCenter.getZ());
		if (skyBox != null)
			skyBox.Draw(gl, 1000.0f, new Vec3(rotationX,rotationY,0));
		
		if (style != null) {
			if (!style.isInitialized()) {
				style.Init();
				if (!style.isInitialized()) {
					setStyle(null);		
					return;
				} 
			}

			//effect.render(gl, creatures, Creature.ModelType.Parts);
			style.render(gl, creatures, Creature.ModelType.Parts);
		}
	}

	public void displayChanged(GLAutoDrawable drawable, boolean modeChanged, boolean deviceChanged) {
	}

	public void init(GLAutoDrawable drawable) {
		Log.getInstance().log("dbg", "Renderer.init");
		Log.getInstance().log("dbg", "Renderer.init creating animator");
		animator = new FPSAnimator(30);
		animator.add(drawable);
		animator.start();
		animator.setRunAsFastAsPossible(false);
		rotationX = 30.0f;
		rotationY = 0.0f;
		Log.getInstance().log("dbg", "Renderer.init initializing opengl");
		GL gl = drawable.getGL();
		gl.glLoadIdentity();
		gl.glClearColor(0.0f, 0.0f, 0.0f,  1.0f);
		gl.glColor3f(1.0f, 1.0f, 1.0f);	
		gl.glClearDepth(1.0);				
		gl.glEnable(GL.GL_DEPTH_TEST);
		gl.glDepthFunc(GL.GL_LEQUAL);
		skyBox = new SkyBox();
		skyBox.Init(gl);
		//style.Init();
		Log.getInstance().log("dbg", "Renderer.init done");
	}

	/**
	 * Dostosowanie widoku do rozmiarow okna aplikacji
	 */
	public void reshape(GLAutoDrawable drawable, int x, int y, int width, int height) {
		GL gl = drawable.getGL();
		GLU glu = new GLU();
		gl.glMatrixMode(GL.GL_PROJECTION);
		gl.glLoadIdentity();
		glu.gluPerspective(90, (float)width/(float)height, 0.015, 1000);
		gl.glMatrixMode(GL.GL_MODELVIEW);
	}
}
