// Copyright (C) 2011-2012 CRS4.
//
// This file is part of Seal.
//
// Seal is free software: you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by the Free
// Software Foundation, either version 3 of the License, or (at your option)
// any later version.
//
// Seal is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
// or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
// for more details.
//
// You should have received a copy of the GNU General Public License along
// with Seal.  If not, see <http://www.gnu.org/licenses/>.

package it.crs4.seal.common;

import java.io.IOException;
import java.io.File;
import java.io.LineNumberReader;
import java.io.Reader;

import java.util.regex.*;
import java.util.Properties;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Collection;


public class ConfigFileParser
{
	// Line patterns.  Lines are trimmed of spaces before being matched.
	// Section name
	private static final Pattern PSectionTitle = Pattern.compile("^\\[ *(\\w+) *\\] *$");
	// key-value.  Key is delimited by : or =.  Spaces before the value are removed.  Since
	// the line is trimmed, spaces at the end of the value will also be removed.
	// Except for this latest point, this should match the behaviour of the Python
	// ConfigParser module.
	private static final Pattern PKvLine = Pattern.compile("^ *([^:=]+)[:=] *(.*) *$");

	// Settings in the default section are inherited by all other sections.
	// It must be named DEFAULT for compatibility.
	private static final String DefaultSectionName = "DEFAULT";

	private Properties defaultProperties;
	private HashMap<String, Properties> sections;

	private enum TokenType {
		SectionName,
		KeyValue
	}

	public static class KvPair
	{
		private String key;
		private String value;

		public KvPair(String k, String v) {
			key = k;
			value = v;
		}

		public String getKey() { return key; }
		public String getValue() { return value; }

		public boolean equals(Object other) {
			if (other instanceof KvPair)
			{
				KvPair otherPair = (KvPair)other;
				if (key == null && value == null)
					return otherPair.key == null && otherPair.value == null;
				else if (key == null && value != null)
					return (key == otherPair.key) && value.equals(otherPair.value);
				else if (value == null && key != null)
					return (value == otherPair.value) && key.equals(otherPair.key);
				else
					return key.equals(otherPair.key) && value.equals(otherPair.value);
			}
			else
				return false;
		}

		public int hashCode() {
			int k = (key == null) ? 0 : key.hashCode();
			int v = (value == null) ? 0 : value.hashCode();
			return k ^ v;
		}

		public String toString() {
		 	return ((key == null) ? "''" : key) + " => " + ((value == null) ? "''" : value);
		}
	}

	public static class KvIterator implements Iterator<KvPair>
	{
		private Iterator<String> namesIt;
		private Properties properties;

		public KvIterator(Properties p)
		{
			properties = p;
			namesIt = p.stringPropertyNames().iterator();
		}

		public boolean hasNext()
		{
			return namesIt.hasNext();
		}

		public KvPair next()
		{
			String propName = namesIt.next();
			return new KvPair(propName, properties.getProperty(propName));
		}

		public void remove()
		{
			throw new UnsupportedOperationException();
		}
	}

	// in here we maintain the current scanner state and token
	private LineNumberReader input;
	private TokenType currentToken;

	private String currentSectionName;
	private KvPair tokenKV;

	public ConfigFileParser()
	{
		defaultProperties = new Properties();
		sections = new HashMap<String, Properties>();
		currentSectionName = null;;
	}

	/**
	 * Reads the next high-level token from the input file.
	 * Advances the reader to the next high-level token, either a section title or
	 * a key-value assignment.
	 *
	 * @return true if the next token was read; false if the token was not advanced because there's no more to read.
	 * @exception IOException Something bad happened while reading the file.
	 * @exception FormatException An invalid line was read from the config file.
	 */
	private boolean advanceToken() throws IOException, FormatException
	{
		String line = null;
		boolean done = false;
		do
		{
			line = input.readLine();
			if (line != null) // readLine returns null on EOF
			{
				line = line.trim();
				if (line.isEmpty() || line.charAt(0) == '#' || line.charAt(0) == ';')
				{
					// comment line.  Skip
				}
				else
				{
					Matcher titleMatcher = PSectionTitle.matcher(line);
					Matcher kvMatcher = PKvLine.matcher(line);
					if (titleMatcher.matches())
					{
						currentToken = TokenType.SectionName;
						currentSectionName = titleMatcher.group(1);
						done = true;
					}
					else if (kvMatcher.matches())
					{
						currentToken = TokenType.KeyValue;
						tokenKV = new KvPair(kvMatcher.group(1).trim(), kvMatcher.group(2).trim());
						done = true;
					}
					else
						throw new FormatException("Invalid config line (" + input.getLineNumber() + "): " + line);
				}
			}
			else
				done = true; // EOF
		}
		while (!done);

		return line != null;
	}

	public void load(Reader cfgFile) throws IOException, FormatException
	{
		input = new LineNumberReader(cfgFile);

		// re-initialize data structures to support reloading
		defaultProperties = new Properties();
		sections = new HashMap<String, Properties>();
		currentSectionName = null;

		while (advanceToken())
		{
			if (TokenType.KeyValue == currentToken)
			{
				Properties p;
				if (currentSectionName == null || currentSectionName.equalsIgnoreCase(DefaultSectionName))
					p = defaultProperties;
				else
					p = sections.get(currentSectionName);

				p.put(tokenKV.getKey(), tokenKV.getValue());
			}
			else if (TokenType.SectionName == currentToken)
			{
				sections.put(currentSectionName, new Properties(defaultProperties));
			}
			else
				throw new RuntimeException("Unknown token type " + currentToken + ". Please file a bug report");
		}
	}

	public Collection<String> getSectionNames()
	{
		return sections.keySet();
	}

	public boolean hasSection(String sectionName)
	{
		if (sectionName != null && sectionName.equalsIgnoreCase(DefaultSectionName))
			return true;
		else
			return sections.containsKey(sectionName);
	}

	public Iterator<KvPair> getSectionIterator(String sectionName)
	{
		Properties p = sections.get(sectionName);
		if (p == null)
			p = new Properties(defaultProperties);

		return new KvIterator(p);
	}

	public String getValue(String sectionName, String key)
	{
		Properties section = sections.get(sectionName);
		if (section != null)
			return section.getProperty(key);
		else
			return defaultProperties.getProperty(key);
	}
}
