package org.newdawn.slick.particles;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.util.ArrayList;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Result;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.newdawn.slick.Color;
import org.newdawn.slick.geom.Vector2f;
import org.newdawn.slick.particles.ConfigurableEmitter.ColorRecord;
import org.newdawn.slick.particles.ConfigurableEmitter.LinearInterpolator;
import org.newdawn.slick.particles.ConfigurableEmitter.RandomValue;
import org.newdawn.slick.particles.ConfigurableEmitter.SimpleValue;
import org.newdawn.slick.util.Log;
import org.newdawn.slick.util.ResourceLoader;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;

/**
 * Utility methods to (de)serialize ConfigureEmitters to and from XML
 * 
 * @author kevin
 */
public class ParticleIO {

	/**
	 * Load a set of configured emitters into a single system
	 * 
	 * @param ref
	 *            The reference to the XML file (file or classpath)
	 * @param mask
	 * @return A configured particle system
	 * @throws IOException
	 *             Indicates a failure to find, read or parse the XML file
	 */
	public static ParticleSystem loadConfiguredSystem(String ref, Color mask)
			throws IOException {
		return loadConfiguredSystem(ResourceLoader.getResourceAsStream(ref),
            null, null, mask);
	}
	
	/**
	 * Load a set of configured emitters into a single system
	 * 
	 * @param ref
	 *            The reference to the XML file (file or classpath)
	 * @return A configured particle system
	 * @throws IOException
	 *             Indicates a failure to find, read or parse the XML file
	 */
	public static ParticleSystem loadConfiguredSystem(String ref)
			throws IOException {
		return loadConfiguredSystem(ResourceLoader.getResourceAsStream(ref),
            null, null, null);
	}

	/**
	 * Load a set of configured emitters into a single system
	 * 
	 * @param ref
	 *            The XML file to read
	 * @return A configured particle system
	 * @throws IOException
	 *             Indicates a failure to find, read or parse the XML file
	 */
	public static ParticleSystem loadConfiguredSystem(File ref)
			throws IOException {
      return loadConfiguredSystem(new FileInputStream(ref), null, null, null);
	}

	/**
	 * Load a set of configured emitters into a single system
	 * 
	 * @param ref
	 *            The stream to read the XML from
	 * @param mask The mask used to make the particle image transparent
	 * @return A configured particle system
	 * @throws IOException
	 *             Indicates a failure to find, read or parse the XML file
	 */
	public static ParticleSystem loadConfiguredSystem(InputStream ref, Color mask)
			throws IOException {
      return loadConfiguredSystem(ref, null, null, mask);
	}
	
	/**
	 * Load a set of configured emitters into a single system
	 * 
	 * @param ref
	 *            The stream to read the XML from
	 * @return A configured particle system
	 * @throws IOException
	 *             Indicates a failure to find, read or parse the XML file
	 */
	public static ParticleSystem loadConfiguredSystem(InputStream ref)
			throws IOException {
      return loadConfiguredSystem(ref, null, null, null);
	}

	/**
	 * Load a set of configured emitters into a single system
	 * 
	 * @param ref
	 *            The reference to the XML file (file or classpath)
	 * @return A configured particle system
	 * @param factory
	 *            The factory used to create the emitter than will be poulated
	 *            with loaded data.
	 * @throws IOException
	 *             Indicates a failure to find, read or parse the XML file
	 */
	public static ParticleSystem loadConfiguredSystem(String ref,
			ConfigurableEmitterFactory factory) throws IOException {
		return loadConfiguredSystem(ResourceLoader.getResourceAsStream(ref),
            factory, null, null);
	}

	/**
	 * Load a set of configured emitters into a single system
	 * 
	 * @param ref
	 *            The XML file to read
	 * @return A configured particle system
	 * @param factory
	 *            The factory used to create the emitter than will be poulated
	 *            with loaded data.
	 * @throws IOException
	 *             Indicates a failure to find, read or parse the XML file
	 */
	public static ParticleSystem loadConfiguredSystem(File ref,
			ConfigurableEmitterFactory factory) throws IOException {
      return loadConfiguredSystem(new FileInputStream(ref), factory, null, null);
	}

	/**
	 * Load a set of configured emitters into a single system
	 * 
	 * @param ref
	 *            The stream to read the XML from
	 * @return A configured particle system
	 * @param factory
	 *            The factory used to create the emitter than will be poulated
	 *            with loaded data.
	 * @throws IOException
	 *             Indicates a failure to find, read or parse the XML file
	 */
	public static ParticleSystem loadConfiguredSystem(InputStream ref,
			ConfigurableEmitterFactory factory) throws IOException {
            return loadConfiguredSystem(ref, factory, null, null);
         }

   /**
    * Load a set of configured emitters into a single system
    *
    * @param ref
    *            The stream to read the XML from
    * @param factory
    *            The factory used to create the emitter than will be poulated
    *            with loaded data.
    * @param system The particle system that will be loaded into
    * @param mask The mask used to make the image background transparent
    * @return A configured particle system
    * @throws IOException
    *             Indicates a failure to find, read or parse the XML file
    */
   public static ParticleSystem loadConfiguredSystem(InputStream ref,
         ConfigurableEmitterFactory factory, ParticleSystem system, Color mask) throws IOException {
		if (factory == null) {
			factory = new ConfigurableEmitterFactory() {
				public ConfigurableEmitter createEmitter(String name) {
					return new ConfigurableEmitter(name);
				}
			};
		}
		try {
			DocumentBuilder builder = DocumentBuilderFactory.newInstance()
					.newDocumentBuilder();
			Document document = builder.parse(ref);

			Element element = document.getDocumentElement();
			if (!element.getNodeName().equals("system")) {
				throw new IOException("Not a particle system file");
			}
			
         if (system == null) {
         system = new ParticleSystem("org/newdawn/slick/data/particle.tga",
					2000, mask);
         }
			boolean additive = "true".equals(element.getAttribute("additive"));
			if (additive) {
				system.setBlendingMode(ParticleSystem.BLEND_ADDITIVE);
			} else {
				system.setBlendingMode(ParticleSystem.BLEND_COMBINE);
			}
			boolean points = "true".equals(element.getAttribute("points"));
			system.setUsePoints(points);

			NodeList list = element.getElementsByTagName("emitter");
			for (int i = 0; i < list.getLength(); i++) {
				Element em = (Element) list.item(i);
				ConfigurableEmitter emitter = factory.createEmitter("new");
				elementToEmitter(em, emitter);

				system.addEmitter(emitter);
			}

			system.setRemoveCompletedEmitters(false);
			return system;
		} catch (IOException e) {
			Log.error(e);
			throw e;
		} catch (Exception e) {
			Log.error(e);
			throw new IOException("Unable to load particle system config");
		}
	}

	/**
	 * Save a particle system with only ConfigurableEmitters in to an XML file
	 * 
	 * @param file
	 *            The file to save to
	 * @param system
	 *            The system to store
	 * @throws IOException
	 *             Indicates a failure to save or encode the system XML.
	 */
	public static void saveConfiguredSystem(File file, ParticleSystem system)
			throws IOException {
		saveConfiguredSystem(new FileOutputStream(file), system);
	}

	/**
	 * Save a particle system with only ConfigurableEmitters in to an XML file
	 * 
	 * @param out
	 *            The location to which we'll save
	 * @param system
	 *            The system to store
	 * @throws IOException
	 *             Indicates a failure to save or encode the system XML.
	 */
	public static void saveConfiguredSystem(OutputStream out,
			ParticleSystem system) throws IOException {
		try {
			DocumentBuilder builder = DocumentBuilderFactory.newInstance()
					.newDocumentBuilder();
			Document document = builder.newDocument();

			Element root = document.createElement("system");
			root
					.setAttribute(
							"additive",
							""
									+ (system.getBlendingMode() == ParticleSystem.BLEND_ADDITIVE));
			root.setAttribute("points", "" + (system.usePoints()));

			document.appendChild(root);
			for (int i = 0; i < system.getEmitterCount(); i++) {
				ParticleEmitter current = system.getEmitter(i);
				if (current instanceof ConfigurableEmitter) {
					Element element = emitterToElement(document,
							(ConfigurableEmitter) current);
					root.appendChild(element);
				} else {
					throw new RuntimeException(
							"Only ConfigurableEmitter instances can be stored");
				}
			}

			Result result = new StreamResult(new OutputStreamWriter(out,
					"utf-8"));
			DOMSource source = new DOMSource(document);

			TransformerFactory factory = TransformerFactory.newInstance();
			Transformer xformer = factory.newTransformer();
			xformer.setOutputProperty(OutputKeys.INDENT, "yes");

			xformer.transform(source, result);
		} catch (Exception e) {
			Log.error(e);
			throw new IOException("Unable to save configured particle system");
		}
	}

	/**
	 * Load a single emitter from an XML file
	 * 
	 * @param ref
	 *            The reference to the emitter XML file to load (classpath or
	 *            file)
	 * @return The configured emitter
	 * @throws IOException
	 *             Indicates a failure to find, read or parse the XML file
	 */
	public static ConfigurableEmitter loadEmitter(String ref)
			throws IOException {
		return loadEmitter(ResourceLoader.getResourceAsStream(ref), null);
	}

	/**
	 * Load a single emitter from an XML file
	 * 
	 * @param ref
	 *            The XML file to read
	 * @return The configured emitter
	 * @throws IOException
	 *             Indicates a failure to find, read or parse the XML file
	 */
	public static ConfigurableEmitter loadEmitter(File ref) throws IOException {
		return loadEmitter(new FileInputStream(ref), null);

	}

	/**
	 * Load a single emitter from an XML file
	 * 
	 * @param ref
	 *            The stream to read the XML from
	 * @return The configured emitter
	 * @throws IOException
	 *             Indicates a failure to find, read or parse the XML file
	 */
	public static ConfigurableEmitter loadEmitter(InputStream ref)
			throws IOException {
		return loadEmitter(ref, null);
	}

	/**
	 * Load a single emitter from an XML file
	 * 
	 * @param ref
	 *            The reference to the emitter XML file to load (classpath or
	 *            file)
	 * @return The configured emitter
	 * @param factory
	 *            The factory used to create the emitter than will be poulated
	 *            with loaded data.
	 * @throws IOException
	 *             Indicates a failure to find, read or parse the XML file
	 */
	public static ConfigurableEmitter loadEmitter(String ref,
			ConfigurableEmitterFactory factory) throws IOException {
		return loadEmitter(ResourceLoader.getResourceAsStream(ref), factory);
	}

	/**
	 * Load a single emitter from an XML file
	 * 
	 * @param ref
	 *            The XML file to read
	 * @return The configured emitter
	 * @param factory
	 *            The factory used to create the emitter than will be poulated
	 *            with loaded data.
	 * @throws IOException
	 *             Indicates a failure to find, read or parse the XML file
	 */
	public static ConfigurableEmitter loadEmitter(File ref,
			ConfigurableEmitterFactory factory) throws IOException {
		return loadEmitter(new FileInputStream(ref), factory);

	}

	/**
	 * Load a single emitter from an XML file
	 * 
	 * @param ref
	 *            The stream to read the XML from
	 * @param factory
	 *            The factory used to create the emitter than will be poulated
	 *            with loaded data.
	 * @return The configured emitter
	 * @throws IOException
	 *             Indicates a failure to find, read or parse the XML file
	 */
	public static ConfigurableEmitter loadEmitter(InputStream ref,
			ConfigurableEmitterFactory factory) throws IOException {
		if (factory == null) {
			factory = new ConfigurableEmitterFactory() {
				public ConfigurableEmitter createEmitter(String name) {
					return new ConfigurableEmitter(name);
				}
			};
		}
		try {
			DocumentBuilder builder = DocumentBuilderFactory.newInstance()
					.newDocumentBuilder();
			Document document = builder.parse(ref);

			if (!document.getDocumentElement().getNodeName().equals("emitter")) {
				throw new IOException("Not a particle emitter file");
			}

			ConfigurableEmitter emitter = factory.createEmitter("new");
			elementToEmitter(document.getDocumentElement(), emitter);

			return emitter;
		} catch (IOException e) {
			Log.error(e);
			throw e;
		} catch (Exception e) {
			Log.error(e);
			throw new IOException("Unable to load emitter");
		}
	}

	/**
	 * Save a single emitter to the XML file
	 * 
	 * @param file
	 *            The file to save the emitter to
	 * @param emitter
	 *            The emitter to store to the XML file
	 * @throws IOException
	 *             Indicates a failure to write or encode the XML
	 */
	public static void saveEmitter(File file, ConfigurableEmitter emitter)
			throws IOException {
		saveEmitter(new FileOutputStream(file), emitter);
	}

	/**
	 * Save a single emitter to the XML file
	 * 
	 * @param out
	 *            The location to which we should save
	 * @param emitter
	 *            The emitter to store to the XML file
	 * @throws IOException
	 *             Indicates a failure to write or encode the XML
	 */
	public static void saveEmitter(OutputStream out, ConfigurableEmitter emitter)
			throws IOException {
		try {
			DocumentBuilder builder = DocumentBuilderFactory.newInstance()
					.newDocumentBuilder();
			Document document = builder.newDocument();

			document.appendChild(emitterToElement(document, emitter));
			Result result = new StreamResult(new OutputStreamWriter(out,
					"utf-8"));
			DOMSource source = new DOMSource(document);

			TransformerFactory factory = TransformerFactory.newInstance();
			Transformer xformer = factory.newTransformer();
			xformer.setOutputProperty(OutputKeys.INDENT, "yes");

			xformer.transform(source, result);
		} catch (Exception e) {
			Log.error(e);
			throw new IOException("Failed to save emitter");
		}
	}

	/**
	 * Get the first child named as specified from the passed XML element
	 * 
	 * @param element
	 *            The element whose children are interogated
	 * @param name
	 *            The name of the element to retrieve
	 * @return The requested element
	 */
	private static Element getFirstNamedElement(Element element, String name) {
		NodeList list = element.getElementsByTagName(name);
		if (list.getLength() == 0) {
			return null;
		}

		return (Element) list.item(0);
	}

	/**
	 * Convert from an XML element to an configured emitter
	 * 
	 * @param element
	 *            The XML element to convert
	 * @param emitter
	 *            The emitter that will be configured based on the XML
	 */
	private static void elementToEmitter(Element element,
			ConfigurableEmitter emitter) {
		emitter.name = element.getAttribute("name");
		emitter.setImageName(element.getAttribute("imageName"));

		String renderType = element.getAttribute("renderType");
		emitter.usePoints = Particle.INHERIT_POINTS;
		if (renderType.equals("quads")) {
			emitter.usePoints = Particle.USE_QUADS;
		}
		if (renderType.equals("points")) {
			emitter.usePoints = Particle.USE_POINTS;
		}

		String useOriented = element.getAttribute("useOriented");
		if (useOriented != null)
			emitter.useOriented = "true".equals(useOriented);

		String useAdditive = element.getAttribute("useAdditive");
		if (useAdditive != null)
			emitter.useAdditive = "true".equals(useAdditive);

		parseRangeElement(getFirstNamedElement(element, "spawnInterval"),
				emitter.spawnInterval);
		parseRangeElement(getFirstNamedElement(element, "spawnCount"),
				emitter.spawnCount);
		parseRangeElement(getFirstNamedElement(element, "initialLife"),
				emitter.initialLife);
		parseRangeElement(getFirstNamedElement(element, "initialSize"),
				emitter.initialSize);
		parseRangeElement(getFirstNamedElement(element, "xOffset"),
				emitter.xOffset);
		parseRangeElement(getFirstNamedElement(element, "yOffset"),
				emitter.yOffset);
		parseRangeElement(getFirstNamedElement(element, "initialDistance"),
				emitter.initialDistance);
		parseRangeElement(getFirstNamedElement(element, "speed"), emitter.speed);
		parseRangeElement(getFirstNamedElement(element, "length"),
				emitter.length);
		parseRangeElement(getFirstNamedElement(element, "emitCount"),
				emitter.emitCount);

		parseValueElement(getFirstNamedElement(element, "spread"),
				emitter.spread);
		parseValueElement(getFirstNamedElement(element, "angularOffset"),
				emitter.angularOffset);
		parseValueElement(getFirstNamedElement(element, "growthFactor"),
				emitter.growthFactor);
		parseValueElement(getFirstNamedElement(element, "gravityFactor"),
				emitter.gravityFactor);
		parseValueElement(getFirstNamedElement(element, "windFactor"),
				emitter.windFactor);
		parseValueElement(getFirstNamedElement(element, "startAlpha"),
				emitter.startAlpha);
		parseValueElement(getFirstNamedElement(element, "endAlpha"),
				emitter.endAlpha);
		parseValueElement(getFirstNamedElement(element, "alpha"), emitter.alpha);
		parseValueElement(getFirstNamedElement(element, "size"), emitter.size);
		parseValueElement(getFirstNamedElement(element, "velocity"),
				emitter.velocity);
		parseValueElement(getFirstNamedElement(element, "scaleY"),
				emitter.scaleY);

		Element color = getFirstNamedElement(element, "color");
		NodeList steps = color.getElementsByTagName("step");
		emitter.colors.clear();
		for (int i = 0; i < steps.getLength(); i++) {
			Element step = (Element) steps.item(i);
			float offset = Float.parseFloat(step.getAttribute("offset"));
			float r = Float.parseFloat(step.getAttribute("r"));
			float g = Float.parseFloat(step.getAttribute("g"));
			float b = Float.parseFloat(step.getAttribute("b"));

			emitter.addColorPoint(offset, new Color(r, g, b, 1));
		}

		// generate new random play length
		emitter.replay();
	}

	/**
	 * Convert from an emitter to a XML element description
	 * 
	 * @param document
	 *            The document the element will be part of
	 * @param emitter
	 *            The emitter to convert
	 * @return The XML element based on the configured emitter
	 */
	private static Element emitterToElement(Document document,
			ConfigurableEmitter emitter) {
		Element root = document.createElement("emitter");
		root.setAttribute("name", emitter.name);
		root.setAttribute("imageName", emitter.imageName == null ? ""
				: emitter.imageName);
		root
				.setAttribute("useOriented", emitter.useOriented ? "true"
						: "false");
		root
				.setAttribute("useAdditive", emitter.useAdditive ? "true"
						: "false");

		if (emitter.usePoints == Particle.INHERIT_POINTS) {
			root.setAttribute("renderType", "inherit");
		}
		if (emitter.usePoints == Particle.USE_POINTS) {
			root.setAttribute("renderType", "points");
		}
		if (emitter.usePoints == Particle.USE_QUADS) {
			root.setAttribute("renderType", "quads");
		}

		root.appendChild(createRangeElement(document, "spawnInterval",
				emitter.spawnInterval));
		root.appendChild(createRangeElement(document, "spawnCount",
				emitter.spawnCount));
		root.appendChild(createRangeElement(document, "initialLife",
				emitter.initialLife));
		root.appendChild(createRangeElement(document, "initialSize",
				emitter.initialSize));
		root.appendChild(createRangeElement(document, "xOffset",
				emitter.xOffset));
		root.appendChild(createRangeElement(document, "yOffset",
				emitter.yOffset));
		root.appendChild(createRangeElement(document, "initialDistance",
				emitter.initialDistance));
		root.appendChild(createRangeElement(document, "speed", emitter.speed));
		root
				.appendChild(createRangeElement(document, "length",
						emitter.length));
		root.appendChild(createRangeElement(document, "emitCount",
				emitter.emitCount));

		root
				.appendChild(createValueElement(document, "spread",
						emitter.spread));
		root.appendChild(createValueElement(document, "angularOffset",
				emitter.angularOffset));
		root.appendChild(createValueElement(document, "growthFactor",
				emitter.growthFactor));
		root.appendChild(createValueElement(document, "gravityFactor",
				emitter.gravityFactor));
		root.appendChild(createValueElement(document, "windFactor",
				emitter.windFactor));
		root.appendChild(createValueElement(document, "startAlpha",
				emitter.startAlpha));
		root.appendChild(createValueElement(document, "endAlpha",
				emitter.endAlpha));
		root.appendChild(createValueElement(document, "alpha", emitter.alpha));
		root.appendChild(createValueElement(document, "size", emitter.size));
		root.appendChild(createValueElement(document, "velocity",
				emitter.velocity));
		root
				.appendChild(createValueElement(document, "scaleY",
						emitter.scaleY));

		Element color = document.createElement("color");
		ArrayList list = emitter.colors;
		for (int i = 0; i < list.size(); i++) {
			ColorRecord record = (ColorRecord) list.get(i);
			Element step = document.createElement("step");
			step.setAttribute("offset", "" + record.pos);
			step.setAttribute("r", "" + record.col.r);
			step.setAttribute("g", "" + record.col.g);
			step.setAttribute("b", "" + record.col.b);

			color.appendChild(step);
		}

		root.appendChild(color);

		return root;
	}

	/**
	 * Create an XML element based on a configured range
	 * 
	 * @param document
	 *            The document the element will be part of
	 * @param name
	 *            The name to give the new element
	 * @param range
	 *            The configured range
	 * @return A configured XML element on the range
	 */
	private static Element createRangeElement(Document document, String name,
			ConfigurableEmitter.Range range) {
		Element element = document.createElement(name);
		element.setAttribute("min", "" + range.getMin());
		element.setAttribute("max", "" + range.getMax());
		element.setAttribute("enabled", "" + range.isEnabled());

		return element;
	}

	/**
	 * Create an XML element based on a configured value
	 * 
	 * @param document
	 *            The document the element will be part of
	 * @param name
	 *            The name to give the new element
	 * @param value
	 *            The configured value
	 * @return A configure XML element based on the value
	 */
	private static Element createValueElement(Document document, String name,
			ConfigurableEmitter.Value value) {
		Element element = document.createElement(name);

		// void: now writes the value type
		if (value instanceof SimpleValue) {
			element.setAttribute("type", "simple");
			element.setAttribute("value", "" + value.getValue(0));
		} else if (value instanceof RandomValue) {
			element.setAttribute("type", "random");
			element
					.setAttribute("value", ""
							+ ((RandomValue) value).getValue());
		} else if (value instanceof LinearInterpolator) {
			element.setAttribute("type", "linear");
			element.setAttribute("min", ""
					+ ((LinearInterpolator) value).getMin());
			element.setAttribute("max", ""
					+ ((LinearInterpolator) value).getMax());
			element.setAttribute("active", ""
					+ ((LinearInterpolator) value).isActive());

			ArrayList curve = ((LinearInterpolator) value).getCurve();
			for (int i = 0; i < curve.size(); i++) {
				Vector2f point = (Vector2f) curve.get(i);

				Element pointElement = document.createElement("point");
				pointElement.setAttribute("x", "" + point.x);
				pointElement.setAttribute("y", "" + point.y);

				element.appendChild(pointElement);
			}
		} else {
			Log.warn("unkown value type ignored: " + value.getClass());
		}

		return element;
	}

	/**
	 * Parse an XML element into a configured range
	 * 
	 * @param element
	 *            The XML element to parse
	 * @param range
	 *            The range to configure based on the XML
	 */
	private static void parseRangeElement(Element element,
			ConfigurableEmitter.Range range) {
		if (element == null) {
			return;
		}
		range.setMin(Float.parseFloat(element.getAttribute("min")));
		range.setMax(Float.parseFloat(element.getAttribute("max")));
		range.setEnabled("true".equals(element.getAttribute("enabled")));
	}

	/**
	 * Parse an XML element into a configured value
	 * 
	 * @param element
	 *            The XML element to parse
	 * @param value
	 *            The value to configure based on the XML
	 */
	private static void parseValueElement(Element element,
			ConfigurableEmitter.Value value) {
		if (element == null) {
			return;
		}

		String type = element.getAttribute("type");
		String v = element.getAttribute("value");

		if (type == null || type.length() == 0) {
			// support for old style which did not write the type
			if (value instanceof SimpleValue) {
				((SimpleValue) value).setValue(Float.parseFloat(v));
			} else if (value instanceof RandomValue) {
				((RandomValue) value).setValue(Float.parseFloat(v));
			} else {
				Log.warn("problems reading element, skipping: " + element);
			}
		} else {
			// type given: this is the new style
			if (type.equals("simple")) {
				((SimpleValue) value).setValue(Float.parseFloat(v));
			} else if (type.equals("random")) {
				((RandomValue) value).setValue(Float.parseFloat(v));
			} else if (type.equals("linear")) {
				String min = element.getAttribute("min");
				String max = element.getAttribute("max");
				String active = element.getAttribute("active");

				NodeList points = element.getElementsByTagName("point");

				ArrayList curve = new ArrayList();
				for (int i = 0; i < points.getLength(); i++) {
					Element point = (Element) points.item(i);

					float x = Float.parseFloat(point.getAttribute("x"));
					float y = Float.parseFloat(point.getAttribute("y"));

					curve.add(new Vector2f(x, y));
				}

				((LinearInterpolator) value).setCurve(curve);
				((LinearInterpolator) value).setMin(Integer.parseInt(min));
				((LinearInterpolator) value).setMax(Integer.parseInt(max));
				((LinearInterpolator) value).setActive("true".equals(active));
			} else {
				Log.warn("unkown type detected: " + type);
			}
		}
	}
}