package de.ugoe.cs.eventbench.windows;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;

import org.apache.commons.codec.binary.Base64;

import de.ugoe.cs.util.FileTools;
import de.ugoe.cs.util.StringTools;
import de.ugoe.cs.util.console.Console;

/**
 * <p>
 * Pre-processes log files generated by the EventBench's MFCUsageMonitor. It
 * decodes Base64 encoding into UTF-16. It removes all lines of the log file,
 * that do not start with the prefix "UL:", end everything before the prefix and
 * the prefix itself.
 * </p>
 * 
 * @author Steffen Herbold
 * @version 1.0
 */
public class LogPreprocessor {

	/**
	 * <p>
	 * Internal flag that monitors whether there is an open session-node in the
	 * XML file to ensure that there is a closing session-node for each opening
	 * session node and, thereby, ensure that the XML file is well formed.
	 * </p>
	 */
	private boolean sessionOpen = false;

	/**
	 * <p>
	 * Internal flag that monitors whether a message node is longer than one
	 * line, as the prefix handling is different in this case.
	 * </p>
	 */
	private boolean msgIncomplete = false;

	/**
	 * <p>
	 * Flag that marks whether the log file is Base64 encoded.
	 * </p>
	 */
	private boolean base64;

	/**
	 * <p>
	 * Constructor. Creates a new LogPreprocessor that does not decode Base64.
	 * </p>
	 */
	public LogPreprocessor() {
		this(false);
	}

	/**
	 * <p>
	 * Constructor. Creates a new LogPreprocessor.
	 * </p>
	 * 
	 * @param base64
	 *            if true, Base64 will be decoded.
	 */
	public LogPreprocessor(boolean base64) {
		this.base64 = base64;
	}

	/**
	 * <p>
	 * Pre-processes a single log file.
	 * </p>
	 * 
	 * @param source
	 *            name and path of the source file
	 * @param target
	 *            name and path of the target file
	 * @throws IOException
	 *             thrown if there is a problem with reading from or writing to
	 *             the source, respectively target file
	 * @throws FileNotFoundException
	 *             thrown if the source file is not found
	 */
	public void convertToXml(String source, String target) throws IOException,
			FileNotFoundException {
		OutputStreamWriter targetFile = new OutputStreamWriter(
				new FileOutputStream(target), "UTF-16");
		targetFile.write("<?xml version=\"1.0\" encoding=\"UTF-16\"?>"
				+ StringTools.ENDLINE);
		targetFile.write("<log>" + StringTools.ENDLINE);
		processFile(source, targetFile);
		if (sessionOpen) {
			targetFile.write(" </session>" + StringTools.ENDLINE);
		}
		targetFile.write("</log>");
		targetFile.close();
	}

	/**
	 * <p>
	 * Pre-processes all files in a given source folder.
	 * </p>
	 * 
	 * @param source
	 *            path of the source folder
	 * @param target
	 *            name and path of the target file
	 * @throws IOException
	 *             thrown if there is a problem with reading from or writing to
	 *             the source, respectively target file
	 * @throws FileNotFoundException
	 *             thrown if the source file is not found
	 */
	public void convertDirToXml(String path, String target) throws IOException,
			FileNotFoundException {
		OutputStreamWriter targetFile = new OutputStreamWriter(
				new FileOutputStream(target), "UTF-16");
		targetFile.write("<?xml version=\"1.0\" encoding=\"UTF-16\"?>"
				+ StringTools.ENDLINE);
		targetFile.write("<log>" + StringTools.ENDLINE);
		File folder = new File(path);
		if (!folder.isDirectory()) {
			throw new IOException(path + " is not a directory");
		}
		String absolutPath = folder.getAbsolutePath();
		for (String filename : folder.list()) {
			String source = absolutPath + "/" + filename;
			Console.traceln("Processing file: " + source);
			processFile(source, targetFile);
		}

		if (sessionOpen) {
			targetFile.write(" </session>" + StringTools.ENDLINE);
		}
		targetFile.write("</log>");
		targetFile.close();
	}

	/**
	 * <p>
	 * Internal function that pre-processes a log file.
	 * </p>
	 * 
	 * @param source
	 *            name and path of the source file
	 * @param target
	 *            name and path of the target file
	 * @throws IOException
	 *             thrown if there is a problem with reading from or writing to
	 *             the source, respectively target file
	 * @throws FileNotFoundException
	 *             thrown if the source file is not found
	 */
	private void processFile(String source, OutputStreamWriter targetFile)
			throws FileNotFoundException, IOException {
		String[] lines = FileTools.getLinesFromFile(source, false);
		String incompleteLine = "";
		// Open source and read line by line
		for (String currentLine : lines) {
			if (currentLine.contains("UL: <session>")) {
				if (sessionOpen) {
					targetFile.write(" </session>" + StringTools.ENDLINE);
					targetFile.write(" <session>" + StringTools.ENDLINE);
				} else {
					targetFile.write(" <session>" + StringTools.ENDLINE);
					sessionOpen = true;
				}
			} else if (currentLine.contains("UL: </session>")) {
				if (sessionOpen) {
					targetFile.write(" </session>" + StringTools.ENDLINE);
					sessionOpen = false;
				}
			} else if (msgIncomplete || currentLine.contains("UL: ")) {

				String currentContent;
				String actualLine;
				if (msgIncomplete) {
					actualLine = currentLine;
				} else {
					String[] splitResult = currentLine.split("UL: ");
					actualLine = splitResult[1];
				}
				if (base64) {
					Base64 decoder = new Base64();
					byte[] decoded = decoder.decode(actualLine);
					currentContent = new String(decoded, "UTF-16LE");
					currentContent = currentContent.substring(0,
							currentContent.length() - 1);
				} else {
					currentContent = actualLine;
				}
				if (msgIncomplete) {
					incompleteLine += currentContent;
					if (incompleteLine.contains("</msg>")) {
						msgIncomplete = false;
						targetFile.write(incompleteLine + StringTools.ENDLINE);
						incompleteLine = "";
					}
				} else {
					if (currentContent.contains("<msg") && sessionOpen) {
						if (currentContent.contains("</msg>")) {
							targetFile.write("  " + currentContent
									+ StringTools.ENDLINE);
						} else {
							msgIncomplete = true;
							incompleteLine += currentContent;
						}
					}
				}
			}
		}
	}

}
