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);
};