/**
 * Copyright (C) 2009 STMicroelectronics
 *
 * This file is part of "Mind Compiler" is free software: you can redistribute 
 * it and/or modify it under the terms of the GNU Lesser General Public License 
 * as published by the Free Software Foundation, either version 3 of the 
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT 
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
 * details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Contact: mind@ow2.org
 *
 * Authors: Matthieu Leclercq
 * Contributors: Julien Tous
 */

package org.ow2.mind;

import static org.ow2.mind.PathHelper.fullyQualifiedNameToPath;
import static org.ow2.mind.PathHelper.isRelative;
import static org.ow2.mind.compilation.DirectiveHelper.splitOptionString;
import static org.testng.Assert.assertNotNull;
import static org.testng.Assert.assertTrue;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.objectweb.fractal.adl.ADLException;
import org.objectweb.fractal.adl.Definition;
import org.objectweb.fractal.adl.Loader;
import org.objectweb.fractal.adl.error.Error;
import org.ow2.mind.adl.DefinitionCompiler;
import org.ow2.mind.adl.GraphCompiler;
import org.ow2.mind.adl.graph.ComponentGraph;
import org.ow2.mind.adl.graph.Instantiator;
import org.ow2.mind.compilation.CompilationCommand;
import org.ow2.mind.compilation.CompilationCommandExecutor;
import org.ow2.mind.compilation.CompilerCommand;
import org.ow2.mind.compilation.CompilerContextHelper;
import org.ow2.mind.error.ErrorCollection;
import org.ow2.mind.error.ErrorManager;
import org.ow2.mind.inject.GuiceModuleExtensionHelper;
import org.ow2.mind.io.BasicOutputFileLocator;
import org.ow2.mind.io.OutputFileLocator;
import org.ow2.mind.plugin.PluginLoaderModule;
import org.ow2.mind.plugin.PluginManager;
import org.testng.Assert;

import com.google.inject.Guice;
import com.google.inject.Injector;

public class CompilerRunner {

  public static final String              DEFAULT_CFLAGS    = "-g -Wall -Werror -Wredundant-decls -Wunreachable-code -Wstrict-prototypes -Wwrite-strings";
  public static final String              CFLAGS_PROPERTY   = "mind.test.cflags";

  public static final String              COMPILER_PROPERTY = "mind.test.compiler";

  public final ErrorManager               errorManager;
  public final Loader                     adlLoader;

  public final Instantiator               graphInstantiator;

  public final OutputFileLocator          outputFileLocator;
  public final DefinitionCompiler         definitionCompiler;
  public final GraphCompiler              graphCompiler;

  public final CompilationCommandExecutor executor;

  public final PluginManager              pluginManager;

  public final File                       buildDir;
  public Map<Object, Object>              context;
  private final Map<Object, Object>       initialContext;

  public CompilerRunner() throws ADLException {
    this(new HashMap<Object, Object>());
  }

  public CompilerRunner(final Map<Object, Object> initialContext)
      throws ADLException {
    this.initialContext = initialContext;
    final Injector pluginManagerInjector = Guice
        .createInjector(new PluginLoaderModule());
    pluginManager = pluginManagerInjector.getInstance(PluginManager.class);

    final Injector injector = Guice.createInjector(GuiceModuleExtensionHelper
        .getModules(pluginManager, initialContext));

    errorManager = injector.getInstance(ErrorManager.class);
    adlLoader = injector.getInstance(Loader.class);
    graphInstantiator = injector.getInstance(Instantiator.class);
    graphCompiler = injector.getInstance(GraphCompiler.class);
    outputFileLocator = injector.getInstance(OutputFileLocator.class);
    definitionCompiler = injector.getInstance(DefinitionCompiler.class);
    executor = injector.getInstance(CompilationCommandExecutor.class);

    buildDir = new File("target/build");

    // init context
    initContext();
  }

  public void initContext() throws ADLException {
    context = new HashMap<Object, Object>(initialContext);
    if (!buildDir.exists()) {
      buildDir.mkdirs();
    }
    context.put(BasicOutputFileLocator.OUTPUT_DIR_CONTEXT_KEY, buildDir);

    final String cFlags = System.getProperty(CFLAGS_PROPERTY, DEFAULT_CFLAGS);
    CompilerContextHelper.setCFlags(context, splitOptionString(cFlags));

    final String compiler = System.getProperty(COMPILER_PROPERTY);
    if (compiler != null) {
      CompilerContextHelper.setCompilerCommand(context, compiler);
      CompilerContextHelper.setAssemblerCommand(context, compiler);
      CompilerContextHelper.setLinkerCommand(context, compiler);
    }

  }

  public Definition load(final String adlName) throws ADLException {
    errorManager.clear();
    final Definition d = adlLoader.load(adlName, context);
    final List<Error> errors = errorManager.getErrors();
    if (!errors.isEmpty()) {
      throw new ADLException(new ErrorCollection(errors));
    }
    return d;
  }

  public Collection<File> compileDefinition(final String adlName)
      throws ADLException, InterruptedException {
    final Definition d = load(adlName);
    final Collection<CompilationCommand> c = definitionCompiler.visit(d,
        context);
    executor.exec(c, context);
    final List<File> result = new ArrayList<File>();
    for (final CompilationCommand command : c) {
      if (command instanceof CompilerCommand)
        result.addAll(command.getOutputFiles());
    }
    return result;
  }

  public File compile(final String adlName) throws ADLException,
      InterruptedException {
    return compile(adlName, null);
  }

  public File compile(final String adlName, final String executableName)
      throws ADLException, InterruptedException {
    String outputPath;
    if (executableName != null) {
      context.put("executable-name", executableName);
      outputPath = executableName;
      if (isRelative(outputPath)) {
        outputPath = "/" + outputPath;
      }
    } else {
      outputPath = fullyQualifiedNameToPath(adlName, null);
    }
    final File outputFile = outputFileLocator.getCExecutableOutputFile(
        outputPath, context);

    final Definition d = load(adlName);

    final ComponentGraph componentGraph = graphInstantiator.instantiate(d,
        context);
    List<Error> errors = errorManager.getErrors();
    if (!errors.isEmpty()) {
      throw new ADLException(new ErrorCollection(errors));
    }

    final Collection<CompilationCommand> commands = graphCompiler.visit(
        componentGraph, context);
    final boolean execOK = executor.exec(commands, context);

    errors = errorManager.getErrors();
    if (!errors.isEmpty()) {
      throw new ADLException(new ErrorCollection(errors));
    }

    assertTrue(
        execOK,
        "Execution of compilation commands returns false, but no error has been logged in error manager ");

    return outputFile;
  }

  public int run(final File executable, final String... params)
      throws Exception {
    final Process p;
    if (params != null && params.length > 0) {
      final List<String> args = new ArrayList<String>(params.length + 1);
      args.add(executable.getAbsolutePath());
      for (final String param : params) {
        args.add(param);
      }
      p = new ProcessBuilder(args).redirectErrorStream(true).start();
    } else {
      p = new ProcessBuilder(executable.getAbsolutePath()).redirectErrorStream(
          true).start();
    }
    final Thread readerThread = new Thread() {
      @Override
      public void run() {
        // output output and error stream on the logger.
        final BufferedReader reader = new BufferedReader(new InputStreamReader(
            p.getInputStream()));
        try {
          String line = reader.readLine();
          while (line != null) {
            System.out.println(line);
            line = reader.readLine();
          }
          reader.close();
        } catch (final IOException e) {
          throw new java.lang.Error("Can't read error stream of process", e);
        }
      }
    };
    readerThread.start();
    final int rValue = p.waitFor();
    readerThread.join();

    return rValue;
  }

  public int compileAndRun(final String adlName, final String executableName,
      final String... params) throws Exception {
    final File executable = compile(adlName, executableName);
    assertNotNull(executable);
    return run(executable, params);
  }

  public void compileRunAndCheck(final String adlName,
      final String executableName, final String... params) throws Exception {
    Assert.assertEquals(compileAndRun(adlName, executableName, params), 0,
        "Unexpected result returned by test");
  }

  public void compileRunAndCheck(final String adlName) throws Exception {
    compileRunAndCheck(adlName, null);
  }

}
