package loon.action.scripting;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Set;
import java.util.Stack;

import loon.action.scripting.pack.PackAnimation;
import loon.action.scripting.pack.PackFrame;
import loon.action.scripting.pack.PackTileFactory;
import loon.action.scripting.pack.PackTileMap;
import loon.core.LRelease;
import loon.core.graphics.Screen;
import loon.core.graphics.opengl.LTexturePack;
import loon.core.graphics.opengl.LTexturePack.PackEntry;
import loon.core.resource.Resources;
import loon.utils.StringUtils;
import loon.utils.xml.XMLElement;
import loon.utils.xml.XMLParser;


/**
 * Copyright 2008 - 2011
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 * 
 * @project loonframework
 * @author chenpeng
 * @email：ceponline@yahoo.com.cn
 * @version 0.1
 */
public class ScriptFactory implements LRelease {

	private PackTileFactory tileFactory;

	private HashMap<String, Script> scripts;

	private HashMap<String, LTexturePack> packs;

	private HashMap<String, PackTileMap> maps;

	private HashMap<String, PackAnimation> animations;

	private ArrayList<Stack<StackFrame>> executionStacks;

	private Callback callback;

	String packName;

	String mapName;

	int packCount;

	private InputStream inputStream;

	private Screen screen;

	public ScriptFactory(InputStream in) {
		this(null, in);
	}

	public ScriptFactory(String res) {
		this(null, res);
	}

	public ScriptFactory(Screen screen, String res) {
		this.screen = screen;
		try {
			this.inputStream = Resources.openResource(res);
		} catch (IOException e) {
			e.printStackTrace();
		}
		this.init();
	}

	public ScriptFactory(Screen screen, InputStream in) {
		this.screen = screen;
		this.inputStream = in;
		this.init();
	}

	private void init() {
		this.scripts = new HashMap<String, Script>(10);
		this.packs = new HashMap<String, LTexturePack>(10);
		this.maps = new HashMap<String, PackTileMap>(10);
		this.animations = new HashMap<String, PackAnimation>(10);
		this.executionStacks = new ArrayList<Stack<StackFrame>>(10);
	}

	public void setMapName(String name) {
		this.mapName = name;
	}

	public String getMapName() {
		return mapName;
	}

	public void setPackName(String name) {
		this.packName = name;
	}

	public String getPackName() {
		return packName;
	}

	public void load() {
		if (inputStream == null) {
			return;
		}
		XMLElement root = XMLParser.parse(inputStream).getRoot();
		for (Iterator<?> itr = root.elements(); itr.hasNext();) {
			Object o = itr.next();
			if (o instanceof XMLElement) {
				XMLElement ele = (XMLElement) o;
				if ("packs".equalsIgnoreCase(ele.getName())) {
					for (Iterator<?> it = ele.elements("pack"); it.hasNext();) {
						LTexturePack pack = new LTexturePack((XMLElement) it
								.next());
						packs.put(pack.getName(), pack);
						if (packName == null) {
							packName = pack.getName();
						}
					}
				} else if ("scripts".equalsIgnoreCase(ele.getName())) {
					for (Iterator<?> it = ele.elements("script"); it.hasNext();) {
						XMLElement child = (XMLElement) it.next();
						String name = child.getAttribute("name").getValue();
						Script script = new Script(name);
						for (Iterator<?> it1 = child.elements(); it1.hasNext();) {
							Object obj = it1.next();
							if (obj instanceof XMLElement) {
								XMLElement child3 = (XMLElement) obj;
								if (child3.getName().equals("function")) {
									if (child3.hasAttribute("x")) {
										script.add(new WithPosition(child3
												.getAttribute("name")
												.getValue(), child3
												.getAttribute("x")
												.getIntValue(), child3
												.getAttribute("y")
												.getIntValue()));
									} else {
										if (child3.hasAttribute("text")) {
											script.add(new WithText(child3
													.getAttribute("name")
													.getValue(), child3
													.getAttribute("text")
													.getValue()));
										} else {
											script.add(new UserFunction(child3
													.getAttribute("name")
													.getValue()));
										}
									}
								}
								if (child3.getName().equals("wait")) {
									script
											.add(new WaitFunction(child3
													.getAttribute("time")
													.getIntValue()));
								}
								if (child3.getName().equals("jump")) {
									script
											.add(new JumpFunction(child3
													.getAttribute("script")
													.getValue()));
								}
								if (child3.getName().equals("execute")) {
									script.add(new Execute(child3.getAttribute(
											"script").getValue()));
								}
								if (child3.getName().equals("call")) {
									script
											.add(new CallFunction(child3
													.getAttribute("script")
													.getValue()));
								}
							}
						}
						scripts.put(name, script);
					}
				} else if ("animations".equalsIgnoreCase(ele.getName())) {
					String packName = ele.getAttribute("pack", null);
					for (Iterator<?> it = ele.elements("animation"); it.hasNext();) {
						XMLElement child = (XMLElement) it.next();
						String name = child.getAttribute("name", "");
						boolean looped = true;
						if (child.hasAttribute("noloop")) {
							looped = false;
						}
						LTexturePack pack = null;
						if (packName != null) {
							pack = packs.get(packName);
						} else {
							if (packName == null) {
								Set<String> set = packs.keySet();
								pack = packs.get(set.iterator().next());
							}
						}
						String context = child.getContents();
						String[] strings = StringUtils.split(context, ",");
						PackAnimation animation = new PackAnimation(name,
								looped);
						for (String res : strings) {
							PackEntry entry = pack.getEntry(res);
							if (entry == null) {
								try {
									entry = pack
											.getEntry(Integer.parseInt(res));
								} catch (Exception e) {
								}
							}
							if (entry != null) {
								animation.addFrame(new PackFrame(entry));
							}
						}
						animations.put(name, animation);
					}
				} else if ("maps".equalsIgnoreCase(ele.getName())) {
					for (Iterator<?> it = ele.elements("map"); it.hasNext();) {
						XMLElement child = (XMLElement) it.next();
						String name = child.getAttribute("name", String
								.valueOf(System.currentTimeMillis()));
						if (mapName == null) {
							mapName = name;
						}
						maps.put(name, new PackTileMap(tileFactory, child,
								screen));
					}
				}
			}
		}
		this.packCount = packs.size();
		if (inputStream != null) {
			try {
				inputStream.close();
				inputStream = null;
			} catch (Exception e) {
			}
		}
	}

	int update(Stack<StackFrame> stack, StackFrame frame, Callback callback) {
		if (frame.isComplete()) {
			return Script.COMPLETE_SCRIPT;
		}
		for (; frame.hasNext();) {
			Function command = frame.next();
			command.update(this, stack, callback);
		}
		if (frame.hasNext()) {
			return Script.UNCOMPLETE_FUNCTION;
		}
		return Script.COMPLETE_FUNCTION;
	}

	public void update(Stack<StackFrame> stack) {
		StackFrame frame = stack.peek();
		frame.newTick();
		switch (update(stack, frame, callback)) {
		case Script.COMPLETE_SCRIPT:
			stack.pop();
			update();
			break;
		case Script.COMPLETE_FUNCTION:
			break;
		case Script.UNCOMPLETE_FUNCTION:
			update();
			break;
		}
	}

	public void update() {
		for (int i = 0; i < executionStacks.size(); i++) {
			Stack<StackFrame> stack = executionStacks.get(i);
			if (stack.empty()) {
				executionStacks.remove(stack);
			} else {
				update(stack);
			}
		}
	}

	public int getPackCount() {
		return packCount;
	}

	public void setCallback(Callback callback) {
		this.callback = callback;
	}

	void jump(Stack<StackFrame> stack, String name) {
		stack.pop();
		execute(stack, name);
	}

	void execute(Stack<StackFrame> stack, String name) {
		Script script = scripts.get(name);
		stack.push(new StackFrame(script));
	}

	public void call(String name) {
		Stack<StackFrame> stack = new Stack<StackFrame>();
		executionStacks.add(stack);
		execute(stack, name);
	}

	public void clear() {
		executionStacks.clear();
	}

	public PackTileFactory getTileFactory() {
		return tileFactory;
	}

	public void setTileFactory(PackTileFactory tileFactory) {
		this.tileFactory = tileFactory;
	}

	public PackAnimation getAnimation(String name) {
		PackAnimation animation = animations.get(name);
		if (animation != null) {
			return animations.get(name);
		}
		PackEntry entry = packs.get(packName).getEntry(name);
		if (entry != null) {
			animation = new PackAnimation(name, false);
			animation.addFrame(new PackFrame(entry));
		} else {
			try {
				entry = packs.get(packName).getEntry(Integer.parseInt(name));
				if (entry != null) {
					animation = new PackAnimation(name, false);
					animation.addFrame(new PackFrame(entry));
				}
			} catch (Exception e) {
			}
		}
		if (animation != null) {
			animations.put(name, animation);
		}
		return animation;
	}

	public Screen getScreen() {
		return screen;
	}

	public LTexturePack getPack(String name) {
		return packs.get(name);
	}

	public Script getScript(String name) {
		return scripts.get(name);
	}

	public PackTileMap getMap(String name) {
		return getMap(name, null);
	}

	public PackTileMap getMap(String name, PackTileFactory factory) {
		PackTileMap map = maps.get(name);
		if (map != null && map.isDirty()) {
			map.init(factory);
			map.update();
		}
		return map;
	}

	@Override
	public void dispose() {
		if (packs != null) {
			for (LTexturePack pack : packs.values()) {
				if (pack != null) {
					pack.dispose();
					pack = null;
				}
			}
			packs.clear();
		}
		if (scripts != null) {
			scripts.clear();
			scripts = null;
		}
		if (packs != null) {
			packs.clear();
			packs = null;
		}
		if (maps != null) {
			maps.clear();
			maps = null;
		}
		if (animations != null) {
			animations.clear();
			animations = null;
		}
		if (executionStacks != null) {
			executionStacks.clear();
			executionStacks = null;
		}
		if (inputStream != null) {
			try {
				inputStream.close();
				inputStream = null;
			} catch (Exception e) {
			}
		}
	}

}
