import {
  findIndex, map, propEq, reduce,
  reverse, prop, flatten, uniq, reject, contains, omit
} from 'rambda';
import type { JobId } from '../../types';
import { max, remove } from 'ramda';

type JobTime = {
  jobId: JobId,
  name: string,
  t: number,
  d: number,
  w: number,
  anc: JobId[],
};

const findMinJob = (fi: number, jobTimes: JobTime[]) =>
  reduce(
    (minJob: JobTime, jobTime: JobTime) => {
      const minJobU = minJob.w * max(fi - minJob.d, 0);
      const jobTimeU = jobTime.w * max(fi - jobTime.d, 0);

      return minJobU < jobTimeU ? minJob : jobTime;
    },
    { w: Infinity },
    jobTimes
  );

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

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

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

  return cj;
};

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

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

  let fi = reduce((sum, job: JobTime) => sum + job.t, 0, jobs);
  let jobsD = jobs;
  let R = [];

  while (fi > 0) {
    const allAcc = uniq(flatten(
      map(prop('anc'), jobsD)
    ));

    const G = reject((jobTime: JobTime) => contains(jobTime.jobId, allAcc), jobsD);

    if (!G.length) return null;

    const minJob = findMinJob(fi, G);
    const minJobIndex = findIndex(propEq('jobId', minJob.jobId), jobsD);

    jobsD = remove(minJobIndex, 1, jobsD);
    fi -= minJob.t;

    R.push(minJob);
  }

  const withC = updateCTime(reverse(R));

  return map(omit(['w', 'anc']), withC);
};

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

  if (!schedule) return null;

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