/*
 * Copyright 2000-2010 JetBrains s.r.o.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package jetbrains.buildServer.eclipse.agent;

import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;

import jetbrains.buildServer.RunBuildException;
import jetbrains.buildServer.agent.BuildRunnerContext;
import jetbrains.buildServer.agent.runner.CommandLineBuildService;
import jetbrains.buildServer.agent.runner.ProcessListener;
import jetbrains.buildServer.agent.runner.ProgramCommandLine;
import jetbrains.buildServer.agent.runner.SimpleProgramCommandLine;
import jetbrains.buildServer.eclipse.EclipseConstants;
import jetbrains.buildServer.eclipse.agent.builder.FeatureBasedProjectBuilder;
import jetbrains.buildServer.eclipse.agent.builder.JavaProjectBuilder;
import jetbrains.buildServer.eclipse.agent.builder.PluginsProjectBuilder;
import jetbrains.buildServer.eclipse.agent.builder.SiteProjectBuilder;
import jetbrains.buildServer.eclipse.java.BuildDescriptor;
import jetbrains.buildServer.eclipse.java.PlatformDescriptor;

import org.apache.log4j.Logger;
import org.jetbrains.annotations.NotNull;

public class EclipseBuilderFactory extends CommandLineBuildService /*BuildServiceAdapter*/{

  private static final String DEFAULT_JAVAC_SOURCE = "1.5";
  private static final String DEFAULT_JAVAC_TARGET = "1.5";

  private final static Logger LOG = Logger.getLogger(EclipseBuilderFactory.class);

  protected IBuilder myBuilder;

  protected EclipseBuilderFactory() {
    LOG.debug("New factory instance created");
  }

  protected IBuilder getBuilder() {
    return myBuilder;
  }

  @Override
  public ProgramCommandLine makeProgramCommandLine() throws RunBuildException {
    final LinkedList<ProgramCommandLine> runnerCommands = new LinkedList<ProgramCommandLine>(Arrays.asList(getBuilder().makeCommandLines()));
    if (runnerCommands == null || runnerCommands.size() == 0) {
      throw new RunBuildException(String.format("The '%s' did not return any command", getBuilder().getName()));

    } else if (runnerCommands.size() == 1) {
      LOG.debug("Single command detected. Run in general.");
      return runnerCommands.getFirst();

    } else {
      //have to concatenate several command into single line.
      LOG.debug("Batch command detected. Construct summary command.");
      final LinkedList<String> mergedArguments = new LinkedList<String>();
      for (final ProgramCommandLine command : runnerCommands) {
        LOG.debug(String.format("Processing command: %s %s", command.getExecutablePath(), command.getArguments()));
        if (!mergedArguments.isEmpty()) {
          mergedArguments.add("&&");
        }
        mergedArguments.add(command.getExecutablePath());//program
        for (String arg : command.getArguments()) {//args
          mergedArguments.add(arg);
        }
      }
      return makeOsRunnable(mergedArguments);

    }
  }

  private SimpleProgramCommandLine makeOsRunnable(final @NotNull LinkedList<String> args) throws RunBuildException {
    final String shell;
    if (isWindows()) {
      args.add(0, "/C");
      shell = "cmd";
    } else if (isUnix()) {
      args.add(0, "'");
      args.add("'");
      args.add(0, "-c");
      shell = "sh";
    } else if (isMacos()) {
      args.add(0, "'");
      args.add("'");
      args.add(0, "-c");
      shell = "sh";
    } else {
      throw new RunBuildException(String.format("Unsupported OS: sun.desktop='%s'", System.getProperty("sun.desktop")));
    }
    LOG.debug(String.format("Summary command: %s", args));
    return new SimpleProgramCommandLine(getRunnerContext(), shell, args);
  }

  private boolean isWindows() {
    final String osName = System.getProperty("sun.desktop").toLowerCase();
    return osName.contains("windows");
  }

  private boolean isUnix() {
    return !isWindows() && !isMacos();
  }

  private boolean isMacos() {
    return false;
  }

  @Override
  public void afterInitialized() throws RunBuildException {
    final BuildRunnerContext context = getRunnerContext();
    final PlatformDescriptor platform = getPlatform(context);
    LOG.debug(String.format("Eclipse platform exists: %s", platform));
    final BuildDescriptor buildDescriptor = getBuildDescriptor(platform, context);
    LOG.debug(String.format("Created build descriptor: %s", buildDescriptor));
    /**
     * Prepare
     */
    myBuilder = null;
    if (buildDescriptor.isSite()) {
      myBuilder = new SiteProjectBuilder(getRunnerContext(), platform, buildDescriptor);
    } else if (buildDescriptor.isProduct() || buildDescriptor.isFeature()) {
      myBuilder = new FeatureBasedProjectBuilder(getRunnerContext(), platform, buildDescriptor);
    } else if (buildDescriptor.isPlugin()) {
      myBuilder = new PluginsProjectBuilder(getRunnerContext(), platform, buildDescriptor);
    } else if (buildDescriptor.isJava()) {
      myBuilder = new JavaProjectBuilder(getRunnerContext(), platform, buildDescriptor);
    }
    //provide command line
    if (myBuilder != null) {
      LOG.debug(String.format("'%s' created", myBuilder));
      myBuilder.init();
    } else {
      //nothing found nothing build...
      throw new RunBuildException(String.format("Could not find appropriate runner: platform [%s], build [%s]", platform, buildDescriptor));
    }
    super.afterInitialized();
  }

  @Override
  public void afterProcessSuccessfullyFinished() throws RunBuildException {
    getBuilder().destroy();
    LOG.debug("afterProcessSuccessfullyFinished()");
    super.afterProcessSuccessfullyFinished();
  }

  protected BuildDescriptor getBuildDescriptor(@NotNull final PlatformDescriptor platform, @NotNull final BuildRunnerContext context) throws RunBuildException {
    //.project
    final String mainProjectPath = context.getRunnerParameters().get(EclipseConstants.SETTINGS_RELATIVE_PATH_TO_PROJECT);
    LOG.debug(String.format("found %s='%s'", EclipseConstants.SETTINGS_RELATIVE_PATH_TO_PROJECT, mainProjectPath));
    if (mainProjectPath == null) {
      throw new RunBuildException(String.format("Path to main eclipse's project file is not set. Make sure this one is specified in the runner configuration."));
    }
    final File mainProject = new File(context.getWorkingDirectory(), mainProjectPath);//TODO: is it MUST be in working folder?
    LOG.debug(String.format("checking main project file '%s'", mainProject));
    if (!mainProject.exists()) {
      throw new RunBuildException(String.format("Main eclipse's project file '%s' does not exist. Make sure this one is properly specified in the runner configuration.", mainProject));
    }
    //output
    String outputPath = context.getRunnerParameters().get(EclipseConstants.SETTINGS_ARTIFACTS_OUTPUT_PATH);
    if (outputPath == null || outputPath.trim().length() == 0) {
      LOG.debug(String.format("Output path is empty. Use working directory as destination", outputPath));
      outputPath = ".";
    }
    final File outputDirectory = new File(context.getWorkingDirectory(), outputPath);//TODO: is it MUST be in working folder?
    if (!outputDirectory.exists()) {
      outputDirectory.mkdirs();
      LOG.debug(String.format("New output directory '%s' created", outputDirectory));
    }
    //source
    final String javacSource;
    final String params = context.getRunnerParameters().get(EclipseConstants.PDE_BUILD_PROP_JAVAC_SOURCE);
    if (params != null && params.trim().length() > 0) {
      javacSource = params.trim();
    } else {
      javacSource = DEFAULT_JAVAC_SOURCE;//let so...
    }
    //target
    final String javacTarget;
    final String paramt = context.getRunnerParameters().get(EclipseConstants.PDE_BUILD_PROP_JAVAC_TARGET);
    if (paramt != null && paramt.trim().length() > 0) {
      javacTarget = paramt.trim();
    } else {
      javacTarget = DEFAULT_JAVAC_TARGET;//let so...
    }
    try {
      final BuildDescriptor descriptor = BuildDescriptor.create(platform, getJavaHome(), mainProject, outputDirectory, javacSource, javacTarget);      
      descriptor.validate();
      return descriptor;      
    } catch (IOException e) {
      throw new RunBuildException(e.getMessage());
    } catch (IllegalArgumentException e) {
      throw new RunBuildException(e.getMessage());
    }
    
  }

  private File getJavaHome() {
    return new File(System.getProperty("java.home"));//TODO: it's possible to set unique JAVA for build 
  }

  protected PlatformDescriptor getPlatform(@NotNull final BuildRunnerContext context) throws RunBuildException {
    /**
     * looking for path
     */
    //check build setting
    String eclipseHome = context.getRunnerParameters().get(EclipseConstants.SETTINGS_ECLIPSE_BASE);
    if (eclipseHome == null) {
      LOG.debug(String.format("No \"%s\" found in RunnerParameters", EclipseConstants.SETTINGS_ECLIPSE_BASE));
      //check system properties
      eclipseHome = context.getBuildParameters().getSystemProperties().get(EclipseConstants.ECLIPSE_HOME_PROP_NAME);
      if (eclipseHome == null) {
        LOG.debug(String.format("No \"%s\" found in SystemProperties", EclipseConstants.ECLIPSE_HOME_PROP_NAME));
        //check environment variables
        eclipseHome = context.getBuildParameters().getEnvironmentVariables().get(EclipseConstants.ECLIPSE_HOME_ENV_NAME);
        if (eclipseHome == null) {
          //TODO: check ECLIPSE_HOME also
          throw new RunBuildException(
              String.format("Path to an Eclipse platform is not set. Make sure this one is specified in the runner configuration or '%s' System property or '%s' Environment variable is defined.", EclipseConstants.ECLIPSE_HOME_PROP_NAME, EclipseConstants.ECLIPSE_HOME_ENV_NAME));
        }
        LOG.debug(String.format("%s=\"%s\" found in EnvironmentVariables", EclipseConstants.ECLIPSE_HOME_ENV_NAME, eclipseHome));
      } else {
        LOG.debug(String.format("%s=\"%s\" found in SystemProperties", EclipseConstants.ECLIPSE_HOME_PROP_NAME, eclipseHome));
      }
      LOG.debug(String.format("%s=\"%s\" found in RunnerParameters", EclipseConstants.SETTINGS_ECLIPSE_BASE, eclipseHome));
    }
    /**
     * check the path is valid
     */
    final File home = new File(eclipseHome);
    if (!home.exists() || !home.isDirectory()) {
      throw new RunBuildException(String.format("The specified Path to Eclipse platform \"%s\" does not exist or is not a directory.", eclipseHome));
    }
    /**
     * load platform description
     */
    final PlatformDescriptor descriptor = PlatformDescriptor.create(home);
    try {
      descriptor.validate();
    } catch (IllegalArgumentException e) {
      throw new RunBuildException(e.getMessage());
    }
    return descriptor;
  }

  @Override
  public List<ProcessListener> getListeners() {
    return Collections.<ProcessListener> singletonList(new BuildLogger(getLogger()));
  }

  public static String getJavaExecutable(final @NotNull BuildRunnerContext context) {
    return "java";//TODO: use settings!!!!
  }
}
