package org.etsi.t3q;

import java.io.File;
import java.io.FileFilter;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Stack;

import org.etsi.t3q.config.T3QConfig;
import org.etsi.t3q.exceptions.TTCN3BehaviorException;
import org.etsi.t3q.exceptions.TTCN3ParserException;
import org.etsi.t3q.visitor.T3QVisitor;
import org.etsi.common.MiscTools;
import org.etsi.common.configuration.ConfigTools;
import org.etsi.common.configuration.ConfigurationProfile;
import org.etsi.common.configuration.ToolConfiguration;

import antlr.MismatchedTokenException;
import antlr.RecognitionException;
import antlr.TokenStreamException;
import de.ugoe.cs.swe.trex.core.analyzer.rfparser.LocationAST;
import de.ugoe.cs.swe.trex.core.analyzer.rfparser.TTCN3Analyzer;
import de.ugoe.cs.swe.trex.core.analyzer.rfparser.TTCN3AnalyzerFlyweightFactory;
import de.ugoe.cs.swe.trex.core.analyzer.rfparser.TTCN3FormatterTreeParser;
import de.ugoe.cs.swe.trex.core.analyzer.rfparser.TTCN3Parser;
import de.ugoe.cs.swe.trex.core.formatter.TTCN3Formatter;
import de.ugoe.cs.swe.trex.core.formatter.TTCN3FormatterParameters;

public class T3QMT {

	//TODO: a largely deprecated multithreaded version of T3Q. 
	//TODO: transfer the relevant concepts to the current version of T3Q
	
	private static String versionNumber = "v0.1.1";
	public static ToolConfiguration config = null;
	public static ConfigurationProfile activeProfile = null;
	private HashMap<String, String> argsMap = new HashMap<String, String>();
	private String targetPath = null;
	private HashMap<String, Integer> linesOfCodeMap = new HashMap<String, Integer>();
	private int totalLoc = 0;

	//private String outputPathArg = null;

	public T3QMT() {
	}

	// --------------------------------------------------------------------------

	public void showHelp() {
		System.out.println("Help:");
		System.out.println("  t3q[.cmd/.sh] [options] path");
		System.out.println("");
		System.out.println("  Options (specify in any order): ");
		System.out.println("    --help ");
		System.out.println("    --profile [profilename] ");
		//System.out.println("    --output [path] ");
		System.out.println("");
	}

	// --------------------------------------------------------------------------

	public void run(String[] args) {
		Runtime runtime = Runtime.getRuntime();
		int maxThreads = runtime.availableProcessors();
		System.out.println("T3Q " + versionNumber);
		System.out.println("  TTCN-3 version supported: " + TTCN3Parser.getSupportedVersion());
		System.out.println("  Number of processors: " + maxThreads);
		System.out.println("==========================================");
        System.out.println();

		
		if (handleCommandLineArguments(args) == false)
			return;

		if (argsMap.get("profile") != null) {
			loadConfig(argsMap.get("profile"));
		} else {
			loadConfig(null);
		}

		File path = new File(targetPath);
		if (!path.isDirectory()) {
			System.out
					.println("Error: specified argument is *not* a directory!");
			return;
		}
		
		if (T3QMT.activeProfile.isSettingRecursiveProcessing()){
			System.out.println("Collecting ttcn3 files recursively in \""
					+ targetPath + "\" ...");
		} else {
			System.out.println("Collecting ttcn3 files non-recursively in \""
					+ targetPath + "\" ...");
		}
		List<String> ttcn3Resources = findTTCN3Resources(targetPath);

		long startTime = System.currentTimeMillis();

		try {
			System.out.println("Parsing files...");
			TTCN3AnalyzerFlyweightFactory
			.getInstance();
			final Stack<Thread> threadStack = new Stack<Thread>();
			
			for (int j = 0; j < ttcn3Resources.size(); j++) {
				final int i = j;
				final String resourcePath = ttcn3Resources.get(i);
								
				Thread analysisThread = new Thread(new Runnable() {

					@Override
					public void run() {
						TTCN3AnalyzerFlyweightFactory analyzerFactory = TTCN3AnalyzerFlyweightFactory
						.getInstance();

						analyzeFile(resourcePath);
						TTCN3Analyzer analyzer = analyzerFactory.getTTCN3Analyzer(resourcePath);

						if (analyzer.getExceptions().size() > 0){
							
							try {
								throw new TTCN3ParserException("Error while parsing file "
										+ analyzer.getFilename());
							} catch (TTCN3ParserException e) {
								System.err.println(e.getLocalizedMessage());
								for (int i1 = 0; i1 < analyzer.getExceptions().size(); i1++) {
									System.err.println("Line "
											+ analyzer.getExceptions().get(i1).getLine() + ": "
											+ analyzer.getExceptions().get(i1).getMessage());
								}
							}
						}
					}
				});
				threadStack.push(analysisThread);
			}
			TTCN3Analyzer analyzer = null;

			Runnable parserRunnable = new Runnable() {
					@Override
					public void run() {
						while (!threadStack.isEmpty()) {
							Thread analysisThread = threadStack.pop();
							analysisThread.start();
							try {
								analysisThread.join();
							} catch (InterruptedException e) {
								e.printStackTrace();
							}
						}
					}
			};
			
			ArrayList<Thread> parserThreadList = new ArrayList<Thread>();
			for (int i=0; i < maxThreads; i++) {
				Thread parserThread = new Thread(parserRunnable);
				parserThreadList.add(parserThread);
				parserThread.start();
			}

			for (int i=0; i < maxThreads; i++) {
				try {
					parserThreadList.get(i).join();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}			
			
			long endTime = System.currentTimeMillis();
			long elapsed = endTime - startTime;
			double elapsedMinutes = ((double) elapsed) / 1000.0 / 60.0;
			System.out.println("done parsing in " + elapsed + "ms ("
					+ doubleToString2(elapsedMinutes) + " minutes).");
			
			if (T3QMT.activeProfile.isStatShowLOC()){
				System.out.println("Total lines of code parsed: " + totalLoc);
			}
			
			TTCN3AnalyzerFlyweightFactory analyzerFactory = TTCN3AnalyzerFlyweightFactory
					.getInstance();

			System.out.println("Postprocessing...");
			startTime = System.currentTimeMillis();
			analyzerFactory.postProcess();
			endTime = System.currentTimeMillis();
			elapsed = endTime - startTime;
			elapsedMinutes = ((double) elapsed) / 1000.0 / 60.0;
			System.out.println("Done processing in " + elapsed + "ms ("
					+ doubleToString2(elapsedMinutes) + " minutes).");
			startTime = System.currentTimeMillis();
			
			System.out.println("==========================================");
			for (int i = 0; i < ttcn3Resources.size(); i++) {
				analyzer = analyzerFactory.getTTCN3Analyzer(ttcn3Resources
						.get(i));
				
				T3QVisitor visitor = new T3QVisitor();
				System.out.println("Analyzing: " + analyzer.getFilename());
				visitor.setFilename(analyzer.getFilename());
				visitor.acceptDFS((LocationAST) analyzer.getParser().getAST());

			}
			endTime = System.currentTimeMillis();
			elapsed = endTime - startTime;
			elapsedMinutes = ((double) elapsed) / 1000.0 / 60.0;
			System.out.println("Quality checks finished in " + elapsed + "ms ("
					+ doubleToString2(elapsedMinutes) + " minutes).");

			analyzer = handleFormatter(ttcn3Resources, analyzer,
					analyzerFactory);
			if (T3QMT.activeProfile.isStatShowLOC()){
				System.out.println("Total lines of code processed: " + totalLoc);
			}

		} catch (TTCN3BehaviorException e) {
			System.err.println(e.getLocalizedMessage());
		}
//		} catch (TTCN3ParserException e) {
//  			//Default setting where processing is terminated in the event of a parsing error
//			System.err.println(e.getLocalizedMessage());
//			for (int i = 0; i < analyzer.getExceptions().size(); i++) {
//				System.err.println("Line "
//						+ analyzer.getExceptions().get(i).getLine() + ": "
//						+ analyzer.getExceptions().get(i).getMessage());
//			}
//			//TODO: Isolate different steps and implement a recovery mechanism:
//
//		}

	}

	private TTCN3Analyzer handleFormatter(List<String> ttcn3Resources,
			TTCN3Analyzer analyzer,
			TTCN3AnalyzerFlyweightFactory analyzerFactory) {
		long startTime;
		long endTime;
		long elapsed;
		double elapsedMinutes;
		if (false) {
			//TODO: consider ways to avoid reparsing (performing the comment collection during the first parse)
			System.out
					.println("==========================================");
			System.out.println("Formatting Code...");
			startTime = System.currentTimeMillis();

			TTCN3Formatter formatter = new TTCN3Formatter();

			for (int i = 0; i < ttcn3Resources.size(); i++) {
				analyzer = analyzerFactory.getTTCN3Analyzer(ttcn3Resources
						.get(i));
				System.out.println("  Formatting file: "
						+ analyzer.getFilename());
//				try {

					String resourcePath = ttcn3Resources.get(i);
					
					String source = MiscTools.readFile(resourcePath);
					
//					String formatted = formatter.formatTTCN3Source(source, T3QMT.activeProfile
//							.getFormattingParameters());
					
					//TODO: consider a separate output handler 
					//calculate the target path for the current resource
					String inputPath = new File(targetPath).getPath(); //strips the slash at the end
					String subPath = resourcePath.substring(inputPath.length()); 
//					String outputPathArg = T3QMT.activeProfile.getPathFormattedOutputPath();
//					String outputPath = outputPathArg + subPath;
					
//					System.out.println("****************************");
//					System.out.println("InputPath: "+inputPath);
//					System.out.println("ResourcePath: "+resourcePath);
//					System.out.println("SubPath: "+subPath);
//					System.out.println("OutputPath: "+outputPath);
//					System.out.println("****************************");
					
//					MiscTools.writeFile(outputPath, formatted);

//				} catch (RecognitionException e1) {
//					System.err.println("Recognition exception:");
//					System.err.println(e1.getLocalizedMessage());
//				} catch (TokenStreamException e) {
//					System.err.println("Token stream exception:");
//					e.printStackTrace();
//				} catch (Exception e) {
//					System.err.println("Exception:");
//					e.printStackTrace();
//				}

			}
			endTime = System.currentTimeMillis();
			elapsed = endTime - startTime;
			elapsedMinutes = ((double) elapsed) / 1000.0 / 60.0;
			System.out.println("Code formatting finished in " + elapsed
					+ "ms (" + doubleToString2(elapsedMinutes)
					+ " minutes).");

		}
		return analyzer;
	}

	private boolean handleCommandLineArguments(String[] args) {
		if (args.length < 1) {
			System.out.println("Error: too few command line arguments...");
			System.out.println("");
			showHelp();
			return false;
		}
		parseCommandLineArguments(args);
		if (argsMap.get("help") != null) {
			showHelp();
			return false;
		}

		return true;
	}

	// --------------------------------------------------------------------------

	private void parseCommandLineArguments(String[] args) {
		String key = "";
		String value = "";
		targetPath = "";

		boolean lastKey = false;
		for (int i = 0; i < args.length; i++) {
			if (args[i].startsWith("--")) {
				key = args[i].replaceAll("--", "").toLowerCase();

				if (lastKey) {
					argsMap.put(key, "true");
					key = null;
					value = null;
					lastKey = false;
				}

				lastKey = true;
			} else {
				value = args[i];
				if ((key != null) && (argsMap.get(key) == null)
						&& (key.length() > 0)) {
					argsMap.put(key, value);
					key = null;
					value = null;
				} else {
					targetPath += value + " ";
				}
				lastKey = false;
			}
		}

		if (key != null) {
			if ((argsMap.get(key) == null) && (key.length() > 0)) {
				argsMap.put(key, "true");
			}
		}
		targetPath = targetPath.trim();
	}

	// --------------------------------------------------------------------------

	private void loadConfig(String specifiedProfile) {
		boolean newConfiguration = false;
		System.out.println("Loading configuration ...");
		String configFile = null;
//		String configFile = ConfigTools.findConfig();
		if (configFile != null) {
			System.out.println("  Loading XML Configuration: " + configFile);
//			config = ConfigTools.readConfig(configFile);
		} else {
//			System.out
//					.println("  No existing configuration. Creating new default configuration: "
//							+ ConfigTools.getDefaultConfigurationFilePath());
			config = new T3QConfig();
//			ConfigurationProfile qcProfile = new ConfigurationProfile();
//			qcProfile.setProfileName("exampleProfile");
//			config.setDefaultConfigurationProfile("all");
//			if (config.getConfigurationProfiles() == null) {
//				ArrayList<ConfigurationProfile> qcProfiles = new ArrayList<ConfigurationProfile>();
//				config.setConfigurationProfiles(qcProfiles);
//			}
//			config.getConfigurationProfiles().add(qcProfile);
////			ConfigTools.writeConfig(config, ConfigTools
////					.getDefaultConfigurationFilePath());
//			newConfiguration = true;
		}
//		ConfigurationProfile allEnabledProfile = new ConfigurationProfile();
//		allEnabledProfile.setProfileName("all");
//		config.getConfigurationProfiles().add(allEnabledProfile);

		if (newConfiguration) {
			System.out.println("  Auto-selecting 'all' profile.");
//			activeProfile = allEnabledProfile;
		} else {
			if (specifiedProfile != null)
				activateProfile(specifiedProfile);
			if (activeProfile == null) {
				if (specifiedProfile != null) {
					System.out
							.println("  Profile '"
									+ specifiedProfile
									+ "' not found in configuration. Trying default profile '"
									+ config.getDefaultConfigurationProfile()
									+ "'.");
				} else {
					System.out
							.println("  No Profile specified. Trying default profile '"
									+ config.getDefaultConfigurationProfile()
									+ "'.");
				}
				specifiedProfile = config.getDefaultConfigurationProfile();
				activateProfile(specifiedProfile);

			}
			if (activeProfile == null) {
				System.out
						.println("  Profile '"
								+ specifiedProfile
								+ "' not found in configuration. Auto-selecting 'all' profile.");
//				activeProfile = allEnabledProfile;
			}
		}
	}

	// --------------------------------------------------------------------------

	private void activateProfile(String specifiedProfile) {
		for (int i = 0; i < config.getConfigurationProfiles().size(); i++) {
			ConfigurationProfile qualityProfile = config
					.getConfigurationProfiles().get(i);
			if (qualityProfile.getProfileName().toLowerCase().equals(
					specifiedProfile.toLowerCase())) {
				System.out.println("  Selecting '" + specifiedProfile
						+ "' profile.");
				activeProfile = qualityProfile;
			}
		}
	}

	// --------------------------------------------------------------------------

	List<String> findTTCN3Resources(String directory) {
		List<String> files = new LinkedList<String>();

		File f = new File(directory);

		File[] fileNames = f.listFiles(new FileFilter() {
			public boolean accept(File pathname) {
				if (pathname.getPath().endsWith(".ttcn3")
						|| pathname.getPath().endsWith(".ttcn")
						|| pathname.getPath().endsWith(".3mp"))
					return true;
				return false;
			}
		});

		for (int i = 0; i < fileNames.length; i++) {
			files.add(fileNames[i].getPath());
		}

		File[] directories = f.listFiles(new FileFilter() {
			public boolean accept(File pathname) {
				if (pathname.isDirectory())
					return true;
				return false;
			}
		});

		if (T3QMT.activeProfile.isSettingRecursiveProcessing()){
			for (int i = 0; i < directories.length; i++) {
				files.addAll(findTTCN3Resources(directories[i].getPath()));
			}
		}

		return files;
	}

	// --------------------------------------------------------------------------

	private TTCN3Analyzer analyzeFile(String filename) {
		TTCN3AnalyzerFlyweightFactory analyzerFactory = TTCN3AnalyzerFlyweightFactory
				.getInstance();
		analyzerFactory.setStandaloneUsage(true);
		String code = MiscTools.readFile(filename);

		int index = 0;
		int loc = 0;
		while (index != -1) {
			if (index == 0)
				index = code.indexOf("\n", index);
			else
				index = code.indexOf("\n", index+1);
			loc++;
		}
		linesOfCodeMap.put(filename, loc);
		totalLoc += loc;		

		System.out.println("  Parsing file: " + filename + " (LOC: " + linesOfCodeMap.get(filename) + ") ...");
		long startTime = System.currentTimeMillis();
		
		TTCN3Analyzer analyzer = analyzerFactory.getTTCN3Analyzer(filename,
				code);
		try {
			analyzer.analyze();
		} catch (MismatchedTokenException e) {
			e.printStackTrace();
		} catch (RecognitionException e) {
			e.printStackTrace();
		} catch (TokenStreamException e) {
			e.printStackTrace();
		} catch (Exception e) {
			e.printStackTrace();
		}
		long endTime = System.currentTimeMillis();
		long elapsed = endTime-startTime;
		double elapsedMinutes = ((double) elapsed) / 1000.0 / 60.0;
		
		System.out.println("    ...done " + filename + " in " + elapsed + "ms (" + doubleToString2(elapsedMinutes) + " minutes).");
		
		return analyzer;
	}

	// --------------------------------------------------------------------------
	public static String doubleToString2(double d) {
		DecimalFormat fmt = new DecimalFormat("0.00");
		String string = fmt.format(d);
		return string;
	}

	// --------------------------------------------------------------------------

	public static void main(String[] args) {
		T3QMT tool = new T3QMT();
		tool.run(args);
	}

}
