import { findIndex, find, map, propEq, reduce, sort, concat } from 'rambda';
import type { JobId } from '../../types';
import { max, remove, slice } from 'ramda';

type JobTime = {
  jobId: JobId,
  t: number,
  d: number,
  name: string
};

const findLateJob = (jobTimes: JobTime[]) =>
  find(({ c, d }: JobTime) => c > d, jobTimes);

const findMaxTJob = (jobTimes: JobTime[]) =>
  reduce(
    (maxTJob: JobTime, jobTime: JobTime) => maxTJob.t > jobTime.t ? maxTJob : jobTime,
    { t: -Infinity },
    jobTimes
  );

const updateCTime = (jobTimes: JobTime[]) => {
  let prev = 0;

  const cj = map((jobTime: JobTime) => {
    prev += jobTime.t;

    return { ...jobTime, c: prev };
  }, jobTimes);

  return cj;
};

export const fillCorrupted = (jobs: JobTime[]) => map(
  ({ d, ...rest }) => ({ ...rest, d: d || 1000 }),
  jobs);

export const createSchedule = (jobs: JobTime[]) => {
  jobs = fillCorrupted(jobs);

  const F = sort((a: JobTime, b: JobTime) => a.d - b.d, jobs);
  const L = [];

  let cj = updateCTime(F);
  let lateJob;

  while (lateJob = findLateJob(cj)) {
    const lateJobIndex = findIndex(propEq('jobId', lateJob.jobId), cj);
    const jobsBeforeLateJob = slice(0, lateJobIndex + 1, cj);

    const maxTJob = findMaxTJob(jobsBeforeLateJob);
    const maxTJobIndex = findIndex(propEq('jobId', maxTJob.jobId), cj);

    cj = remove(maxTJobIndex, 1, cj);
    cj = updateCTime(cj);

    L.push(maxTJob);
  }

  const schedule = concat(cj, L);

  return updateCTime(schedule);
};

export default (jobs: JobTime[]) => {
  const schedule = createSchedule(jobs);

  const normalizedSchedule = map((jobTime: JobTime) => ({
    processor: 1,
    startTime: jobTime.c - jobTime.t,
    endTime: jobTime.c,
    delayed: max(0, jobTime.c - jobTime.d),
    name: jobTime.name
  }), schedule);

  return [normalizedSchedule];
};