//
// Program to simulate CPU scheduling.
//
// Input to this class's main method is a file (name given as a command-line
// argument) containing one or more lines, each representing one job to
// schedule, and containing:
//   Job ID (string)
//   Arrival time (integer)
//   Running time (integer)
//   Priority (integer, where higher numbers indicate higher priority)
//
// (Optional second command-line argument "--verbose" gives more-verbose 
// output.  You do not have to use this in your code, but it may be
// useful for debugging.)
//
// Output is, for each scheduling algorithm implemented, the following
//     information for each job, plus average turnaround:
//   Job ID and other input info
//   Start time
//   End time
//   Turnaround time
//

// FIXME comments show where you must add/change code.  

import scala.util.Sorting
import scala.collection.mutable.Queue

//
// ---- Main program ----
//
if (args.length < 1) {
  System.err.println("parameters:  inputfile [--verbose]")
  sys.exit(1)
}
val verbose = (args.length > 1) && (args(1) == "--verbose")
val jobsIn = parseFile(args(0))

scheduleAndPrint("First come, first served", scheduleFCFS, jobsIn, None)
scheduleAndPrint("Shortest job first", scheduleSJF, jobsIn, None);
scheduleAndPrint("Shortest remaining job next", scheduleSRTN, jobsIn, None);
scheduleAndPrint("Priority", schedulePriority, jobsIn, None);
scheduleAndPrint("Priority with preemption", schedulePriorityPreemption, jobsIn, None);
scheduleAndPrint("Round robin, quantum = 1", scheduleRR, jobsIn, Some(1));
scheduleAndPrint("Round robin, quantum = 4", scheduleRR, jobsIn, Some(4));

// ---- Classes for representing each job ----

case class JobInfoIn(jobID:String, arrival:Int, runtime:Int, priority:Int) 
class JobInfo(info:JobInfoIn) {
  val jobID = info.jobID
  val arrival = info.arrival
  val runtime = info.runtime
  val priority = info.priority
  var start = 0
  var end = 0
  var turnaround = 0
  var timeLeft = 0
}

// ---- Functions ---- //

def parseFile(fileName:String) : List[JobInfoIn] = {
  val lines = 
    try {
      val file = io.Source.fromFile(fileName)
      val tmp = file.getLines.toList
      file.close
      tmp
    }
    catch {
      case e:java.io.FileNotFoundException => {
        System.err.println("file "+fileName+" not found")
      sys.exit(1)
    }
    case t:Throwable => {
      System.err.println("error reading file "+fileName+t)
      sys.exit(1)
    }
  }
  lines.map(parseLine(_))
}

def parseLine(line:String) : JobInfoIn = {
  val fields = line.trim.split("""\s+""")
  if (fields.length != 4) {
    System.err.println("invalid input line "+line)
    sys.exit(1)
  }
  try {
    JobInfoIn(fields(0), fields(1).toInt, fields(2).toInt, fields(3).toInt)
  }
  catch {
    case e:NumberFormatException => {
      System.err.println("invalid input line "+line)
      sys.exit(1)
    }
  }
}

def scheduleAndPrint(headerMsg:String, 
  schedulerFunction : (Array[JobInfo], Option[Int]) => Boolean,
  jobsIn:Seq[JobInfoIn], 
  timeQuantum:Option[Int]) 
{
  val workArray = jobsIn.map(j => new JobInfo(j)).toArray
  Sorting.stableSort(workArray, 
    (j1:JobInfo, j2:JobInfo) => (j1.arrival < j2.arrival))

  val fmt = "%-4s %8s %8s %8s %8s %8s %12s"
  println()
  println(headerMsg)
  
  if (!schedulerFunction(workArray, timeQuantum)) {
    println()
    println("Not implemented")
    println()
  }
  else {
    println()
    println(fmt.format(
      "job", "arrival", "runtime", "priority", "start", "end",
      "turnaround"))
    println(fmt.format(
    "---", "-------", "-------", "--------", "-----", "---",
    "----------"))
    var totalTurnaround = 0
    workArray.foreach(j => {
      println(fmt.format(
        j.jobID, j.arrival, j.runtime, j.priority, 
        j.start, j.end, j.turnaround))
      totalTurnaround += j.turnaround
    })
    println()
    println("Average turnaround time = %.2f".format(
      totalTurnaround.toDouble / workArray.length))
    println()
  }
}

//
// ---- Scheduler functions ----
//
// These functions should either:
// (*) Fill in the start, end, and turnaround fields
//     of each element of an array of JobInfo objects and
//     return "true", or
// (*) Return false (to indicate not implemented yet)
//
// Parameters:
// (*) Array of JobInfo objects built from input and
//     guaranteed to be in order by arrival time.
// (*) Optional integer argument to be used to pass time slice
//     to function for Round Robin.
//
//
// Hints:
//
// I recommend a strategy like the one I use for FCFS:
//
// Simulate actual scheduling, with a "time" counter
// that advances as the algorithm works its way through
// its input, with a "ready queue" of jobs that have arrived but
// have not been completed yet.  This copes gracefully with
// the (perhaps rare) case in which all jobs that have arrived
// have been completed but more jobs will arrive later.
//
// The Scala PriorityQueue may be helpful with some algorithms.
//
// For preemptive algorithms, note that the JobInfo class includes
// a variable timeLeft that will likely be useful.
//

//
// First come, first served.
//
def scheduleFCFS(workArray:Array[JobInfo], unused:Option[Int]) : Boolean = {
  if (verbose) {
    println("\nStarting FCFS scheduling")
  }
 
  // this is a queue of indices into the array
  val readyQueue = new Queue[Int]

  var time = 0
  var nextInput = 0

  // schedule jobs until no more input and ready queue is empty
  while ((nextInput < workArray.length || (readyQueue.length > 0))) {
    // add jobs with arrival times <= current time to ready queue
    while ((nextInput < workArray.length) && (workArray(nextInput).arrival <= time)) {
      readyQueue.enqueue(nextInput)
      nextInput += 1
    }
    if (verbose) {
      println("At time %d, ready queue %s".format(
        time, listOfIDs(readyQueue.toList, workArray)))
    }
    // if there's anything ready, schedule it
    if (readyQueue.length > 0) {
      val jobIndex = readyQueue.dequeue
      val job = workArray(jobIndex)
      if (verbose) {
        println("starting job %s".format(job.jobID))
      }
      job.start = time
      time += job.runtime
      job.end = time
    }
  }
  workArray.foreach(j => { j.turnaround = j.end - j.arrival } )
  true
}

// Shortest job first.

def scheduleSJF(workArray:Array[JobInfo], unused:Option[Int]) : Boolean = {
  // FIXME your code goes here
  /*
  if (verbose) {
    println("\nStarting SJF scheduling")
  }
  */
  false // FIXME
}

// Shortest remaining time next (SJF with preemption).

def scheduleSRTN(workArray:Array[JobInfo], unused:Option[Int]) : Boolean = {
  // FIXME your code goes here (optional)
  /*
  if (verbose) {
    println("\nStarting SRTN scheduling (SJF with preemption)")
  }
  */
  false // FIXME
}

// Priority (no preemption).

def schedulePriority(workArray:Array[JobInfo], unused:Option[Int]) : Boolean = {
  // FIXME your code goes here
  /*
  if (verbose) {
    println("\nStarting Priority scheduling (no preemption)")
  }
  */
  false // FIXME
}


// Priority with preemption.

def schedulePriorityPreemption(workArray:Array[JobInfo], unused:Option[Int]) : Boolean = {
  // FIXME your code goes here (optional)
  /*
  if (verbose) {
    println("\nStarting Priority scheduling with preemption")
  }
  */
  false // FIXME
}

// Round robin.

def scheduleRR(workArray:Array[JobInfo], timeQuantum:Option[Int]) : Boolean = {
  // FIXME your code goes here 
  /*
  if (verbose) {
    println("\nStarting round robin scheduling (time quantum %d)".format(
      timeQuantum.get"))
  }
  */
  false // FIXME
}

// ---- Support functions ----

def listOfIDs(readyQueueList:Seq[Int], workArray:Array[JobInfo]) : String = {
  readyQueueList.map(i => workArray(i).jobID).mkString(",")
}