// This software is released into the Public Domain.  See copying.txt for details.
package org.openstreetmap.osmosis.extract.apidb.v0_6;

import java.io.File;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;

import org.openstreetmap.osmosis.core.OsmosisConstants;
import org.openstreetmap.osmosis.core.OsmosisRuntimeException;
import org.openstreetmap.osmosis.core.util.FileBasedLock;
import org.openstreetmap.osmosis.core.util.ResourceFileManager;
import org.openstreetmap.osmosis.extract.apidb.common.Configuration;
import org.openstreetmap.osmosis.replication.common.TimestampTracker;


/**
 * The main entry point for the apidb change extraction application.
 * 
 * @author Brett Henderson
 */
public class OsmosisExtractApiDb {

	private static final File LOCK_FILE = new File("osmosis-extract-apidb.lock");
	private static final File CONFIG_FILE = new File("osmosis-extract-apidb.conf");
	private static final File DATA_DIR = new File("data");
	private static final File TSTAMP_FILE = new File("timestamp.txt");
	private static final File TSTAMP_NEW_FILE = new File("timestampnew.txt");
	private static final File DATA_TSTAMP_FILE = new File("data/timestamp.txt");
	private static final File DATA_TSTAMP_NEW_FILE = new File("data/timestampnew.txt");
	private static final String CONFIG_RESOURCE = "osmosis-extract-apidb.conf";
	private static final String COMMAND_HELP = "help";
	private static final String COMMAND_INITIALIZE = "initialize";
	private static final String COMMAND_INFO = "info";
	private static final String COMMAND_EXTRACT = "extract";
	private static final String COMMAND_LINE_DATE_FORMAT = "yyyy-MM-dd_HH:mm:ss";
	private static final Locale COMMAND_LINE_DATE_LOCALE = Locale.US;
	private static final TimeZone COMMAND_LINE_DATE_TIMEZONE = TimeZone.getTimeZone("UTC");
	private final String[] programArgs;


	/**
	 * The entry point to the application.
	 * 
	 * @param args
	 *            The command line arguments.
	 */
	public static void main(String[] args) {
		FileBasedLock fileLock = new FileBasedLock(LOCK_FILE);
		boolean success = false;

		try {
			fileLock.lock();

			new OsmosisExtractApiDb(args).run();

			fileLock.unlock();

			success = true;

		} finally {
			fileLock.close();
		}

		// Indicate success or otherwise.
		if (success) {
			System.exit(0);
		} else {
			System.exit(1);
		}
	}


	/**
	 * Creates a new instance.
	 * 
	 * @param programArgs
	 *            The command line arguments.
	 */
	public OsmosisExtractApiDb(String[] programArgs) {
		this.programArgs = programArgs;
	}


	/**
	 * Launches program execution.
	 */
	public void run() {
		int argIndex;
		String command;

		if (programArgs.length == 0) {
			helpCommand();
		} else {
			argIndex = 0;
			command = programArgs[argIndex++];

			if (COMMAND_HELP.equals(command)) {
				helpCommand();
			} else if (COMMAND_INITIALIZE.equals(command)) {
				initializeCommand(programArgs, argIndex);
			} else if (COMMAND_INFO.equals(command)) {
				infoCommand();
			} else if (COMMAND_EXTRACT.equals(command)) {
				extractCommand();
			} else {
				System.out.println("Command " + command + " is not recognised.");
			}
		}
	}


	/**
	 * Creates a configuration object.
	 * 
	 * @return The configuration.
	 */
	private Configuration getConfiguration() {
		return new Configuration(CONFIG_FILE);
	}


	/**
	 * Creates a timestamp tracker object for persisting the currently extracted timestamp.
	 * 
	 * @return The timestamp tracker.
	 */
	private TimestampTracker getTimestampTracker() {
		return new TimestampTracker(TSTAMP_FILE, TSTAMP_NEW_FILE);
	}


	/**
	 * Creates a timestamp tracker object for persisting the currently extracted timestamp into the
	 * data directory for consumers to download.
	 * 
	 * @return The timestamp tracker.
	 */
	private TimestampTracker getDataTimestampSetter() {
		return new TimestampTracker(DATA_TSTAMP_FILE, DATA_TSTAMP_NEW_FILE);
	}


	/**
	 * Gets a date argument from the program arguments.
	 * 
	 * @param args
	 *            The program arguments.
	 * @param argIndex
	 *            The current argument index.
	 * @return The parsed date.
	 */
	private Date getDateArgument(String[] args, int argIndex) {
		// Verify that the argument is available.
		if (args.length <= argIndex) {
			throw new OsmosisRuntimeException("A date argument is required at argument " + (argIndex + 1) + ".");
		}

		try {
			SimpleDateFormat dateFormat;

			dateFormat = new SimpleDateFormat(COMMAND_LINE_DATE_FORMAT, COMMAND_LINE_DATE_LOCALE);
			dateFormat.setTimeZone(COMMAND_LINE_DATE_TIMEZONE);

			return dateFormat.parse(args[argIndex]);

		} catch (ParseException e) {
			throw new OsmosisRuntimeException("Argument " + (argIndex + 1) + " must be a date in format "
					+ COMMAND_LINE_DATE_FORMAT + ".", e);
		}
	}


	/**
	 * Prints usage information to the console.
	 */
	private void helpCommand() {
		System.out.println("Osmosis Extract ApiDb Version " + OsmosisConstants.VERSION);
		System.out.println("Usage: osmosis-apidb-extract <command> <options>");
		System.out.println("Commands:");
		System.out.println("\t" + COMMAND_INITIALIZE + " <" + COMMAND_LINE_DATE_FORMAT + ">");
		System.out.println("\t" + COMMAND_INFO);
		System.out.println("\t" + COMMAND_EXTRACT);
	}


	/**
	 * Initialises the current working directory.
	 * 
	 * @param args
	 *            The input arguments.
	 * @param initialArgIndex
	 *            The current offset into the arguments.
	 */
	private void initializeCommand(String[] args, int initialArgIndex) {
		int currentArgIndex;
		Date initialExtractDate;
		ResourceFileManager resourceFileManager;

		// Get the command line arguments.
		currentArgIndex = initialArgIndex;
		initialExtractDate = getDateArgument(args, currentArgIndex++);

		if (CONFIG_FILE.exists()) {
			throw new OsmosisRuntimeException("Config file " + CONFIG_FILE + " already exists.");
		}
		resourceFileManager = new ResourceFileManager();
		resourceFileManager.copyResourceToFile(getClass(), CONFIG_RESOURCE, CONFIG_FILE);

		if (!DATA_DIR.exists()) {
			if (!DATA_DIR.mkdir()) {
				throw new OsmosisRuntimeException("Unable to create directory " + DATA_DIR);
			}
		}

		if (TSTAMP_FILE.exists()) {
			throw new OsmosisRuntimeException("Extract timestamp file " + TSTAMP_FILE + " already exists.");
		}
		getTimestampTracker().setTime(initialExtractDate);
	}


	/**
	 * Provides information about the state of the current working directory.
	 */
	private void infoCommand() {
		Configuration configuration;
		TimestampTracker timestampTracker;

		configuration = getConfiguration();
		timestampTracker = getTimestampTracker();

		System.out.println("Configuration");
		System.out.println("\thost: " + configuration.getHost());
		System.out.println("\tdatabase: " + configuration.getDatabase());
		System.out.println("\tuser: " + configuration.getUser());
		System.out.println("\tpassword: " + configuration.getPassword());
		System.out.println("\tdb: " + configuration.getDbType());
		System.out.println("\tintervalLength: " + configuration.getIntervalLength());
		System.out.println("\tlagLength: " + configuration.getLagLength());
		System.out.println("\tchangeSetBeginFormat: " + configuration.getChangeFileBeginFormat());
		System.out.println("\tchangeSetEndFormat: " + configuration.getChangeFileEndFormat());
		System.out.println();
		System.out.println("Data");
		System.out.println("\tCurrent Timestamp: " + timestampTracker.getTime());
	}


	/**
	 * Performs the extraction process.
	 */
	private void extractCommand() {
		Configuration configuration;
		DatabaseTimeLoader timeLoader;
		boolean fullHistory;
		TimestampTracker timestampTracker;
		TimestampTracker dataTimestampSetter;
		long extractTime;
		long maximumExtractTime;
		long nextExtractTime;

		configuration = getConfiguration();
		timeLoader = new DatabaseTimeLoader(configuration.getDatabaseLoginCredentials());
		fullHistory = configuration.getReadFullHistory();
		timestampTracker = getTimestampTracker();
		dataTimestampSetter = getDataTimestampSetter();

		// Determine the last extraction time.
		extractTime = timestampTracker.getTime().getTime();

		while (true) {
			Date intervalBegin;
			Date intervalEnd;
			IntervalExtractor extractor;

			nextExtractTime = extractTime + configuration.getIntervalLength();

			// Determine the maximum extraction time. It is the current time minus the lag length.
			maximumExtractTime = timeLoader.getDatabaseTime().getTime() - configuration.getLagLength();

			// Stop when the maximum extraction time is passed.
			if (nextExtractTime > maximumExtractTime) {
				break;
			}

			// Calculate the beginning and end of the next changeset interval.
			intervalBegin = new Date(extractTime);
			intervalEnd = new Date(nextExtractTime);

			// Extract a changeset for the current interval.
			extractor = new IntervalExtractor(configuration, DATA_DIR, intervalBegin, intervalEnd, fullHistory);
			extractor.run();

			// Update and persist the latest extract timestamp to both the
			// working directory and the output data directory.
			extractTime = nextExtractTime;
			timestampTracker.setTime(new Date(extractTime));
			dataTimestampSetter.setTime(new Date(extractTime));
		}
	}
}
