/**
  *
  *                      OOAS Compiler
  *
  *       Copyright 2015, AIT Austrian Institute of Technology.
  * This code is based on the C# Version of the OOAS Compiler, which is
  * copyright 2015 by the Institute of Software Technology, Graz University
  * of Technology with portions copyright by the AIT Austrian Institute of
  * Technology. All rights reserved.
  *
  * SEE THE "LICENSE" FILE FOR THE TERMS UNDER WHICH THIS FILE IS PROVIDED.
  *
  * If you modify the file please update the list of contributors below to in-
  * clude your name. Please also stick to the coding convention of using TABs
  * to do the basic (block-level) indentation and spaces for anything after
  * that. (Enable the display of special chars and it should be pretty obvious
  * what this means.) Also, remove all trailing whitespace.
  *
  * Contributors:
  *               Willibald Krenn (AIT)
  *               Stephan Zimmerer (AIT)
  *               Markus Demetz (AIT)
  *               Christoph Czurda (AIT)
  *
  */


package org.momut.ooas;

import java.io.File;
import java.io.FileFilter;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.momut.ooas.CompilerConfiguration.LabelCompression;
import org.momut.ooas.ast.IAstVisitor;
import org.momut.ooas.codegen.prolog.OoaPrologVisitor;
import org.momut.ooas.codegen.prologsymbolic.OoaPrologSymbolicVisitor;
import org.momut.ooas.parser.ParserError;
import org.momut.ooas.parser.ParserMessage;
import org.momut.ooas.parser.ParserState;
import org.momut.ooas.parser.ParserWarning;
import org.momut.ooas.parser.ooaCustomParser;
import org.momut.ooas.visitors.OoaActionClassifierVisitor;
import org.momut.ooas.visitors.OoaMethodPureClassifierVisitor;
import org.momut.ooas.visitors.OoaObjectInstantiationVisitor;
import org.momut.ooas.visitors.OoaReplaceOpaqueVisitor;
import org.momut.ooas.visitors.OoaResolveExpressionsVisitor;
import org.momut.ooas.visitors.OoaSymbolSortVisitor;
import org.momut.ooas.visitors.OoaTypeCheckVisitor;
import org.momut.ooas.visitors.OoaTypeRenameVisitor;
import org.momut.ooas.visitors.OoaTypesVisitor;
import org.momut.ooas.visitors.optimisation.OoaRemoveTrivialPrioritizedCompositionVisitor;

public final class CmdlineCompiler
{
	public interface CreateVisitor {
		public IAstVisitor cerate(ParserState aState);
	}

	public static class Options
	{
		public boolean quiet = false;
		public String fileToParse = "";
		public String outputToFile = "";
		public String namedTypePrefix = "";
		public String namespace = "";
		public ArrayList<CreateVisitor> pipeLine = new ArrayList<CreateVisitor>();
	}

	static void displayTitle()
	{
		System.out.println();
		System.out.println("          >> OO-Action System Parser <<");
		System.out.println();
		System.out.println();
	}


	/* fire up the parser */
	static int runAntlrParser(Options options)
	{
		ParserState pState;
		try {
			pState = new ParserState(options.fileToParse, options.namedTypePrefix);
			final int result = ooaCustomParser.FirstPass(pState);
			if (result > 0)
			{
				System.out.println(String.format("The parser returned %s errors:", result));
				for (final ParserError error: pState.listOfParserErrors)
					System.out.println(String.format("ERROR (%s): %s,%s; %s",
							error.file(), error.line(), error.column(), error.message()));
			}
			else
			{
				System.out.println("   Passed: OoaParser");
				if (options.pipeLine.size() > 0)
				{

					IAstVisitor visitor = null;
					for (final CreateVisitor item: options.pipeLine)
					{
						visitor = item.cerate(pState);
						pState.ooaSystem.Accept(visitor);
						if (pState.listOfParserErrors.size() > 0)
						{
							System.out.println(String.format("The parser (%s) returned %s errors:",
									visitor.returnVisitorName(), pState.listOfParserErrors.size()));

							for (final ParserError error: pState.listOfParserErrors)
								System.out.println(String.format("ERROR (%s): %s,%s; %s",
										error.file(), error.line(), error.column(), error.message()));
							return pState.listOfParserErrors.size();
						}
						else
						{
							System.out.print("   Passed: ");
							System.out.println(visitor.returnVisitorName());
						}
					}
					System.out.println();
					System.out.println(String.format("File %s successfully parsed.", pState.filename));
					System.out.println();

					if (pState.listOfParserMessages.size() > 0 && !(options.quiet))
					{
						System.out.println(String.format("Parser returned %s messages:", pState.listOfParserMessages.size()));
						for (final ParserMessage message: pState.listOfParserMessages)
							System.out.println(String.format("Message (%s): %s,%s; %s",
									message.file(), message.line(), message.column(), message.message()));
						System.out.println();
						System.out.println();
					}

					if (pState.listOfParserWarnings.size() > 0 && !(options.quiet))
					{
						System.out.println(String.format("Parser returned %s warnings:", pState.listOfParserWarnings.size()));
						for (final ParserWarning warning: pState.listOfParserWarnings)
							System.out.println(String.format("Warning (%s): %s,%s; %s",
									warning.file(), warning.line(), warning.column(), warning.message()));
						System.out.println("");
						System.out.println("");
					}

					if (!options.outputToFile.equals(""))
					{
						System.out.println(String.format("Writing output to %s.", options.outputToFile));
						final FileWriter fw = new FileWriter(options.outputToFile, false);
						fw.write(visitor.toString()); // fixme: this should be ascii..
						fw.close();
					}
					else
						System.out.println(visitor.toString());
				}
				else
				{
					System.out.println();
					System.out.println(String.format("File %s successfully parsed.", pState.filename));
					System.out.println("");
				}
			}

			return result;
		} catch (final IOException e) {
			e.printStackTrace();
			return -1;
		}
	}


	private static void fillPipeLine(Options options) {
		int index = 0;

		options.pipeLine.add(index++, new CreateVisitor(){
			@Override
			public IAstVisitor cerate(ParserState aState) {
				return new OoaSymbolSortVisitor(aState);
			}});
		options.pipeLine.add(index++, new CreateVisitor(){
			@Override
			public IAstVisitor cerate(ParserState aState) {
				return new OoaReplaceOpaqueVisitor(aState);
			}});
		options.pipeLine.add(index++, new CreateVisitor(){
			@Override
			public IAstVisitor cerate(ParserState aState) {
				return new OoaTypesVisitor(aState);
			}});
		options.pipeLine.add(index++, new CreateVisitor(){
			@Override
			public IAstVisitor cerate(ParserState aState) {
				return new OoaResolveExpressionsVisitor(aState);
			}});
		options.pipeLine.add(index++, new CreateVisitor(){
			@Override
			public IAstVisitor cerate(ParserState aState) {
				return new OoaMethodPureClassifierVisitor(aState);
			}});
		options.pipeLine.add(index++, new CreateVisitor(){
			@Override
			public IAstVisitor cerate(ParserState aState) {
				return new OoaTypeCheckVisitor(aState);
			}});

		if (!options.namedTypePrefix.equals(""))
		{
			options.pipeLine.add(index++, new CreateVisitor(){
				@Override
				public IAstVisitor cerate(ParserState aState) {
					return new OoaTypeRenameVisitor(aState);
				}});
		}

		options.pipeLine.add(index++, new CreateVisitor(){
			@Override
			public IAstVisitor cerate(ParserState aState) {
				return new OoaRemoveTrivialPrioritizedCompositionVisitor(aState);
			}});
		options.pipeLine.add(index++, new CreateVisitor(){
			@Override
			public IAstVisitor cerate(ParserState aState) {
				return new OoaActionClassifierVisitor(aState);
			}});

		options.pipeLine.add(index++, new CreateVisitor(){
			@Override
			public IAstVisitor cerate(ParserState aState) {
				return new OoaObjectInstantiationVisitor(aState, LabelCompression.NoCompression);
			}});
	}


	/* entry point */
	public static void main(String[] args)
	{
		displayTitle();

		if (args.length != 4) {
			System.out.println("3 Arguments expected: backend orig/mut inputfile outputfile");
			System.out.println();
			System.out.println("   backend    ... 'psym' or 'p'");
			System.out.println("   orig/mut   ... 'orig' or 'mut'");
			System.out.println("   input  file or directory ... full path to input .ooas file or");
			System.out.println("                                directory containing .ooas files");
			System.out.println("   output file or directory ... full path to output file; will erase or");
			System.out.println("                                directory (only if also input is a directory)");
			System.out.println();
			System.exit(-1);
		}

		final Options options  = new Options();
		final boolean symbolic = args[0].toLowerCase().trim().equals("psym");
		final boolean mutant   = args[1].toLowerCase().trim().equals("mut");
		options.fileToParse    = args[2];
		options.outputToFile   = args[3];
		fillPipeLine(options); // add all basic operations
		final String namespace = mutant? "asm" : "as";
		options.namespace      = namespace;

		// add the prolog codegen target...
		if (symbolic) {
			options.pipeLine.add(new CreateVisitor(){
				@Override
				public IAstVisitor cerate(ParserState aState) {
					return new OoaPrologSymbolicVisitor(aState, 35, namespace);
				}});
		} else {
			options.pipeLine.add(new CreateVisitor(){
				@Override
				public IAstVisitor cerate(ParserState aState) {
					return new OoaPrologVisitor(aState, 35, namespace); // "as" ... original, "asm" ... mutant
				}});
		}
		System.out.println(String.format("Running OOAS Compiler... [backend=%s, namespace=%s]", symbolic?"psym":"p", namespace));
		if (new File(options.fileToParse).isDirectory()) {
			directoryMode(options);
		} else {
			fileMode(options);
		}
	}

	/**
	 * Iterates over all files in a directory if options.fileToParse is a directory
	 * and runs the antlr parser for each file. the output is stored in the same
	 * directory if the options.outputToFile is blank or not a directory.
	 * @param options
	 */
	public static void directoryMode(final Options options) {
		final File input  = new File(options.fileToParse);
		final File output = new File(options.outputToFile);
		final List<File> errors = new ArrayList<File>();

		File[] filesToParse = null;
		final FileFilter filter = new FileFilter() {
			@Override
			public final boolean accept(final File myFile) {
				return myFile.getName().endsWith(".ooas");
			}
		};

		System.out.println(String.format("Processing Directory [Input, Output]: %s, %s",
				input.getAbsolutePath(), output.getAbsoluteFile()));

		filesToParse = input.listFiles(filter);

		int result              = 0;
		File fileToParse        = null;
		String inputFileName    = "";
		String outputFileName   = "";
		final String outputFileSuffix = "." + options.namespace + ".pl";

		if (filesToParse != null) {
			for (int i = 0; i < filesToParse.length; ++i) {
				fileToParse    = filesToParse[i];
				inputFileName  = fileToParse.getAbsolutePath();
				outputFileName = fileToParse.getName().substring(0, fileToParse.getName().indexOf(".ooas")).concat(outputFileSuffix);

				if (output != null) {
					if (!output.isDirectory())
						output.mkdir();
					outputFileName = output.getAbsolutePath() + File.separatorChar + outputFileName;
				}
				else {
					// use same directory as input file for output
					outputFileName = fileToParse.getParent() + File.separatorChar + outputFileName;
				}

				options.fileToParse  = inputFileName;
				options.outputToFile = outputFileName;

				System.out.println(String.format("Parsing %s", options.fileToParse));
				result = runAntlrParser(options);
				if (result != 0)
					errors.add(fileToParse);
			}
		}

		if (errors.size() > 0) {
			System.out.println("Following files were not processed correctly:");
			final Iterator<File> i = errors.iterator();
			File file = null;
			while (i.hasNext()) {
				file = i.next();
				System.out.println(file.getName());
			}
			System.exit(-1);
		}
		System.exit(0);
	}

	/**
	 * Runs the antlr parser with the given options
	 * @param options
	 */
	public static void fileMode(final Options options) {
		System.out.println(String.format("Parsing %s.", options.fileToParse));
		final int result = runAntlrParser(options);
		System.exit(result);
	}
}
