package kirjanpito.ui;

import java.io.File;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.LinkedList;

import kirjanpito.db.Account;
import kirjanpito.db.DataAccessException;
import kirjanpito.db.DataSource;
import kirjanpito.db.DataSourceFactory;
import kirjanpito.db.Session;
import kirjanpito.util.AppSettings;
import kirjanpito.util.Registry;

public class CommandLineInterface {
	private Registry registry;
	private int retcode;
	private String lastCommand;
	private String key;
	private String value;
	private SimpleDateFormat dateFormat;

	public int run(String[] arguments) {
		registry = new Registry();
		dateFormat = new SimpleDateFormat("yyyy-MM-dd");
		AppSettings settings = AppSettings.getInstance();
		LinkedList<String> args = new LinkedList<String>();

		for (String arg : arguments) {
			args.add(arg);
		}

		retcode = 0;

		if (args.size() >= 2 && (args.peek() == "-c" || args.peek() == "--config")) {
			args.remove();
			File configFile = new File(args.remove());
			settings.load(configFile);
		}
		else {
			File file = new File(AppSettings.buildDirectoryPath(Kirjanpito.APP_NAME),
				"asetukset.properties");
			settings.load(file);
		}

		try {
			openDataSource();

			if (!initializeRegistry()) {
				System.err.println("Tietokannassa ei ole vielä perustietoja");
				retcode = 4;
			}
		}
		catch (DataAccessException e) {
			System.err.println(e.getMessage());
			retcode = 1;
		}

		while (!args.isEmpty() && retcode == 0) {
			String arg = args.remove();

			if (arg.equals("--find-accounts")) {
				lastCommand = "find-accounts";
				parseFindAccountsCommand(args);
			}
			else if (arg.equals("--add-account")) {
				lastCommand = "add-account";
				parseSaveAccountCommand(args, true);
			}
			else {
				System.err.println(String.format("Virheellinen komento: %s", arg));
				retcode = 2;
			}
		}

		return retcode;
	}

	private void parseFindAccountsCommand(LinkedList<String> args) {
		while (parseKeyValue(args)) {
			System.err.println(key + " = " + value);
		}

		for (Account account : registry.getAccounts()) {
			printAccount(account);
		}
	}

	private void parseSaveAccountCommand(LinkedList<String> args, boolean addMode) {

	}

	/**
	 * Lukee avain-arvo-parin listasta <code>args</code>. Avain asetetaan muuttujaan
	 * <code>key</code> ja arvo muuttujaan <code>value</code>.
	 *
	 * @param args lista, josta avain-arvo-pari luetaan
	 * @return löytyykö listasta avain-arvo-paria
	 */
	private boolean parseKeyValue(LinkedList<String> args) {
		key = value = null;

		if (args.isEmpty()) {
			return false;
		}

		String next = args.peek();

		if (next.startsWith("-")) {
			return false;
		}

		args.removeFirst();
		int pos = next.indexOf('=');

		if (pos < 0) {
			key = next;
			value = "";
		}
		else {
			key = next.substring(0, pos);
			value = next.substring(pos + 1);
		}

		return true;
	}

	/**
	 * Avaa tietokantayhteyden.
	 *
	 * @throws DataAccessException jos yhteyden avaaminen epäonnistuu
	 */
	private void openDataSource() throws DataAccessException {
		/* Luetaan tietokanta-asetukset. */
		AppSettings settings = AppSettings.getInstance();
		String url = settings.getString("database.url", null);
		String username = settings.getString("database.username", "kirjanpito");
		String password = settings.getString("database.password", "kirjanpito");

		if (url == null) {
			url = buildDefaultJDBCURL();
		}

		registry.setDataSource(DataSourceFactory.create(url, username, password));
	}

	/**
	 * Muuntaa merkkijonon kokonaisluvuksi.
	 * Muuttujan <code>retcode</code> arvoksi asetetaan 3,
	 * jos muuntaminen epäonnistuu.
	 *
	 * @param value muunnettava merkkijono
	 * @param key avain, joka tulostuu virheilmoitukseen
	 * @return kokonaisluvuksi
	 */
	private int parseIntValue(String value, String key) {
		try {
			return Integer.parseInt(value);
		}
		catch (NumberFormatException e) {
			System.err.println(String.format("%s: Virheellinen kokonaisluku: %s = %s",
					lastCommand, key, value));
			retcode = 3;
			return -1;
		}
	}

	/**
	 * Muuntaa päivämäärämerkkijonon Date-olioksi.
	 * Muuttujan <code>retcode</code> arvoksi asetetaan 3,
	 * jos muuntaminen epäonnistuu.
	 *
	 * @param value päivämäärämerkkijono muodossa yyyy-MM-dd
	 * @param key avain, joka tulostuu virheilmoitukseen
	 * @return päivämäärä
	 */
	private Date parseDateValue(String value, String key) {
		try {
			return dateFormat.parse(value);
		}
		catch (ParseException e) {
			System.err.println(String.format("%s: Virheellinen päivämäärä: %s = %s",
					lastCommand, key, value));
			retcode = 3;
			return null;
		}
	}

	/**
	 * Tulostaa tilin <code>account</code> tiedot.
	 *
	 * @param account tili
	 */
	private void printAccount(Account account) {
		System.out.println("account");
		printProperty("id", Integer.toString(account.getId()));
		printProperty("number", account.getNumber());
		printProperty("name", account.getName());
		System.out.println();
	}

	/**
	 * Tulostaa avain-arvo-parin.
	 *
	 * @param key tulostettava avain
	 * @param value tulostettava arvo
	 */
	private void printProperty(String key, String value) {
		System.out.println(String.format("  %s: %s", key,
				value.replace("\n", "\\n").replace("\r",  "\\r")));
	}

	/**
	 * Palauttaa tietokannan oletus-JDBC-osoitteen.
	 *
	 * @return JDBC-osoite
	 */
	private String buildDefaultJDBCURL() {
		AppSettings settings = AppSettings.getInstance();
		File file = new File(settings.getDirectoryPath(), "kirjanpito.sqlite");
		return "jdbc:sqlite:" + file.getAbsolutePath();
	}

	/**
	 * Hakee tietokannasta perustiedot.
	 *
	 * @return <code>true</code>, jos hakeminen onnistuu; <code>false</code>, jos
	 * tietokantaan ei ole lisätty vielä perustietoja
	 * @throws DataAccessException jos tietojen hakeminen epäonnistuu
	 */
	private boolean initializeRegistry() throws DataAccessException {
		DataSource dataSource = registry.getDataSource();
		Session sess = null;

		try {
			sess = dataSource.openSession();

			/* Jos tietokantaan ei ole lisätty vielä perustietoja,
			 * keskeytetään tietojen hakeminen. */
			if (!registry.fetchPeriod(sess)) {
				return false;
			}

			registry.fetchSettings(sess);
			registry.fetchChartOfAccounts(sess);
			registry.fetchEntryTemplates(sess);
			registry.fetchDocumentTypes(sess);
		}
		finally {
			if (sess != null) sess.close();
		}

		return true;
	}

	public static void main(String[] args) {
		System.exit(new CommandLineInterface().run(args));
	}
}