package de.rico.adventure.script;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.List;

import bsh.EvalError;
import bsh.Interpreter;
import de.rico.adventure.actions.ActionScheduler;
import de.rico.adventure.conversation.Conversation;
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.script.trigger.AbstractTrigger;
import de.rico.adventure.structure.Room;
import de.rico.engine.camera.Camera;
import de.rico.engine.effect.light.LightIface;
import de.rico.engine.effect.light.ogllight.AbstractLight;
import de.rico.engine.effect.texproj.TextureProjector;
import de.rico.engine.geometry.SkyBox;

/**
 * This class can be used to access and interpret script files
 * in connection with a room.
 * @author Frank Bruns
 */
public class ScriptInterpreter
{
	private Interpreter interpreter = null; // beanshell interpreter
	private List<Room> rooms = null;
	private List<Conversation> conversations = null;

	/**
	 * Creates a new script interpreter that can be used with
	 * the specied rooms.
	 * @param rooms rooms to bind to the script interpreter
	 * @param cons conversations to bind to the script interpreter
	 */
	public ScriptInterpreter(List<Room> rooms, List<Conversation> cons)
	{
		this.interpreter = new Interpreter();
		this.rooms = rooms;
		this.conversations = cons;

		// binds everything to the interpreter
		this.updateBinding();
	}


	/**
	 * Updates the binding of the rooms entities to the interpreter.
	 * This removes bindings to objects that are no longer part of the
	 * rooms or adds bindings to objects that are new to the rooms.
	 *
	 */
	public void updateBinding()
	{
		// reset the interpreter
		this.interpreter = new Interpreter();


		try
		{
			// bind elements of the rooms to the interpreter
			if(this.rooms != null)
			{
				// bind the camera object to the interpreter
				this.interpreter.set("camera", Camera.getInstance());

				// bind the action scheduler to the interpreter
				this.interpreter.set("scheduler", ActionScheduler.getInstance());


				for(Room room : this.rooms)
				{
					// bind the room itself to the interpreter
					this.interpreter.set(room.getScriptId(), room);

					// bind the OpenGL lights of the room to the interpreter
					for(LightIface light : room.getLights())
					{
						if(light instanceof AbstractLight)
						{
							AbstractLight l = (AbstractLight) light;
							this.interpreter.set(room.getScriptId()+"Light"+l.getLightNr(), l);
						}
					}

					// bind the room's texture projectors to the interpreter
					for(TextureProjector texProj : room.getTexProjectors())
					{
						this.interpreter.set(texProj.getName(), texProj);
					}

					// bind triggers to the interpreter
					for(AbstractTrigger trigger : room.getTriggers())
						this.interpreter.set(trigger.getScriptId(), trigger);

					// bind props to the interpreter
					for(Prop prop : room.getProps())
					{
						if(prop.getMesh() instanceof SkyBox == false)
							this.interpreter.set(prop.getScriptId(), prop);
					}

					// bind interactive hotspots to the interpreter
					for(AbstractInteractiveHotspot hotspot : room.getHotspots())
						this.interpreter.set(hotspot.getScriptId(), hotspot);

					// bind items to the interpreter
					for(Item item : room.getItems())
						this.interpreter.set(item.getScriptId(), item);

					// bind non-player characters to the interpreter
					for(NonPlayerCharacter npc : room.getCharacters())
						this.interpreter.set(npc.getScriptId(), npc);

					// bind player to the interpreter, if he is at
					// the room
					if(room.getPlayer() != null)
						this.interpreter.set(
							room.getPlayer().getScriptId(),
							room.getPlayer());
				}
			}

			// bind conversations to the interpreter
			if(this.conversations != null)
			{
				for(Conversation con : this.conversations)
					this.interpreter.set(con.getScriptId(), con);
			}
		}
		catch (EvalError e)
		{
			e.printStackTrace();
		}


		// bind the script file to the interpreter
		// for every room that was specified before
		try
		{
			if(this.rooms != null)
			{
				for(Room room : this.rooms)
					this.interpreter.source(room.getScriptFile());
			}
		}
		catch (Exception e)
		{
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	/**
	 * Binds a variable to the script interpreter. The provided script id
	 * will be used as a reference name.
	 * @param scriptId id to reference the object by
	 * @param variable variable to bind to the script interpreter
	 */
	public void bindVariable(String scriptId, Object variable)
	{
		try
		{
			this.interpreter.set(scriptId, variable);
		}
		catch (EvalError e)
		{
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	/**
	 * Unbinds the variable referenced by the specified script id
	 * from the script interpreter.
	 * @param scriptId id to reference the variable by
	 */
	public void unbindVariable(String scriptId)
	{
		try
		{
			this.interpreter.unset(scriptId);
		}
		catch (EvalError e)
		{
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	/**
	 * Invoke a method from the script file the interpreter has
	 * been initialized with.
	 * @param methodName method name in the script file
	 */
	public void invokeMethod(String methodName)
	{
		this.runCode(methodName);
	}

	/**
	 * Runs the specified scripted code.
	 * @param code script code to run
	 */
	public void runCode(String code)
	{
		try
		{
			this.interpreter.eval(code);
		}
		catch(EvalError exc)
		{
			System.out.println("Error while running scripted code '"
					+code+"'");
			exc.printStackTrace();
		}
	}

	/**
	 * Executes the specified script file line by line
	 * @param scriptFile script file to execute
	 */
	public void runScript(String scriptFile)
	{
		try
		{
			this.interpreter.source(scriptFile);
		}
		catch (FileNotFoundException e)
		{
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		catch (IOException e)
		{
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		catch (EvalError e)
		{
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}
