/*
 * Copyright (C) 2008 Steve Ratcliffe
 * 
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License version 2 as
 *  published by the Free Software Foundation.
 * 
 *  This program 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.
 * 
 * 
 * Author: Steve Ratcliffe
 * Create date: May 26, 2008
 */
package uk.me.parabola.mkgmap;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.io.IOException;
import java.io.Reader;
import java.util.Collection;
import java.util.HashSet;

import uk.me.parabola.log.Logger;
import uk.me.parabola.mkgmap.scan.TokType;
import uk.me.parabola.mkgmap.scan.Token;
import uk.me.parabola.mkgmap.scan.TokenScanner;

/**
 * Holds and reads options.  Like a properties file, but order is important
 * and events are generated when options are read.
 *
 * You can use the normal option syntax <tt>foo=bar</tt>.
 * You can also use <tt>foo: bar</tt> and for longer options that
 * span several lines <tt>foo { this can span lines }</tt>
 *
 * @author Steve Ratcliffe
 */
public class Options {
	private static final Logger log = Logger.getLogger(Options.class);

	private final OptionProcessor proc;

	// Used to prevent the same file being read more than once.
	private final Collection<String> readFiles = new HashSet<>();

	public Options(OptionProcessor proc) {
		this.proc = proc;
	}

	/**
	 * Read a config file that contains more options.  When the number of
	 * options becomes large it is more convenient to place them in a file.
	 *
	 * If the same file is read more than once, then the second time
	 * will be ignored.
	 *
	 * @param filename The filename to obtain options from.
	 */
	public void readOptionFile(String filename) throws IOException {
		log.info("reading option file", filename);

		File file = new File(filename);
		try {
			// Don't read the same file twice.
			String path = file.getCanonicalPath();
			if (readFiles.contains(path))
				return;
			readFiles.add(path);
		} catch (IOException e) {
			// Probably want to do more than warn here.
			log.warn("the config file could not be read");
			return;
		}

		try (Reader r = new InputStreamReader(new FileInputStream(filename)/*NB: DefaultCharset*/)) {
			readOptionFile(r, filename);
		}
		
	}

	public void readOptionFile(Reader r, String filename) {
		BufferedReader br;
		if (r instanceof BufferedReader)
			br = (BufferedReader) r;
		else
			br = new BufferedReader(r);
		TokenScanner ts = new TokenScanner(filename, br);
		ts.setExtraWordChars("-");
		if (r instanceof InputStreamReader)
			ts.setCharset(((InputStreamReader)r).getEncoding());

		File file = new File(filename);
		String parent = file.getParent();

		while (!ts.isEndOfFile()) {
			Token tok = ts.nextToken();
			if (tok.isValue("#")) {
				ts.skipLine();
				continue;
			}

			String key = tok.getValue();

			ts.skipSpace();
			tok = ts.peekToken();
			
			if (tok.getType() == TokType.SYMBOL) {

				String punc = ts.nextValue();
				String val;
				if (":".equals(punc) || "=".equals(punc)) {
					val = ts.readLine();
				} else if ("{".equals(punc)) {
					ts.skipSpace();
					val = ts.readUntil(TokType.SYMBOL, "}");
					ts.nextToken();  // discard the closing brace
				} else {
					ts.skipLine();
					continue;
				}

				// Relative file names in the file are taken relative to the
				// location of the argument file.
				if ("input-file".equals(key) && !new File(val).isAbsolute()) 
					val = new File(parent, val).getPath();

				proc.processOption(new Option(key, val));
			} else if (key != null){
				proc.processOption(new Option(key, ""));
			} else {
				ts.skipLine();
			}
		}
	}
}
