import { map, reduce, times } from 'rambda';
import type { JobId } from '../../types';
import { groupBy, max } from 'ramda';
 
type JobTime = {
  jobId: JobId,
  t: number
};
 
type JobTimeAssigned = JobTime | {
  processor: number,
};
 
type JobTimeAssignedWithC = JobTimeAssigned | {
  c: number,
};
 
export const updateCTime = (jobTimes: JobTimeAssigned[]) => {
  let prev = 0;
  let prevProcessor = 0;
 
  const cj = map((jobTime: JobTimeAssigned) => {
    if (jobTime.processor !== prevProcessor) {
      prev = 0;
    }
 
    prev += jobTime.t;
    prevProcessor = jobTime.processor;
 
    return { ...jobTime, c: prev };
  }, jobTimes);
 
  return cj;
};
 
export const createSchedule = (jobs: JobTime[], processorCount: number): JobTimeAssignedWithC[] => {
  let G = jobs;
  const sumT = reduce((acc, job) => acc + job.t, 0, G);
  const maxT = reduce((acc, job) => max(acc, job.t), -Infinity, G);
  const K = max(Math.ceil(sumT / processorCount), maxT);
  const R = [];
 
  let processorC = times(() => 0, processorCount + 1);
  let activeProcessor = 1;
 
  while (G.length) {
    if (processorC[activeProcessor] + G[0].t <= K) {
      processorC[activeProcessor] += G[0].t;
      R.push({ ...G[0], processor: activeProcessor });
      [, ...G] = G;
 
      if (processorC[activeProcessor] === K) {
        activeProcessor++;
      }
    } else {
      const tDiff = processorC[activeProcessor] + G[0].t - K;
 
      R.push({ ...G[0], t: G[0].t - tDiff, processor: activeProcessor });
      G[0].t = tDiff;
 
      activeProcessor++;
    }
  }
 
  const withC = updateCTime(R);
  return withC;
};
 
export default (jobs: JobTime[], processorCount: number) => {
  const schedule = createSchedule(jobs, processorCount);
 
  const grouped = Object.values(groupBy(jobTime => jobTime.processor, schedule));
 
  return map(map((job: JobTimeAssignedWithC) => ({
    processor: job.processor,
    startTime: job.c - job.t,
    endTime: job.c
  })), grouped);
};