package org.broadinstitute.sting.queue.engine

import org.broadinstitute.sting.queue.function.{CommandLineFunction, DispatchWaitFunction}
import org.broadinstitute.sting.queue.util.{IOUtils, LsfJob, Logging}

/**
 * Runs jobs on an LSF compute cluster.
 */
class LsfJobRunner extends DispatchJobRunner with Logging {
  type DispatchJobType = LsfJob

  /**
   * Dispatches the function on the LSF cluster.
   * @param function Command to run.
   * @param qGraph graph that holds the job, and if this is a dry run.
   */
  def run(function: CommandLineFunction, qGraph: QGraph) = {
    val job = new LsfJob
    job.name = function.jobName
    job.outputFile = function.jobOutputFile
    job.errorFile = function.jobErrorFile
    job.project = function.jobProject
    job.queue = function.jobQueue
    job.command = function.commandLine

    if (!IOUtils.CURRENT_DIR.getCanonicalFile.equals(function.commandDirectory))
      job.workingDir = function.commandDirectory

    if (function.jobRestartable)
      job.extraBsubArgs :+= "-r"

    if (function.memoryLimit.isDefined)
      job.extraBsubArgs ++= List("-R", "rusage[mem=" + function.memoryLimit.get + "]")

    if ( ! function.commandLine.contains("mkdir")) // wild hack -- ignore mkdirs ??
      job.postExecCommand = function.doneOutputs.foldLeft("python /humgen/gsa-scr1/chartl/sting/python/lsf_post_touch.py ")((b,a) => b + a.getAbsolutePath+" ")
    // hacky trailing space, so sue me -- CH

    val previous: Iterable[LsfJob] =
      if (function.isInstanceOf[DispatchWaitFunction]) {
        job.waitForCompletion = true
        getWaitJobs(qGraph)
      } else {
        previousJobs(function, qGraph)
      }
    
    mountCommand(function) match {
      case Some(command) => job.preExecCommand = command
      case None => /* ignore */
    }

    if (previous.size > 0)
      job.extraBsubArgs ++= List("-w", dependencyExpression(previous,
        function.jobRunOnlyIfPreviousSucceed, qGraph.dryRun))

    addJob(function, qGraph, job, previous)

    if (logger.isDebugEnabled) {
      logger.debug(function.commandDirectory + " > " + job.bsubCommand.mkString(" "))
    } else {
      logger.info(job.bsubCommand.mkString(" "))
    }

    if (!qGraph.dryRun)
      job.run
  }

  /**
   * Returns the dependency expression for the prior jobs.
   * @param jobs Previous jobs this job is dependent on.
   * @param runOnSuccess Run the job only if the previous jobs succeed.
   * @param dryRun If the current run is a dry run.
   * @return The dependency expression for the prior jobs.
   */
  private def dependencyExpression(jobs: Iterable[LsfJob],
                                   runOnSuccess: Boolean, dryRun: Boolean) = {
    val jobIds = if (dryRun)
      jobs.toSet[LsfJob].map("\"" + _.name + "\"")
    else
      jobs.toSet[LsfJob].map(_.bsubJobId)

    if (runOnSuccess)
      jobIds.mkString("done(", ") && done(", ")")
    else
      jobIds.mkString("ended(", ") && ended(", ")")
  }
}
