// Copyright 2003 by Basil Vandegriend.  All rights reserved.

package com.basilv.resumecalculator;

import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

import java.io.File;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import java.lang.reflect.Field;
import java.util.*;

import com.basilv.core.AppUtilities;
import com.basilv.core.Assert;
import com.basilv.core.richui.ExceptionHandler;
import com.basilv.resumecalculator.ResumeCalculatorService.CalculateDurationReporter;

/**
 * Resume Calculator reads a resume file and calculates years of experience
 * for a given set of resume entries or keywords provided in one or more entries.
 */
public class ResumeCalculatorApp
{
	static final int EXIT_OK = 0;
	static final int EXIT_INVALID_ARGS = 1;
	static final int EXIT_ERROR = 2;

	static final String EXECUTION_ERRORS_MSG = "Finished execution with errors.";
	static final String EXECUTION_SUCCESSFUL_MSG = "Finished execution successfully.";

	public static void main(String[] args) {
		int result = mainReturningResult(args);
		System.exit(result);
	}
	
	public static int mainReturningResult(String[] args) {
		try {
			AppUtilities.logApplicationInfoToSystemOut("Resume Calculator");

			ResumeCalculatorApp app = new ResumeCalculatorApp();

			Settings settings = new Settings();
			boolean validArguments = app.parseArguments(args, settings);
			if (!validArguments) {
				app.showArgumentsUsage();
				return EXIT_INVALID_ARGS;
			}

			app.execute(settings);
			return EXIT_OK;
			
		} catch (Throwable throwable) {
			ExceptionHandler.handleThrowable("Error running Resume Calculator.", throwable);
			System.err.println("Execution failed with an exception.");
			return EXIT_ERROR;
		}
	}

	
	/**
	 * Parse the supplied arguments and populate the supplied settings.
	 * Display a message to the user 
	 * @return true if the arguments parsed properly, false otherwise.
	 */
	boolean parseArguments(String[] args, Settings settings) {
		Assert.notNull("args", args);

		Map<String, Field> map = new HashMap<String, Field>();
		ArgumentParser parser = new ArgumentParser();
		Field[] fields = ArgumentParser.class.getDeclaredFields();
		for (Field field : fields) {
			Argument argument = field.getAnnotation(Argument.class);
			if (argument != null) {
				Assert.notNullOrEmpty("Value of @argument annotation for field " + field.getName(), argument.value());
				map.put(argument.value(), field);
			}
		}
		
		for (int parseIndex = 0; parseIndex < args.length; parseIndex++ ) {
			String currentArg = args[parseIndex];
			Field field = map.get(currentArg);
			if (field == null) {
				logInfo("Unrecognized argument '" + currentArg + "'.");
				return false;
			}
			parseIndex++;
			if (parseIndex >= args.length) {
				logInfo("Missing parameter for argument '" + currentArg + "'.");
				return false;
			}
			String parameter = args[parseIndex];
			try {
				field.setAccessible(true);
				field.set(parser, parameter);
			} catch (Exception e) {
				throw new RuntimeException("Error setting field ArgumentParser." + field.getName() + " for value '" + parameter + "'.", e);
			}
		}
		
	
		// Check that required arguments were supplied
		if (parser.resumeFile == null) {
			logInfo("Missing argument '-r' to supply resume file.");
			return false;
		}
		if (parser.entryIds == null) {
			logInfo("Missing argument '-d' to supply list of resume entry ids.");
			return false;
		}
		
		File resumeFile = new File(parser.resumeFile);
		if (! (resumeFile.exists() && resumeFile.canRead())) {
			logInfo("Specified resume file '" + FileUtils.getCanonicalPath(resumeFile) + "' does not exist or is not readable.");
			return false;
		}
		settings.setResumeFile(resumeFile);
		
		List<String> entryIds = new ArrayList<String>();
		String[] entryIdStrings = parser.entryIds.split(",");
		for (String entryIdString : entryIdStrings ) {
			entryIds.add(entryIdString.trim());
		}
		settings.setEntryIds(entryIds);

		return true;
	}

	// TODO: Lesson: Post as article?
	private static class ArgumentParser {
		@Argument("-r")
		private String resumeFile;
		
		@Argument("-d")
		private String entryIds;
	}
	
	@Retention(RUNTIME) @Target({FIELD})
	private @interface Argument {
		
		String value() ;
	}
	
	private void showArgumentsUsage() {
		String message="Usage: resumeCalculator -r <resumeFile> -d <entryIds>\n" +
			"Options:\n" +
			"-r <resumeFile> : Specify the resume XML file to use." +
			"-d <entryIds> : Calculate the total duration of the specified comma-delimited list of resume entry ids." +
			"";
		logInfo(message);
		
		// TODO: Can add other operations (like keywords). -d option becomes optional as one of a set of operations.
	}

	public void execute(Settings settings) {

		logInfo("Using resume \"" + FileUtils.getCanonicalPath(settings.getResumeFile()) + "\".");
		
		ResumeCalculatorService service = new ResumeCalculatorService();
		boolean executionSuccess = service.calculateDurationOfEntries(settings, new ConsoleCalculateDurationReporter());
		
		String executionMessage;
		if (executionSuccess) {
			executionMessage = EXECUTION_SUCCESSFUL_MSG;
		} else {
			executionMessage = EXECUTION_ERRORS_MSG;
		}
		logInfo(""); // Empty line
		logInfo(executionMessage);
	}

	private class ConsoleCalculateDurationReporter implements CalculateDurationReporter {

		public void handleExceptionParsingResumeFile(File resumeFile, Exception e) {
			logError("Error parsing resume file '" + FileUtils.getCanonicalPath(resumeFile) + "'. " + e.getMessage());
		}

		public void reportCalculatedDurationInMonthsAndYears(double months, double years) {
			String durationMessage = String.format("Duration is %.1f months or %.1f years.", months, years);
			logInfo(durationMessage);
		}

		
		public void reportSettings(Resume resume, List<String> entryIds) {
			String message = "Calculating for \"" + resume.getName() + "\" the duration of the resume entries: ";
			for (String entryId : entryIds) {
				ResumeEntry entry = resume.getEntry(entryId);
				if (entry == null) {
					throw new RuntimeException("No entry found for id '" + entryId + "'.");
				}
				message += "\n" + entry.getId() + ": " + entry.getSummary();
			}
			logInfo(message);
		}

		
		public void reportReadingResumeFile(File resumeFile) {
			logInfo("Reading resume file '" + FileUtils.getCanonicalPath(resumeFile) + "'...");
		}
	}
	

	private void logError(String message) {
		System.err.println(message);
	}

	private void logInfo(String message) {
		System.out.println(message);
	}

}
