/*
 * #%L
 * LabelUtils
 * %%
 * Copyright (C) 2012 INRIA
 * %%
 * 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.
 * #L%
 */
package org.praat;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.nio.charset.Charset;
import java.text.NumberFormat;
import java.util.Locale;

import jregex.Matcher;
import jregex.Pattern;

import com.google.common.base.Strings;
import com.google.common.io.Files;

public class PraatTextFile extends PraatFile {

	private BufferedReader reader;

	protected BufferedWriter writer;
	private String eol;
	protected NumberFormat number;
	final private int tabSize = 4;
	private int indent = 0;

	private static Pattern STRING_PATTERN = new Pattern("\"({target}.+)\"");
	private static Pattern INTEGER_PATTERN = new Pattern("(?!\\[)({target}\\d+)(?!\\])");
	private static Pattern DOUBLE_PATTERN = new Pattern("(?!\\[)({target}\\d+(\\.\\d+)?)(?!\\])");

	public PraatTextFile() {
		// TODO Auto-generated constructor stub
	}

	public PraatTextFile(File file, Charset charset, EOL eol) throws IOException {
		writer = Files.newWriter(file, charset);
		this.eol = eol.toString();
		number = NumberFormat.getInstance(Locale.US);
		writer.write("File type = \"ooTextFile\"");
		writeLine();
	}

	public PraatObject read(File file) throws Exception {
		return read(file, Charset.defaultCharset());
	}

	public PraatObject read(File file, Charset charset) throws Exception {
		reader = Files.newReader(file, charset);
		reader.readLine(); // discard first line

		// determine payload class (and ignore missing name)
		String className = readString();
		return readPayload(className);
	}

	@Override
	public PraatObject readPayLoad() throws Exception {
		// determine payload class
		String className = readString();

		// read name
		String name = readString();

		// read payload
		PraatObject object = readPayload(className);
		object.setName(name);
		return object;
	}

	private PraatObject readPayload(String className) throws Exception {
		// use reflection to create payload instance
		String packageName = getClass().getPackage().getName();
		String fullyQuallifiedClassString = packageName + "." + className;
		Class<?> praatClass;
		try {
			praatClass = Class.forName(fullyQuallifiedClassString);
		} catch (ClassNotFoundException e) {
			throw new ClassNotFoundException("Unsupported Praat class: " + className);
		}
		Class<?> superClass = getClass().getSuperclass();
		Constructor<?> constructor = praatClass.getConstructor(superClass);
		PraatObject payload;
		try {
			payload = (PraatObject) constructor.newInstance(this);
		} catch (ClassCastException e) {
			throw new ClassCastException("Could not cast " + praatClass + " to PraatObject");
		}
		return payload;
	}

	@Override
	public String readString() throws IOException {
		// get string from buffer
		String target;
		try {
			target = readPattern(STRING_PATTERN);
		} catch (IOException e) {
			throw new IOException("Could not read from buffer");
		} catch (IllegalArgumentException e) {
			throw new IllegalArgumentException("Could not read string from buffer");
		}
		return target;
	}

	@Override
	public int readInteger() throws IOException {
		// get integer string from buffer
		String target = null;
		try {
			target = readPattern(INTEGER_PATTERN);
		} catch (IOException e) {
			throw new IOException("Could not read from buffer");
		} catch (IllegalArgumentException e) {
			throw new IllegalArgumentException("Could not read integer from buffer");
		}

		// parse integer
		int targetInt;
		try {
			targetInt = Integer.parseInt(target);
		} catch (NumberFormatException e) {
			throw new NumberFormatException("Could not parse integer from string: " + target);
		}
		return targetInt;
	}

	@Override
	public double readDouble() throws IOException {
		// get double string from buffer
		String target = null;
		try {
			target = readPattern(DOUBLE_PATTERN);
		} catch (IOException e) {
			throw new IOException("Could not read from buffer");
		} catch (IllegalArgumentException e) {
			throw new IllegalArgumentException("Could not read double from buffer");
		}

		// parse double
		double targetDouble;
		try {
			targetDouble = Double.parseDouble(target);
		} catch (NumberFormatException e) {
			throw new NumberFormatException("Could not parse double from string: " + target);
		}
		return targetDouble;
	}

	private String readPattern(Pattern pattern) throws IOException {
		String line;
		// keep reading line from BufferedReader until we match
		while ((line = reader.readLine()) != null) {

			// match pattern to line
			Matcher matcher = pattern.matcher(line);

			// find pattern in line
			if (!matcher.find()) {
				continue; // read next line if pattern not found
			}

			// get match target
			String target = null;
			try {
				target = matcher.group("target");
			} catch (IllegalArgumentException e) {
				throw new IllegalArgumentException("Pattern /" + pattern + "/ does not contain required group");
			}
			return target;
		}
		throw new IllegalArgumentException("End of buffer reached without finding pattern /" + pattern + "/");
	}

	public void write(PraatObject object) throws IOException {
		writer.write(String.format("Object class = \"%s\"", object.getClass().getSimpleName()));
		writeLine();
		writeLine();
		writePayLoad(object);
		writer.flush();
		writer.close();
	}

	public void writePayLoad(PraatObject object) throws IOException {
		object.write(this);
	}

	@Override
	public void writeString(String decorator, String value) throws IOException {
		writeLine("%s \"%s\" ", decorator, value);
	}

	@Override
	public void writeInteger(String decorator, int value) throws IOException {
		writeLine("%s %d ", decorator, value);
	}

	@Override
	public void writeDouble(String decorator, double value) throws IOException {
		writeLine("%s %s ", decorator, number.format(value));
	}

	@Override
	public void writeLine(String format, Object... args) throws IOException {
		writer.write(Strings.repeat(" ", tabSize * indent));
		writer.write(String.format(Locale.US, format, args));
		writeLine();
	}

	public void writeLine() throws IOException {
		writer.write(eol);
	}

	@Override
	public void increaseIndent() {
		indent++;
	}

	@Override
	public void decreaseIndent() {
		indent--;
	}

	/**
	 * Constants for end-of-line (EOL) encoding, named for their prevalent operating system.
	 * 
	 * @author ingmar
	 * 
	 */
	public enum EOL {

		/**
		 * CRLF
		 */
		WINDOWS {
			@Override
			public String toString() {
				return "\r\n";
			}
		},

		/**
		 * LF
		 */
		UNIX {
			@Override
			public String toString() {
				return "\n";
			}
		},

		/**
		 * CR
		 */
		MAC {
			@Override
			public String toString() {
				return "\r";
			}
		};

	}

}

@@ -1299,17 +1299,17 @@
 target%7D.
-+
+*
 )%5C%22%22);%0A%09

handle empty labels