/**
 *
 */
package de.rico.editor;

import java.awt.BorderLayout;
import java.awt.event.KeyEvent;
import java.util.ArrayList;
import java.util.LinkedList;

import javax.media.opengl.GL;
import javax.media.opengl.GLDrawable;

import de.rico.adventure.entity.character.NonPlayerCharacter;
import de.rico.adventure.entity.hotspot.AbstractInteractiveHotspot;
import de.rico.adventure.entity.item.Item;
import de.rico.adventure.entity.prop.Prop;
import de.rico.adventure.enums.State;
import de.rico.adventure.script.trigger.AbstractTrigger;
import de.rico.adventure.structure.Room;
import de.rico.adventure.structure.StateManager;
import de.rico.editor.datamodel.AbstractEntityData;
import de.rico.editor.datamodel.CharacterData;
import de.rico.editor.datamodel.DirectLightData;
import de.rico.editor.datamodel.GroundPlaneData;
import de.rico.editor.datamodel.HMTerrainData;
import de.rico.editor.datamodel.ItemData;
import de.rico.editor.datamodel.PosLightData;
import de.rico.editor.datamodel.PropData;
import de.rico.editor.datamodel.SkyBoxData;
import de.rico.editor.datamodel.SpotLightData;
import de.rico.editor.enums.Mode;
import de.rico.editor.gui.EditorMenu;
import de.rico.editor.gui.MainMenuBar;
import de.rico.editor.io.RoomIO;
import de.rico.engine.camera.Camera;
import de.rico.engine.camera.recorder.CamPlayer;
import de.rico.engine.camera.recorder.Recorder;
import de.rico.engine.effect.light.ogllight.DirectionalLight;
import de.rico.engine.effect.light.ogllight.PositionalLight;
import de.rico.engine.effect.light.ogllight.SpotLight;
import de.rico.engine.effect.ortho.RectangleRenderer;
import de.rico.engine.effect.ortho.TextRenderer;
import de.rico.engine.game.AbstractBaseGame;
import de.rico.engine.geometry.AbstractMesh;
import de.rico.engine.geometry.GroundPlane;
import de.rico.engine.geometry.Point3D;
import de.rico.engine.geometry.SkyBox;
import de.rico.engine.geometry.Terrain;
import de.rico.engine.geometry.collision.CollisionFinder;
import de.rico.engine.geometry.modelloader.md2.ModelMD2;
import de.rico.engine.geometry.modelloader.ms3d.ModelMS3DAscii;
import de.rico.engine.geometry.modelloader.threeds.Model3DS;
import de.rico.engine.input.InputAction;
import de.rico.engine.input.InputManager;
import de.rico.engine.input.KeyBoardMouseSwitcherControls;
import de.rico.engine.material.Color4f;
import de.rico.engine.path.GridNode;
import de.rico.engine.path.Path;
import de.rico.engine.util.HeapSizeDrawer;

/**
 * Main class for the world editor.
 *
 * @author Frank Bruns
 */
public class WorldEditor extends AbstractBaseGame
{
	private static String baseFolder = null; // path to room base folder

	private InputManager input = null;
	private KeyBoardMouseSwitcherControls controls = null;

	private InputAction mouseBtnLeft = null;
	private InputAction mouseBtnRight = null;

	public static Room room = null;

	private static ArrayList<AbstractEntityData> entitiesToAdd = new ArrayList<AbstractEntityData>();
	private static String pathLoadSceneFile = null;
	private static LinkedList<DirectLightData> directLightsToAdd = new LinkedList<DirectLightData>();
	private static LinkedList<PosLightData> posLightsToAdd = new LinkedList<PosLightData>();
	private static LinkedList<SpotLightData> spotLightsToAdd = new LinkedList<SpotLightData>();
	private static SkyBoxData skyBoxData = null;

	private static Mode editorMode = Mode.CONSTRUCTION;

	private TextRenderer textRenderer = null;

	private RectangleRenderer rectRenderer = null;

	public static Path path = null; // for creating a path in the editor

	public static Recorder camFlightRecorder = new Recorder(45);
	public static CamPlayer camFlightPlayer = null;
	private InputAction keyR = null; // key for start/stop recording
	private InputAction keyK = null; // for switching between keyboard cam and fps controls

	private HeapSizeDrawer heapDrawer = null;

	/**
	 * Creates a new world editor with the specified window title.
	 *
	 * @param title window title
	 */
	public WorldEditor(String title)
	{
		super(title,100);
	}

	/**
	 * Sets the base folder of the room's data.
	 * @param path path to base folder for the room data
	 */
	public static void setBaseFolder(String path)
	{
		baseFolder = path;
	}

	/**
	 * Returns the base folder of the room's data.
	 * @return path to base folder for the room data
	 */
	public static String getBaseFolder()
	{
		return baseFolder;
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see de.rico.engine.game.AbstractFirstPersonGame#draw(javax.media.opengl.GL)
	 */
	@Override
	public void draw(GL gl)
	{
		if(editorMode == Mode.CONSTRUCTION)
		{
			room.draw(gl);

			// draw hotspots
			for(AbstractInteractiveHotspot hs : room.getHotspots()) hs.draw(gl);
		}
		// draw ground mesh path grid, if in grid mode
		else if(editorMode == Mode.PATH_GRID)
		{
			// draw path grid and make all meshes more transparent
			room.getMeshes().drawTransparent(gl, 0.5f);

			// draw hotspots
			for(AbstractInteractiveHotspot hs : room.getHotspots()) hs.draw(gl);

			// draw the path search grid
			this.drawPathGrid(gl);
		}
		else if(editorMode == Mode.PATH_CREATOR)
		{
			room.draw(gl);
			path.draw(gl);
		}
		else if(editorMode == Mode.TRIGGER)
		{
			// make meshes transparent
			room.getMeshes().drawTransparent(gl, 0.5f);

			//draw trigger
			for(AbstractTrigger trigger : room.getTriggers())
				trigger.draw(gl);
		}
		else if(editorMode == Mode.RECORDER)
		{
			room.draw(gl);

			// draw black bars, to emphasize the cinematic character of
			// the camera flight

			// top bar
			this.rectRenderer.drawRectangle(
					0, 0,
					Camera.getInstance().getViewPort().getWidth(),
					(int)(Camera.getInstance().getViewPort().getHeight()*0.1f));

			// bottom bar
			this.rectRenderer.drawRectangle(
					0,
					Camera.getInstance().getViewPort().getHeight()
					-(int)(Camera.getInstance().getViewPort().getHeight()*0.1f),
					Camera.getInstance().getViewPort().getWidth(),
					(int)(Camera.getInstance().getViewPort().getHeight()*0.1f));
		}

		this.heapDrawer.draw(gl);
	}

	/**
	 * Draws the path grid of the ground mesh.
	 * @param gl
	 */
	private void drawPathGrid(GL gl)
	{
		if(room.getGround() != null)
		{
			room.getGround().getSearchGrid().draw(gl);
		}
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see de.rico.engine.game.AbstractFirstPersonGame#initResources(javax.media.opengl.GLDrawable,
	 *      javax.media.opengl.GL, int, int)
	 */
	@Override
	public void initResources(GLDrawable gld, GL gl, int width, int height)
	{
		this.input = new InputManager(this.getOGLCanvas());
		this.controls = new KeyBoardMouseSwitcherControls(this.input);
		this.keyR = new InputAction("start/stop recording", InputAction.DETECT_INITAL_PRESS_ONLY);
		this.input.assignKey(this.keyR, KeyEvent.VK_R);
		this.keyK = new InputAction("switch controls mode", InputAction.DETECT_INITAL_PRESS_ONLY);
		this.input.assignKey(this.keyK, KeyEvent.VK_K);
		this.mouseBtnLeft = new InputAction("left mouse btn");
		this.input.assignMouse(this.mouseBtnLeft, InputManager.MOUSE_BUTTON_1);
		this.mouseBtnRight = new InputAction("right mouse btn");
		this.input.assignMouse(this.mouseBtnRight, InputManager.MOUSE_BUTTON_3);

		path = new Path();

		this.textRenderer = new TextRenderer();
		this.heapDrawer = new HeapSizeDrawer(40,20);

		// create rectangle renderer for black bars
		this.rectRenderer = new RectangleRenderer(new Color4f(0f,0f,0f,1f));


		room = new Room(this.input);

		StateManager.getInstance().setState(State.NORMAL);
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see de.rico.engine.game.AbstractFirstPersonGame#update(long)
	 */
	@Override
	public void update(long elapsedTime)
	{
		this.controls.update(elapsedTime);

		// load the scene from a file, if available
		if(pathLoadSceneFile != null)
		{
			this.loadScene();
		}

		if(editorMode == Mode.CONSTRUCTION)
		{
			// add skybox
			this.addSkyBox();

			// add entities
			this.addAllEntities();

			// add lights
			this.addAllLights();

			// to postion a mesh by a mouse click
			this.handleMeshPositioning();

			// to position an interactive hotspot
			this.handleHotspotPositioning();
		}
		else if(editorMode == Mode.PATH_GRID)
		{
			this.handlePathGridManipulation();
		}
		else if(editorMode == Mode.PATH_CREATOR)
		{
			this.handlePathCreation();
		}
		else if(editorMode == Mode.TRIGGER)
		{
			this.handleTriggerPositioning();
		}
		else if(editorMode == Mode.RECORDER)
		{
			if(this.keyK.isPressed())
			{
				// switch between the camera control modes
				if(this.controls.isEnabledMouseCam() == false)
					this.controls.setEnabledMouseCam(true);
				else this.controls.setEnabledMouseCam(false);
			}

			if(this.keyR.isPressed())
			{
				if(camFlightRecorder.isRecording() == false)
				{
					// start recording
					camFlightRecorder.start();
				}
				else
				{
					// stop recording
					camFlightRecorder.stop();

					// and set the recording to the player
					camFlightPlayer = new CamPlayer(camFlightRecorder.getRecordedCameraFlight());
				}
			}

			camFlightRecorder.update(elapsedTime);

			// update player, if available
			if(camFlightPlayer != null)
			{
				if(camFlightRecorder.isRecording() == false)
					camFlightPlayer.update(elapsedTime);
			}
		}


		// always update the room
		room.update(elapsedTime);
	}

	/**
	 * Helper method that takes care of manipulating the path grid.
	 */
	private void handlePathGridManipulation()
	{
		// only do something if a ground mesh is avilable
		if(room.getGround() == null) return;

		int x = this.input.getMouseX();
		int y = this.input.getMouseY();
		Point3D pos = CollisionFinder.getPointOfIntersection(
				room.getGround(), x, y);

		GridNode node = null;

		// display if node is walkable
		if(pos != null)
		{
			// get the grid node
			node = room.getGround().getSearchGrid().getGridNode(pos.x, pos.z);
			if(node != null)
				this.textRenderer.drawString("walkable: "+node.isWalkable(), x+10, y-10);
		}

		// switch walkable status
		if(this.mouseBtnLeft.isPressed() == true)
		{
			if(pos != null)
			{
				if(node != null)
					if(node.isWalkable() == false)
						node.setWalkable(true);
				//else node.setWalkable(false);
			}
		}
		if(this.mouseBtnRight.isPressed() == true)
		{
			if(pos != null)
			{
				if(node != null)
					if(node.isWalkable() == true)
						node.setWalkable(false);
			}
		}
	}

	/**
	 * Helper method to create a path in the editor.
	 *
	 */
	private void handlePathCreation()
	{
		// only do something if a ground mesh is avilable
		if(room.getGround() == null) return;


		// add point to path with left click
		if(this.mouseBtnLeft.isPressed() == true)
		{
			this.mouseBtnLeft.reset();

			int x = this.input.getMouseX();
			int y = this.input.getMouseY();

			// find point on ground
			Point3D pos = CollisionFinder.getPointOfIntersection(
					room.getGround(), x, y);


			// add point to path, if available
			if(pos != null) path.addPoint(pos.x, pos.z);
		}

		// remove last point from path with right click
		if(this.mouseBtnRight.isPressed() == true)
		{
			this.mouseBtnRight.reset();
			if(path.getPoints().size() > 0) path.getPoints().removeLast();
		}
	}

	/**
	 * Helper method that takes care of positioning a mesh
	 * by a mouse click.
	 */
	private void handleMeshPositioning()
	{
		if(this.mouseBtnLeft.isPressed())
		{
			String name = EditorMenu.getSelectedMeshName();
			if(name != null)
			{
				AbstractMesh selectedMesh = room.getMeshes().getMeshByName(name);

				for(AbstractMesh mesh : room.getMeshes().getMeshes())
				{

					if(!mesh.equals(selectedMesh) && mesh instanceof SkyBox == false)
					{
						Point3D posOnMesh = CollisionFinder.getPointOfIntersection(
								mesh,
								this.input.getMouseX(),
								this.input.getMouseY());

						if(posOnMesh != null)
							selectedMesh.setPosition(posOnMesh);
					}
				}
			}
		}
	}

	/**
	 * Helper method that takes care of positioning a hotspot
	 * by a mouse click.
	 */
	private void handleHotspotPositioning()
	{
		if(this.mouseBtnLeft.isPressed())
		{
			String scriptId = EditorMenu.getSelectedHotspotName();
			if(scriptId != null)
			{
				AbstractInteractiveHotspot selectedHotspot = room.getHotspot(scriptId);

				for(AbstractMesh mesh : room.getMeshes().getMeshes())
				{
					if(mesh instanceof SkyBox == false)
					{
						Point3D posOnGround = CollisionFinder.getPointOfIntersection(
								mesh,
								this.input.getMouseX(),
								this.input.getMouseY());

						if(posOnGround != null)
						{
							selectedHotspot.setPosition(posOnGround);
						}
					}
				}
			}
		}
	}

	/**
	 * Helper method that takes care of positioning a trigger
	 * by a mouse click.
	 */
	private void handleTriggerPositioning()
	{
		if(this.mouseBtnLeft.isPressed())
		{
			String name = EditorMenu.getSelectedTriggerName();
			if(name != null)
			{
				AbstractTrigger selectedTrigger = room.getTrigger(name);

				// only position the trigger if there is a ground mesh in the room
				if(room.getGround() != null)
				{
					Point3D posOnGround = CollisionFinder.getPointOfIntersection(
							room.getGround(),
							this.input.getMouseX(),
							this.input.getMouseY());

					if(posOnGround != null)
					{
						selectedTrigger.setPosition(posOnGround.x, posOnGround.z);
					}
				}				
			}
		}
	}

	/**
	 * General method that call all neccessary methods to add
	 * lights to the scene.
	 */
	private void addAllLights()
	{
		for (DirectLightData dl : directLightsToAdd)
		{
			this.addDirectionalLight(dl);
		}
		directLightsToAdd.clear();

		for (PosLightData pl : posLightsToAdd)
		{
			this.addPositionalLight(pl);
		}
		posLightsToAdd.clear();

		for (SpotLightData sl : spotLightsToAdd)
		{
			this.addSpotLight(sl);
		}
		spotLightsToAdd.clear();
	}

	/**
	 * General method that call all neccessary methods to add
	 * entities to the scene.
	 */
	private void addAllEntities()
	{
		for (AbstractEntityData entity : entitiesToAdd)
		{
			if(entity.getMesh().getFilePath() != null)
			{
				if (entity.getMesh().getFilePath().endsWith(".3ds")
						|| entity.getMesh().getFilePath().endsWith(".3DS"))
				{
					this.addModel3ds(entity);
				}
				if (entity.getMesh().getFilePath().endsWith(".md2")
						|| entity.getMesh().getFilePath().endsWith(".MD2"))
				{
					this.addModelMd2(entity);
				}
				if (entity.getMesh().getFilePath().endsWith(".txt"))
				{
					this.addModelMs3d(entity);
				}

				if(entity.getMesh() instanceof HMTerrainData)
				{
					this.addHMTerrain(entity);
				}
			}

			if(entity.getMesh() instanceof GroundPlaneData)
			{
				this.addGroundPlane(entity);
			}

		}
		entitiesToAdd.clear();
	}

	/**
	 * Loads the scene from file. It clears the current scene data,
	 * before the scene gets loaded.
	 */
	private void loadScene()
	{
		// clear every existing scene objects beforehand
		skyBoxData = null;
		room.removeAllLights();
		room.removeAllCharacters();
		room.removeAllItems();
		room.removeAllProps();
		room.removeAllCameraPerspectives();
		room.removeAllTriggers();
		EditorMenu.clearMeshList();
		EditorMenu.clearLightList();
		EditorMenu.clearCamPerspectiveList();
		EditorMenu.clearTriggersList();

		new RoomIO(room,true).importRoom(pathLoadSceneFile);
		pathLoadSceneFile = null;
	}

	/**
	 * Adds a spot light to the scene.
	 * @param sl spot light data
	 */
	private void addSpotLight(SpotLightData sl)
	{
		SpotLight spot = new SpotLight(sl.getLightNr());
		spot.setPosition(sl.getPosition());
		spot.setRotX(sl.getRotX());
		spot.setRotY(sl.getRotY());
		spot.setCutOff(sl.getCutOff());
		spot.setExponent(sl.getExponent());
		spot.setAmbient(sl.getAmb());
		spot.setDiffuse(sl.getDiff());
		spot.setShowLightPosition(true);
		room.addLight(spot);

		EditorMenu.addLightToListMenu(sl.getLightNr());
	}

	/**
	 * Adds a positional light to the scene.
	 * @param pl positional light data
	 */
	private void addPositionalLight(PosLightData pl)
	{
		PositionalLight posLight = new PositionalLight(pl.getLightNr());
		posLight.setPosition(pl.getPosition());
		posLight.setAmbient(pl.getAmb());
		posLight.setDiffuse(pl.getDiff());
		posLight.setShowLightPosition(true);
		room.addLight(posLight);

		EditorMenu.addLightToListMenu(pl.getLightNr());
	}

	/**
	 * Adds a directional light to the scene.
	 * @param dl directional light data
	 */
	private void addDirectionalLight(DirectLightData dl)
	{
		DirectionalLight dirLight = new DirectionalLight(dl.getLightNr());
		float[] dir = dl.getDirection();
		dirLight.setDirection(dir[0],dir[1],dir[2]);
		dirLight.setAmbient(dl.getAmb());
		dirLight.setDiffuse(dl.getDiff());
		room.addLight(dirLight);
		EditorMenu.addLightToListMenu(dl.getLightNr());
	}

	/**
	 * Adds a ground plane to the scene.
	 * @param entity entity to create the ground plane from
	 */
	private void addGroundPlane(AbstractEntityData entity)
	{
		GroundPlaneData p = (GroundPlaneData)entity.getMesh();
		GroundPlane plane = new GroundPlane(p.getSize(), p.getTexRepeat());
		plane.setPosition(p.getPosition());
		if(!entity.getMesh().getName().equals(""))
			plane.setName(entity.getMesh().getName());
		else plane.setName("no mesh name");

		if(!entity.getMesh().getTexPath().equals(""))
			plane.setTexUnit0(entity.getMesh().getTexPath(), true,entity.getMesh().getTexRot(),entity.getMesh().getTexFlip());

		plane.initSearchGrid(p.getGridNodeSize());
		PropData pdata = (PropData)entity;
		Prop prop = new Prop(plane);

		if(!pdata.getScriptId().equals(""))
			prop.setScriptId(pdata.getScriptId());
		else prop.setScriptId("no script id");

		room.addProp(prop);
		EditorMenu.addEntityToListMenu(plane.getName()+" (prop)");
	}

	/**
	 * Adds a heightmap terrain to the scene.
	 * @param entity entity to create the terrain from
	 */
	private void addHMTerrain(AbstractEntityData entity)
	{
		HMTerrainData t = (HMTerrainData)entity.getMesh();
		Terrain terrain = new Terrain(t.getFilePath(),t.getTexPath(),t.getResFactor(),t.getSmooth());
		terrain.setPosition(t.getPosition());

		if(!entity.getMesh().getName().equals(""))
			terrain.setName(entity.getMesh().getName());
		else terrain.setName("no mesh name");

		if(!entity.getMesh().getTexPath().equals(""))
			terrain.setTexUnit0(entity.getMesh().getTexPath(), true,entity.getMesh().getTexRot(),entity.getMesh().getTexFlip());

		terrain.initSearchGrid(t.getGridNodeSize());
		PropData pdata = (PropData)entity;
		Prop p = new Prop(terrain);

		if(!pdata.getScriptId().equals(""))
			p.setScriptId(pdata.getScriptId());
		else p.setScriptId("no script id");

		room.addProp(p);
		EditorMenu.addEntityToListMenu(terrain.getName()+" (prop)");
	}

	/**
	 * Adds a Milkshape3D model to the scene.
	 * @param entity entity to create the model from
	 */
	private void addModelMs3d(AbstractEntityData entity)
	{
		// first create the model file and afterwards
		// create the entity using that model.
		ModelMS3DAscii model = new ModelMS3DAscii(entity.getMesh().getBoundingVolumeType());
		model.load(entity.getMesh().getFilePath(), 1f);
		model.setPosition(entity.getMesh().getPosition());

		if(!entity.getMesh().getName().equals(""))
			model.setName(entity.getMesh().getName());
		else model.setName("no mesh name");

		if(!entity.getMesh().getTexPath().equals(""))
			model.setTexUnit0(entity.getMesh().getTexPath(), true,entity.getMesh().getTexRot(),entity.getMesh().getTexFlip());

		model.setShadowEnabled(entity.getMesh().isShadowCaster());

		if(entity instanceof CharacterData)
		{
			NonPlayerCharacter c = new NonPlayerCharacter(model);

			if(!entity.getName().equals(""))
				c.setScreenName(entity.getName());

			if(!entity.getScriptId().equals(""))
				c.setScriptId(entity.getScriptId());
			else c.setScriptId("no script id");

			room.addCharacter(c);
			EditorMenu.addEntityToListMenu(model.getName()+" (npc)");
		}
		if(entity instanceof ItemData)
		{
			ItemData idata = (ItemData)entity;
			Item i = new Item(model);
			if(!entity.getName().equals(""))
				i.setScreenName(entity.getName());

			if(!entity.getScriptId().equals(""))
				i.setScriptId(entity.getScriptId());
			else i.setScriptId("no script id");

			if(!idata.getInventoryImgPath().equals(""))
				i.setInventoryImage(idata.getInventoryImgPath());

			i.setCanBePickedUp(idata.canBePickedUp());
			room.addItem(i);
			EditorMenu.addEntityToListMenu(model.getName()+" (item)");
		}
		if(entity instanceof PropData)
		{
			PropData pdata = (PropData)entity;
			Prop p = new Prop(model);

			if(!entity.getScriptId().equals(""))
				p.setScriptId(pdata.getScriptId());
			else p.setScriptId("no script id");

			room.addProp(p);
			EditorMenu.addEntityToListMenu(model.getName()+" (prop)");
		}
	}

	/**
	 * Adds a MD2 model to the scene.
	 * @param entity entity to create the model from
	 */
	private void addModelMd2(AbstractEntityData entity)
	{
		// first create the model file and afterwards
		// create the entity using that model.
		ModelMD2 model = new ModelMD2(entity.getMesh().getSmooth(),entity.getMesh().getBoundingVolumeType());
		model.load(entity.getMesh().getFilePath(), 1f);
		model.setPosition(entity.getMesh().getPosition());

		if(!entity.getMesh().getName().equals(""))
			model.setName(entity.getMesh().getName());
		else model.setName("no mesh name");

		if(!entity.getMesh().getTexPath().equals(""))
			model.setTexUnit0(entity.getMesh().getTexPath(), true,entity.getMesh().getTexRot(),entity.getMesh().getTexFlip());

		model.setShadowEnabled(entity.getMesh().isShadowCaster());

		if(entity instanceof CharacterData)
		{
			NonPlayerCharacter c = new NonPlayerCharacter(model);
			if(!entity.getName().equals(""))
				c.setScreenName(entity.getName());

			if(!entity.getScriptId().equals(""))
				c.setScriptId(entity.getScriptId());
			else c.setScriptId("no script id");

			room.addCharacter(c);
			EditorMenu.addEntityToListMenu(model.getName()+" (npc)");
		}
		if(entity instanceof ItemData)
		{
			ItemData idata = (ItemData)entity;
			Item i = new Item(model);
			if(!entity.getName().equals(""))
				i.setScreenName(entity.getName());

			if(!entity.getScriptId().equals(""))
				i.setScriptId(entity.getScriptId());
			else i.setScriptId("no script id");

			if(!idata.getInventoryImgPath().equals(""))
				i.setInventoryImage(idata.getInventoryImgPath());

			i.setCanBePickedUp(idata.canBePickedUp());
			room.addItem(i);
			EditorMenu.addEntityToListMenu(model.getName()+" (item)");
		}
		if(entity instanceof PropData)
		{
			PropData pdata = (PropData)entity;
			Prop p = new Prop(model);

			if(!entity.getScriptId().equals(""))
				p.setScriptId(entity.getScriptId());
			else p.setScriptId("no script id");

			room.addProp(p);
			EditorMenu.addEntityToListMenu(model.getName()+" (prop)");
		}
	}

	/**
	 * Adds a 3DS model to the scene.
	 * @param entity entity to create the model from
	 */
	private void addModel3ds(AbstractEntityData entity)
	{
		// first create the model file and afterwards
		// create the entity using that model.
		Model3DS model = new Model3DS(entity.getMesh().getSmooth(),entity.getMesh().getBoundingVolumeType());
		model.load(entity.getMesh().getFilePath(), 1f);
		model.setPosition(entity.getMesh().getPosition());

		if(!entity.getMesh().getName().equals(""))
			model.setName(entity.getMesh().getName());
		else model.setName("no mesh name");

		if(!entity.getMesh().getTexPath().equals(""))
			model.setTexUnit0(entity.getMesh().getTexPath(), true,entity.getMesh().getTexRot(),entity.getMesh().getTexFlip());

		model.setShadowEnabled(entity.getMesh().isShadowCaster());

		if(entity instanceof CharacterData)
		{
			NonPlayerCharacter c = new NonPlayerCharacter(model);

			if(!entity.getName().equals(""))
				c.setScreenName(entity.getName());
			if(!entity.getScriptId().equals(""))
				c.setScriptId(entity.getScriptId());
			else c.setScriptId("no script id");

			room.addCharacter(c);
			EditorMenu.addEntityToListMenu(model.getName()+" (npc)");
		}
		if(entity instanceof ItemData)
		{
			ItemData idata = (ItemData)entity;
			Item i = new Item(model);

			if(!idata.getName().equals(""))
				i.setScreenName(entity.getName());
			if(!idata.getScriptId().equals(""))
				i.setScriptId(idata.getScriptId());
			else i.setScriptId("no script id");

			if(!idata.getInventoryImgPath().equals(""))
				i.setInventoryImage(idata.getInventoryImgPath());

			i.setCanBePickedUp(idata.canBePickedUp());
			room.addItem(i);
			EditorMenu.addEntityToListMenu(model.getName()+" (item)");
		}
		if(entity instanceof PropData)
		{
			PropData pdata = (PropData)entity;
			Prop p = new Prop(model);
			if(!pdata.getScriptId().equals(""))
				p.setScriptId(pdata.getScriptId());
			else p.setScriptId("no script id");
			room.addProp(p);
			EditorMenu.addEntityToListMenu(model.getName()+" (prop)");
		}
	}

	/**
	 * Adds a skybox to the scene.
	 */
	private void addSkyBox()
	{
		if(skyBoxData != null)
		{
			SkyBox skyBox = new SkyBox(5f,5f,5f);
			if(!skyBoxData.getPosZ().equals(""))
				skyBox.setTextureSouth(skyBoxData.getPosZ());
			if(!skyBoxData.getNegZ().equals(""))
				skyBox.setTextureNorth(skyBoxData.getNegZ());
			if(!skyBoxData.getPosX().equals(""))
				skyBox.setTextureEast(skyBoxData.getPosX());
			if(!skyBoxData.getNegX().equals(""))
				skyBox.setTextureWest(skyBoxData.getNegX());
			if(!skyBoxData.getPosY().equals(""))
				skyBox.setTextureTop(skyBoxData.getPosY());
			if(!skyBoxData.getNegY().equals(""))
				skyBox.setTextureBottom(skyBoxData.getNegY());

			skyBox.setName("sky box");
			skyBox.init();
			skyBox.setPosY(-2.5f);
			Prop prop = new Prop(skyBox);
			room.addProp(prop);

			// set skybox data to null again
			skyBoxData = null;
		}
	}

	public static void main(String[] args)
	{
		// workaround for a bug in the Java2d pipeline
		System.setProperty("sun.java2d.noddraw", "true" );

		WorldEditor editor = new WorldEditor(
				"Rico Engine World Editor (by Frank Bruns)");
		editor.initCamera(0f, 10f, 50f, 0f, 0f, 0.1f, 2000f);
		editor.setDisplayFPS(true);
		editor.enableBackFaceCulling();

		// create GUI components
		editor.getMainFrame().add(new EditorMenu(), BorderLayout.EAST);
		editor.getMainFrame().add(new MainMenuBar(editor.getMainFrame()),
				BorderLayout.NORTH);

		editor.start(editor);
	}

	/**
	 * Marks an entity for being added to the
	 * scene during the next update() call.
	 *
	 * @param entity entity to be added
	 */
	public static void markEntityForAdding(AbstractEntityData entity)
	{
		entitiesToAdd.add(entity);
	}

	/**
	 * Marks a sky box (SkyBoxData object) for being added.
	 * @param sb SkyBoxData object
	 */
	public static void markSkyBoxForAdding(SkyBoxData sb)
	{
		skyBoxData = sb;
	}

	/**
	 * Marks a directinal light for being added to the scene during the next
	 * update() call.
	 *
	 * @param dl directional light editor data model
	 */
	public static void markDirectLightForAdding(DirectLightData dl)
	{
		directLightsToAdd.add(dl);
	}

	/**
	 * Marks a positinal light for being added to the scene during the next
	 * update() call.
	 *
	 * @param pl positional light editor data model
	 */
	public static void markPosLightForAdding(PosLightData pl)
	{
		posLightsToAdd.add(pl);
	}

	/**
	 * Marks a spot light for being added to the scene during the next
	 * update() call.
	 *
	 * @param sl spot light editor data model
	 */
	public static void markSpotLightForAdding(SpotLightData sl)
	{
		spotLightsToAdd.add(sl);
	}

	/**
	 * Marks a scene file (specified by its path) to be loaded during
	 * the next update() call.
	 * @param path path+ filename of an editor's scene file
	 */
	public static void markLoadScene(String path)
	{
		pathLoadSceneFile = path;
	}

	/**
	 * Sets the mode of the editor.
	 * @param mode value from the Mode enumeration
	 */
	public static void setMode(Mode mode)
	{
		editorMode = mode;
	}
}
