package de.rico.adventure.io;

import java.util.List;

import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Node;
import org.dom4j.io.SAXReader;

import de.rico.adventure.entity.character.PlayableCharacter;
import de.rico.adventure.inventory.AbstractInventory;
import de.rico.adventure.inventory.BarInventory;
import de.rico.adventure.main.ShootOut3DMain;
import de.rico.adventure.script.trigger.AbstractTrigger;
import de.rico.adventure.structure.Chapter;
import de.rico.adventure.structure.Room;
import de.rico.engine.effect.sound.Mp3Sound;
import de.rico.engine.geometry.MeshVA;
import de.rico.engine.geometry.modelloader.Animation;
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.InputManager;
import de.rico.engine.material.Color4f;

/**
 * This class can be used to read in XML style chapter
 * initialization files (.chp-files).
 * @author Frank Bruns
 *
 */
public class ChapterReader
{


	/**
	 * Reads the games chapter data from the specified file.
	 * @param path filepath + name of the file to load from
	 * @param input input manager object
	 */
	public Chapter readChapter(String path, InputManager input)
	{
		Document document = null;
		SAXReader reader = new SAXReader();
		reader.setEncoding("ISO-8859-1");
        try
		{
        	document = reader.read(path);
		}
		catch (DocumentException e)
		{
			System.out.println("Error while parsing file.");
			e.printStackTrace();
		}

		// read the information from the loaded file
		// read in data, if available
		Node chapterNode = document.selectSingleNode("//chapter");
		if(chapterNode != null)
		{
			// load the image to use as a background image during the
			// loading procedure
			Node loadScreenNode = chapterNode.selectSingleNode("loadscreen/@file");
			String loadScreen = null;
			if(loadScreenNode != null) loadScreen = loadScreenNode.getText();

			ShootOut3DMain.getInstance().showLoadingInfo(loadScreen);

			// interrupt render thread (this thread) for a short time to allow
			// the resource loading display thread to start safely
			try
			{
				Thread.sleep(750);
			}
			catch (InterruptedException e)
			{
				// TODO Auto-generated catch block
				e.printStackTrace();
			}

			Chapter chapter = new Chapter();

			// find rooms and add them
			List<Node> roomNodes = chapterNode.selectNodes("room");
			for(Node roomNode : roomNodes)
			{
				String roomFilePath = roomNode.selectSingleNode("@file").getText();
				String camNr = roomNode.selectSingleNode("@cam").getText();

				Room room = new Room(roomFilePath,input);
				room.setInitialCamPerspective(new Integer(camNr));
				chapter.addRoom(room);
			}


			// read in player data, if available
			Node playerNode = chapterNode.selectSingleNode("player");
			String room = null; // starting room
			if(playerNode != null)
			{
				ShootOut3DMain.getInstance().setLoadingInfoText("Loading: player model");

				// if the player model is a md2 or ms3dascii file
				if(playerNode.selectSingleNode("@type").getText().equals("ms3dascii")
						|| playerNode.selectSingleNode("@type").getText().equals("md2"))
				{
					String smooth = playerNode.selectSingleNode("@smooth").getText();
					String shadow = playerNode.selectSingleNode("@shadow").getText();
					String internalId = playerNode.selectSingleNode("@id").getText();
					String file = playerNode.selectSingleNode("file").getText();
					String tex0 = null;
					String mipmap = "false";
					String rotate = "false";
					String flip = "false";

					// find texture nodes, according to their texture unit
					List<Node> texNodes = playerNode.selectNodes("texture");
					for(Node texNode : texNodes)
					{
						if(texNode.selectSingleNode("@unit").getText().equals("0"))
						{
							tex0 = texNode.getText();
							mipmap = rotate = texNode.selectSingleNode("@mipmap").getText();
							rotate = texNode.selectSingleNode("@rotate").getText();
							flip = texNode.selectSingleNode("@flip").getText();
						}
					}

					// get position, rotation and scale data
					String x = playerNode.selectSingleNode("position/@x").getText();
					String y = playerNode.selectSingleNode("position/@y").getText();
					String z = playerNode.selectSingleNode("position/@z").getText();

					String rotX = playerNode.selectSingleNode("rotation/@x").getText();
					String rotY = playerNode.selectSingleNode("rotation/@y").getText();
					String rotZ = playerNode.selectSingleNode("rotation/@z").getText();

					String scaleX = playerNode.selectSingleNode("scale/@x").getText();
					String scaleY = playerNode.selectSingleNode("scale/@y").getText();
					String scaleZ = playerNode.selectSingleNode("scale/@z").getText();

					// create the model object with the read in data
					MeshVA model = null;

					if(playerNode.selectSingleNode("@type").getText().equals("ms3dascii"))
					{
						model = new ModelMS3DAscii();

						ModelMS3DAscii ms3d = (ModelMS3DAscii)model;
						ms3d.load(file, 1f);
					}
					else if(playerNode.selectSingleNode("@type").getText().equals("md2"))
					{
						model = new ModelMD2(new Boolean(smooth));

						ModelMS3DAscii ms3d = (ModelMS3DAscii)model;
						ms3d.load(file, 1f);
					}

					model.setTexUnit0(
							tex0,
							Boolean.parseBoolean(mipmap),
							Boolean.parseBoolean(rotate),
							Boolean.parseBoolean(flip));
					model.setName(internalId);
					model.setPosition(
							Float.parseFloat(x),
							Float.parseFloat(y),
							Float.parseFloat(z));
					model.setRotation(
							Float.parseFloat(rotX),
							Float.parseFloat(rotY),
							Float.parseFloat(rotZ));
					model.scale(
							Float.parseFloat(scaleX),
							Float.parseFloat(scaleY),
							Float.parseFloat(scaleZ));
					model.setShadowEnabled(Boolean.parseBoolean(shadow));

					// add animations
					List<Node> animNodes = playerNode.selectNodes("animation");
					for(Node animNode : animNodes)
					{
						String animName = animNode.selectSingleNode("@name").getText();
						String start = animNode.selectSingleNode("@start").getText();
						String end = animNode.selectSingleNode("@end").getText();
						String durPerFrame = animNode.selectSingleNode("@timeperframe").getText();
						Node soundNode = animNode.selectSingleNode("@sound");
						Node loopSoundNode = animNode.selectSingleNode("@loopsound");

						// add animation with sound file
						if(soundNode != null)
						{
							String sound = animNode.selectSingleNode("@sound").getText();

							boolean loop = false;
							if(loopSoundNode != null) loop = new Boolean(loopSoundNode.getText());

							model.addAnimation(
									new Animation(animName,
									new Integer(start),
									new Integer(end),
									new Integer(durPerFrame),new Mp3Sound(sound, loop)));
						}
						else // add animation without sound file
						{
							model.addAnimation(
									new Animation(animName,
									new Integer(start),
									new Integer(end),
									new Integer(durPerFrame)));
						}

						model.setAnimation(animName);
					}

					room = playerNode.selectSingleNode("room").getText();
					String name = playerNode.selectSingleNode("name").getText();
					String scriptId = playerNode.selectSingleNode("@scriptid").getText();
					String inventoryType = playerNode.selectSingleNode("inventory/@type").getText();

					AbstractInventory inv = null;
					if(inventoryType.equals("bar"))
					{
						inv = new BarInventory(100,10,new Color4f(0f,1f,0f,1f),input);
						inv.setBGColor(new Color4f(0f,0f,0f,0.65f));
					}

					PlayableCharacter player = new PlayableCharacter(model,inv);
					player.setScreenName(name);
					player.setScriptId(scriptId);

					chapter.setActiveRoom(room);
					chapter.getActiveRoom().setPlayer(player);

					// register player to the triggers of the first room, other registerings will
					// be done during room change
					for(AbstractTrigger trigger : chapter.getActiveRoom().getTriggers())
						trigger.addCandidate(player);
				}

				// if the player model is a 3ds file
				if(playerNode.selectSingleNode("@type").getText().equals("3ds"))
				{
					String smooth = playerNode.selectSingleNode("@smooth").getText();
					String shadow = playerNode.selectSingleNode("@shadow").getText();
					String internalId = playerNode.selectSingleNode("@id").getText();
					String file = playerNode.selectSingleNode("file").getText();
					String tex0 = null;
					String mipmap = "false";
					String rotate = "false";
					String flip = "false";

					// find texture nodes, according to their texture unit
					List<Node> texNodes = playerNode.selectNodes("texture");
					for(Node texNode : texNodes)
					{
						if(texNode.selectSingleNode("@unit").getText().equals("0"))
						{
							tex0 = texNode.getText();
							mipmap = rotate = texNode.selectSingleNode("@mipmap").getText();
							rotate = texNode.selectSingleNode("@rotate").getText();
							flip = texNode.selectSingleNode("@flip").getText();
						}
					}

					// get position, rotation and scale data
					String x = playerNode.selectSingleNode("position/@x").getText();
					String y = playerNode.selectSingleNode("position/@y").getText();
					String z = playerNode.selectSingleNode("position/@z").getText();

					String rotX = playerNode.selectSingleNode("rotation/@x").getText();
					String rotY = playerNode.selectSingleNode("rotation/@y").getText();
					String rotZ = playerNode.selectSingleNode("rotation/@z").getText();

					String scaleX = playerNode.selectSingleNode("scale/@x").getText();
					String scaleY = playerNode.selectSingleNode("scale/@y").getText();
					String scaleZ = playerNode.selectSingleNode("scale/@z").getText();

					// create the model object with the read in data
					Model3DS model = new Model3DS(Boolean.parseBoolean(smooth));
					model.load(file, 1f);
					model.setTexUnit0(
							tex0,
							Boolean.parseBoolean(mipmap),
							Boolean.parseBoolean(rotate),
							Boolean.parseBoolean(flip));
					model.setName(internalId);
					model.setPosition(
							Float.parseFloat(x),
							Float.parseFloat(y),
							Float.parseFloat(z));
					model.setRotation(
							Float.parseFloat(rotX),
							Float.parseFloat(rotY),
							Float.parseFloat(rotZ));
					model.scale(
							Float.parseFloat(scaleX),
							Float.parseFloat(scaleY),
							Float.parseFloat(scaleZ));
					model.setShadowEnabled(Boolean.parseBoolean(shadow));

					room = playerNode.selectSingleNode("room").getText();
					String name = playerNode.selectSingleNode("name").getText();
					String scriptId = playerNode.selectSingleNode("@scriptid").getText();
					String inventoryType = playerNode.selectSingleNode("inventory/@type").getText();

					AbstractInventory inv = null;
					if(inventoryType.equals("bar"))
					{
						inv = new BarInventory(100,10,new Color4f(0f,1f,0f,1f),input);
						inv.setBGColor(new Color4f(0f,0f,0f,0.65f));
					}

					PlayableCharacter player = new PlayableCharacter(model,inv);
					player.setScreenName(name);
					player.setScriptId(scriptId);

					chapter.setActiveRoom(room);
					chapter.getActiveRoom().setPlayer(player);

					// register player to the triggers of the first room, other registerings will
					// be done during room change
					for(AbstractTrigger trigger : chapter.getActiveRoom().getTriggers())
						trigger.addCandidate(player);
				}
			}

			// finally add the conversation and init scripting

			// add conversations to the chapter
			if(chapterNode.selectSingleNode("conversations") != null)
			{
				String conFilePath = chapterNode.selectSingleNode("conversations/@file").getText();
				chapter.addConversations(
						new ConversationReader().readConversations(conFilePath, chapter, input));
			}

			// initialize scripting for the chapter
			ShootOut3DMain.getInstance().setLoadingInfoText("Initialize scripting");
			chapter.initScripting();

			// set the active room again, because the roomOnEnter()
			// method will be called with scripting functional now.
			chapter.setActiveRoom(room);

			ShootOut3DMain.getInstance().setLoadingInfoText("Loading done!");

			return chapter;
		}

		return null;
	}
}
