/**
  *
  *                      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.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

import org.momut.ooas.CompilerConfiguration.Backend;
import org.momut.ooas.ast.IAstVisitor;
import org.momut.ooas.codegen.ast.OoaAstEmitter;
import org.momut.ooas.codegen.cadp.OoaCADPVisitor;
import org.momut.ooas.codegen.prolog.OoaPrologVisitor;
import org.momut.ooas.codegen.prologsymbolic.OoaPrologSymbolicVisitor;
import org.momut.ooas.parser.CompilerMessage;
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.utils.exceptions.OoasCompilerRuntimeException;
import org.momut.ooas.visitors.OoaActionClassifierVisitor;
import org.momut.ooas.visitors.OoaCheckObjectRefsConstant;
import org.momut.ooas.visitors.OoaMethodPureClassifierVisitor;
import org.momut.ooas.visitors.OoaObjectInstantiationVisitor;
import org.momut.ooas.visitors.OoaPrintVisitor;
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.analysis.OoaCheckASTVisitor;
import org.momut.ooas.visitors.analysis.OoaPrintActionSequencesVisitor;
import org.momut.ooas.visitors.analysis.OoaPrintActionsVisitor;
import org.momut.ooas.visitors.analysis.OoaPrintMetricsVisitor;
import org.momut.ooas.visitors.optimisation.OoaRemoveTrivialBlocksVisitor;

public final class Compiler {
	public interface IProgressCallBack {
		public void startingCompilePhase(String compilePhaseName);
		public void doneCompilePhase();
	}
	public interface ILogCallBack {
		public void logError(boolean toConsole, String message);
	}
	private static final class NullProgressCallBack implements IProgressCallBack {
		@Override
		public void startingCompilePhase(String compilePhaseName) {}
		@Override
		public void doneCompilePhase() {}
	}
	private interface CreateVisitor {
		public IAstVisitor create(ParserState aState);
	}

	public final CompilerConfiguration config;
	private final ArrayList<CreateVisitor> pipeLine = new ArrayList<CreateVisitor>();
	private ArrayList<ParserError> listOfParserErrors = new ArrayList<>();
	private ArrayList<ParserWarning> listOfParserWarnings = new ArrayList<>();
	private ArrayList<ParserMessage> listOfParserMessages = new ArrayList<>();

	private void updateParserMsgs(ParserState pState) {
		final Comparator<CompilerMessage> comparator = new Comparator<CompilerMessage>(){
			@Override
			public int compare(CompilerMessage arg0, CompilerMessage arg1) {
				if (arg0.line() != arg1.line())
					return arg0.line() - arg1.line();
				else
					return arg0.column() - arg1.column();
			}};

		listOfParserErrors = pState.listOfParserErrors;
		listOfParserMessages = pState.listOfParserMessages;
		listOfParserWarnings = pState.listOfParserWarnings;

		// sort the logs
		Collections.sort(listOfParserErrors, comparator);
		Collections.sort(listOfParserMessages, comparator);
		Collections.sort(listOfParserWarnings, comparator);
	}

	public List<ParserError> getErrors() {
		return Collections.unmodifiableList(listOfParserErrors);
	}
	public List<ParserWarning> getWarnings() {
		return Collections.unmodifiableList(listOfParserWarnings);
	}
	public List<ParserMessage> getMessages() {
		return Collections.unmodifiableList(listOfParserMessages);
	}

	@Override
	public String toString() {
		final StringBuilder result = new StringBuilder();
		for (final ParserError err: getErrors())
			result.append(String.format("ERROR: %s (%s,%s): %s\n",
				err.file(), err.line(), err.column(), err.message()));
		for (final ParserWarning err: getWarnings())
			result.append(String.format("Warning: %s (%s,%s): %s\n",
				err.file(), err.line(), err.column(), err.message()));
		for (final ParserMessage msg: getMessages())
			result.append(String.format("Info: %s (%s,%s): %s\n",
				msg.file(), msg.line(), msg.column(), msg.message()));
		return result.toString();
	}

	private void fillPipeLine() {
		pipeLine.add(new CreateVisitor(){
			@Override
			public IAstVisitor create(ParserState aState) {
				return new OoaCheckASTVisitor(aState, "Stage 0");
			}});
		pipeLine.add(new CreateVisitor(){
			@Override
			public IAstVisitor create(ParserState aState) {
				return new OoaSymbolSortVisitor(aState);
			}});
		pipeLine.add(new CreateVisitor(){
			@Override
			public IAstVisitor create(ParserState aState) {
				return new OoaReplaceOpaqueVisitor(aState);
			}});
		pipeLine.add(new CreateVisitor(){
			@Override
			public IAstVisitor create(ParserState aState) {
				return new OoaTypesVisitor(aState);
			}});
		pipeLine.add(new CreateVisitor(){
			@Override
			public IAstVisitor create(ParserState aState) {
				return new OoaResolveExpressionsVisitor(aState);
			}});
		pipeLine.add(new CreateVisitor(){
			@Override
			public IAstVisitor create(ParserState aState) {
				return new OoaMethodPureClassifierVisitor(aState);
			}});
		pipeLine.add(new CreateVisitor(){
			@Override
			public IAstVisitor create(ParserState aState) {
				return new OoaTypeCheckVisitor(aState);
			}});

		if (!config.getNamedTypePrefix().equals(""))
		{
			pipeLine.add(new CreateVisitor(){
				@Override
				public IAstVisitor create(ParserState aState) {
					return new OoaTypeRenameVisitor(aState);
				}});
		}

		// do some trivial "optimizations" (remove prioritized and nondet choice blocks
		// with only one element, join sequential blocks that are direct children of
		// sequential blocks to one sequential block
		pipeLine.add(new CreateVisitor(){
			@Override
			public IAstVisitor create(ParserState aState) {
				return new OoaRemoveTrivialBlocksVisitor(aState);
			}});
		// check the AST
		pipeLine.add(new CreateVisitor(){
			@Override
			public IAstVisitor create(ParserState aState) {
				return new OoaCheckASTVisitor(aState,"After Optimisation");
			}});


		pipeLine.add(new CreateVisitor(){
			@Override
			public IAstVisitor create(ParserState aState) {
				return new OoaActionClassifierVisitor(aState);
			}});

		pipeLine.add(new CreateVisitor(){
			@Override
			public IAstVisitor create(ParserState aState) {
				return new OoaPrintMetricsVisitor(aState);
			}
		});

		switch (config.getBackend()) {
		case Printer:
			pipeLine.add(new CreateVisitor(){
				@Override
				public IAstVisitor create(ParserState aState) {
					return new OoaPrintVisitor(aState);
				}});
			break;
		case CADP :
			pipeLine.add(new CreateVisitor(){
				@Override
				public IAstVisitor create(ParserState aState) {
					return new OoaObjectInstantiationVisitor(aState, config.getLabelCompression());
				}});
			pipeLine.add(new CreateVisitor(){
				@Override
				public IAstVisitor create(ParserState aState) {
					return new OoaPrintActionsVisitor(aState);
				}});
			pipeLine.add(new CreateVisitor(){
				@Override
				public IAstVisitor create(ParserState aState) {
					return new OoaPrintActionSequencesVisitor(aState);
				}});
			pipeLine.add(new CreateVisitor(){
				@Override
				public IAstVisitor create(ParserState aState) {
					return new OoaCADPVisitor(aState);
				}});
			break;
		case Prolog:
			pipeLine.add(new CreateVisitor(){
				@Override
				public IAstVisitor create(ParserState aState) {
					return new OoaObjectInstantiationVisitor(aState, config.getLabelCompression());
				}});
			pipeLine.add(new CreateVisitor(){
				@Override
				public IAstVisitor create(ParserState aState) {
					return new OoaPrintActionsVisitor(aState);
				}});
			pipeLine.add(new CreateVisitor(){
				@Override
				public IAstVisitor create(ParserState aState) {
					return new OoaPrintActionSequencesVisitor(aState);
				}});
			pipeLine.add(new CreateVisitor(){
				@Override
				public IAstVisitor create(ParserState aState) {
					return new OoaPrologVisitor(aState, config.getMaxSearchDepth(), config.getNameSpace());
				}});
			break;
		case PrologSymbolic:
			pipeLine.add(new CreateVisitor(){
				@Override
				public IAstVisitor create(ParserState aState) {
					return new OoaCheckObjectRefsConstant(aState);
				}});
			pipeLine.add(new CreateVisitor(){
				@Override
				public IAstVisitor create(ParserState aState) {
					return new OoaObjectInstantiationVisitor(aState, config.getLabelCompression());
				}});
			pipeLine.add(new CreateVisitor(){
				@Override
				public IAstVisitor create(ParserState aState) {
					return new OoaPrintActionSequencesVisitor(aState);
				}});
			pipeLine.add(new CreateVisitor(){
				@Override
				public IAstVisitor create(ParserState aState) {
					return new OoaPrintActionsVisitor(aState);
				}});
			pipeLine.add(new CreateVisitor(){
				@Override
				public IAstVisitor create(ParserState aState) {
					return new OoaPrologSymbolicVisitor(aState, config.getMaxSearchDepth(), config.getNameSpace());
				}});
			break;
		case CopyAST:
			pipeLine.add(new CreateVisitor(){
				@Override
				public IAstVisitor create(ParserState aState) {
					return new OoaObjectInstantiationVisitor(aState, config.getLabelCompression());
				}});
			pipeLine.add(new CreateVisitor(){
				@Override
				public IAstVisitor create(ParserState aState) {
					return new OoaPrintActionsVisitor(aState);
				}});
			pipeLine.add(new CreateVisitor(){
				@Override
				public IAstVisitor create(ParserState aState) {
					return new OoaAstEmitter<>(aState, config.getAstTarget());
				}});
			break;
		default:
			throw new OoasCompilerRuntimeException("Unknown backend.");
		}
	}


	public int compile(ILogCallBack logCallback) {
		return compile(new NullProgressCallBack(), logCallback);
	}

	/* fire up the parser */
	public int compile(IProgressCallBack callback, ILogCallBack logCallback)
	{
		if (config.isWriteable()) {
			config.setWriteOnly();
			fillPipeLine();
		}

		ParserState pState;
		try {
			callback.startingCompilePhase("OoaParser");
			pState = config.useStreams()
					?  new ParserState(config.getUnitName(), config.getInputStream(), config.getNamedTypePrefix())
					:  new ParserState(config.getFileToParse(), config.getNamedTypePrefix());
			final int result = ooaCustomParser.FirstPass(pState);
			if (result == 0) {
				callback.doneCompilePhase();
				if (pipeLine.size() > 0) {
					IAstVisitor visitor = null;
					for (final CreateVisitor item: pipeLine) {
						visitor = item.create(pState);
						callback.startingCompilePhase(visitor.returnVisitorName());
						pState.ooaSystem.Accept(visitor);
						visitor.done();
						if (pState.listOfParserErrors.size() > 0) {
							updateParserMsgs(pState);
							return pState.listOfParserErrors.size();
						} else
							callback.doneCompilePhase();
					}
					// after we've done all visitors, write the output file.
					if (!config.useStreams() && !config.getFileToOutput().equals(""))
					{
						final FileWriter fw = new FileWriter(config.getFileToOutput(), false);
						fw.write(visitor.toString()); // fixme: this should be ascii..
						fw.close();
					} else if (config.useStreams()) {
						final OutputStream out = config.getOutputStream();
						final BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(out));
						writer.write(visitor.toString());
						writer.close();
					}
				}
			}
			updateParserMsgs(pState);
			return result;
		} catch (final IOException e) {
			logCallback.logError(true, e.toString());
			return -1;
		}
	}


	public Compiler(CompilerConfiguration config) {
		this.config = config;
	}

	public Compiler(String unitName, String fileToParse, String fileToWrite, Backend be) {
		this(new CompilerConfiguration(unitName, fileToParse, fileToWrite, be));
	}

	public Compiler(String fileToParse, String fileToWrite, Backend be) {
		this(new File(fileToParse).getName(), fileToParse, fileToWrite, be);
	}

	public Compiler(String unitName, InputStream input, OutputStream output, Backend be) {
		this(new CompilerConfiguration(unitName, input, output, be));
	}
}
