package data;

import java.awt.Color;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import logging.Log;
import logging.LogLevel;

import org.w3c.dom.Document;
import org.w3c.dom.Element;

import tools.file.FileTool;
import tools.xml.XMLReader;

/**
 * 
 * This class stores settings as static attributes
 * 
 * @author Martin Pabst, 2009
 * 
 */
public class Settings {

	/**
	 * List of DateChangeJob objects
	 */
	private static DateChangeJobList dateChangeJobs = new DateChangeJobList();

	/**
	 * List of SQL-scripts
	 */
	private static ArrayList<Script> scripts = new ArrayList<Script>();

	/**
	 * Root to fitnesse wiki
	 */
	private static String fitnesseRoot = "";

	/**
	 * Schoolyear to which fitnesse-tests should get beamed
	 */
	private static int fitnesseDestinationSchoolyear = 2009;

	private static int schuljahrFuerDateienOhneMarker;

	private static String fitnesseProtocoll = null;

	private static boolean fitnesseWriteChanges = false;

	public static String getFitnesseProtocoll() {
		return fitnesseProtocoll;
	}

	/**
	 * read settings from file data/einstellungen.xml
	 */
	public static void readSettings() {
		/**
		 * XML DOM
		 */
		Document doc;

		try {

			/**
			 * read settings data form file data/einstellungen.xml
			 */
			doc = XMLReader.readFile("jobs.xml");
			Element root = XMLReader.root(doc);

			Log.outlColor("Einstellungen laden:", LogLevel.info, Color.blue);
			Log.outl("", LogLevel.info);

			if (root.getNodeName().compareTo("settings") != 0) {
				throw new Exception(
						"Element \"settings\" in der Datei einstellungen.xml erwartet.");
			}

			Element settings = root;

			Element xmlFitnesseRoot = XMLReader.element(settings,
					"fitnesseroot");
			fitnesseRoot = XMLReader.attributeNotNull(xmlFitnesseRoot, "path");
			Log.out("\nFitnesseroot: " + fitnesseRoot, LogLevel.info);

			String sFitnesseDestSchoolyear = XMLReader.attribute(
					xmlFitnesseRoot, "neuesbasisschuljahr");

			fitnesseDestinationSchoolyear = 2009;

			if (sFitnesseDestSchoolyear != null) {

				fitnesseDestinationSchoolyear = Integer
						.parseInt(sFitnesseDestSchoolyear.substring(0, 4));
			}

			Log.outl("\nNeues Basisschuljahr: "
					+ intToSchuljahrString(fitnesseDestinationSchoolyear),
					LogLevel.info);

			String sSchuljahrfuerdateienohnemarker = XMLReader.attribute(
					xmlFitnesseRoot, "schuljahrfuerdateienohnemarker");

			schuljahrFuerDateienOhneMarker = 2008;

			if (sSchuljahrfuerdateienohnemarker != null) {

				schuljahrFuerDateienOhneMarker = Integer
						.parseInt(sSchuljahrfuerdateienohnemarker.substring(0,
								4));
			}

			Log.outl("\nFür Dateien ohne Marker wird als Basisschuljahr "
					+ intToSchuljahrString(schuljahrFuerDateienOhneMarker)
					+ " angenommen.", LogLevel.info);

			String sFitnesseProtocoll = XMLReader.attribute(xmlFitnesseRoot,
					"protocoll");

			if (sFitnesseProtocoll != null) {

				fitnesseProtocoll = sFitnesseProtocoll;
			}

			Log.outl("\nProtokoll für Fitnesse-Änderungen: "
					+ sFitnesseProtocoll, LogLevel.info);

			String sFitnesseWriteChanges = XMLReader.attribute(xmlFitnesseRoot,
					"writechanges");

			if (sFitnesseWriteChanges != null) {

				fitnesseWriteChanges = (sFitnesseWriteChanges
						.compareToIgnoreCase("true") == 0);
			}

			Log.outl("\nAenderungen in originale Fitnesse-Scripte schreiben: "
					+ sFitnesseWriteChanges, LogLevel.info);

			List<Element> xmlDateChangeJobs = XMLReader.elements(settings,
					"dateChangeJob");
			dateChangeJobs.clear();
			for (Element element : xmlDateChangeJobs) {
				DateChangeJob dcj = new DateChangeJob(
						XMLReader.attributeNotNull(element, "entity"),
						XMLReader.attributeNotNull(element, "attribute"),
						XMLReader.attributeNotNull(element, "delta"), XMLReader
								.attributeNotNull(element,
										"onlycorrectcalendar")
								.compareToIgnoreCase("true") == 0);
				dateChangeJobs.add(dcj);
				Log.outlColor("DateChangeJob: ", LogLevel.info, Color.blue);
				Log.outl(dcj.toString(), LogLevel.info);
			}

			Log.outl("", LogLevel.info);

			List<Element> xmlScripts = XMLReader.elements(settings, "script");

			scripts.clear();

			for (Element element2 : xmlScripts) {
				String statements = getText(element2);
				String name = XMLReader.attributeNotNull(element2, "name");
				Script script = new Script(statements, name);
				scripts.add(script);
				Log.outlColor("Scripts: ", LogLevel.info, Color.blue);
				Log.outl(script.toString(), LogLevel.info);
				Log.outl("", LogLevel.info);
			}

			List<Element> xmlNewDateChangeJobs = XMLReader.elements(settings,
					"newDateChangeJob");
			for (Element element3 : xmlNewDateChangeJobs) {
				String delta = XMLReader.attributeNotNull(element3, "delta");
				String text = getText(element3);
				boolean onlyCorrectCalendar = XMLReader
						.elementBooleanAttribute(element3,
								"onlycorrectcalendar");

				parseAttributes(text, delta, onlyCorrectCalendar);

			}

		} catch (Exception e) {
			Log.outl(e.toString() + e.getStackTrace().toString(),
					LogLevel.error);
		}

	}

	public static boolean isFitnesseWriteChanges() {
		return fitnesseWriteChanges;
	}

	public static String intToSchuljahrString(int sj) {
		int sjp1 = (sj + 1) % 100;
		return sj + "/" + (sjp1 < 10 ? "0" : "") + sjp1;
	}

	public static int getSchuljahrFuerDateienOhneMarker() {
		return schuljahrFuerDateienOhneMarker;
	}

	private static String getText(Element element) {
		String filename = XMLReader.attributeNotNull(element, "filename");

		String text = "";

		if (!filename.isEmpty() && filename.length() > 2) {
			try {
				File file = new File(filename);
				text = FileTool.readTextFile(file, "UTF-8");
			} catch (IOException ex) {
				Log.outl("Fehler: Kann Datei " + filename + " nicht lesen. "
						+ ex.toString(), LogLevel.error);
			}
		} else {
			text = XMLReader.text(element);
		}

		return text;

	}

	/**
	 * 
	 * Method {@link parseAttributes} parses blank-separated list of attributes.
	 * State is a helper-enum which holds parsing-states.
	 * 
	 * Example
	 * 
	 * entity1.attribute1 entity2.attribute2 ... entityn.attributen
	 * 
	 * 
	 * @author Martin Pabst, 2009
	 * 
	 */
	enum State {
		/**
		 * parsing entity-identifier
		 */
		PARSEENTITY,

		/**
		 * parsing attribute-identifier
		 */
		PARSEATTRIBUTE,

		/**
		 * parsing blanks between two attributes
		 */
		PARSEBLANK
	};

	/**
	 * parse blank (that means: cr, space, comma)-separated list of attributes
	 * and store them in dateChangeJobs
	 * 
	 * @param text
	 *            String-variable which holds list
	 * @param delta
	 *            number of years to add to attributes given as String, e.g.
	 *            "1", "2", "-2"
	 */
	private static void parseAttributes(String text, String delta,
			boolean onlyCorrectCalendar) {
		/**
		 * start with this state
		 */
		State state = State.PARSEBLANK;

		/**
		 * position of currently parsed character inside text to parse
		 */
		int i = 0;

		/**
		 * currently parsed character
		 */
		char c = ' ';

		/**
		 * entity-identifier of currently parsed attribute (attribute means:
		 * identifier.attribute
		 */
		String entity = "";

		/**
		 * attribute-identifier of currently parsed attribute
		 */
		String attribute = "";

		while (i < text.length()) {

			c = text.charAt(i);

			/*
			 * simple state machine...
			 */
			switch (state) {
			case PARSEATTRIBUTE:
				if (!isBlank(c)) {
					attribute += c;
				} else {
					if (!attribute.isEmpty() && !entity.isEmpty()) {
						DateChangeJob dcj = new DateChangeJob(entity,
								attribute, delta, onlyCorrectCalendar);
						dateChangeJobs.add(dcj);
						Log.outlColor("DateChangeJob: ", LogLevel.info,
								Color.blue);
						Log.outl(dcj.toString(), LogLevel.info);
					}
					attribute = "";
					entity = "";
					state = State.PARSEBLANK;
				}
				i++;
				break;
			case PARSEENTITY:
				if (!(c == '.')) {
					entity += c;
				} else {
					state = State.PARSEATTRIBUTE;
				}
				i++;
				break;
			case PARSEBLANK:
				if (isBlank(c)) {
					i++;
				} else {
					state = State.PARSEENTITY;
					// don't increase i as read char is first char of next
					// entity
				}
				break;
			default:
				break;
			}
		}

		if (!attribute.isEmpty() && !entity.isEmpty()) {
			DateChangeJob dcj = new DateChangeJob(entity, attribute, delta,
					onlyCorrectCalendar);
			dateChangeJobs.add(dcj);
			Log.outlColor("DateChangeJob: ", LogLevel.info, Color.blue);
			Log.outl(dcj.toString(), LogLevel.info);
		}

	}

	/**
	 * returns true, if given character c is a blank ('\n', '\t', '\r', ',',
	 * ' ')
	 * 
	 * @param c
	 * @return true, if given character is a blank
	 */
	private static boolean isBlank(char c) {
		return c == ' ' || c == '\n' || c == '\t' || c == '\r' || c == ',';
	}

	/**
	 * 
	 * @return List of DateChangeJobs
	 */
	public static ArrayList<DateChangeJob> getDateChangeJobs() {
		return dateChangeJobs;
	}

	/**
	 * 
	 * @return List of SQL-scripts
	 */
	public static ArrayList<Script> getScripts() {
		return scripts;
	}

	public static String getFitnesseRoot() {
		return fitnesseRoot;
	}

	public static int getFitnesseDestinationSchoolyear() {
		return fitnesseDestinationSchoolyear;
	}

}
