import {
  addIndex, findIndex, find, map, propEq, reduce, sort, range, concat,
  filter, reverse, prop, flatten, uniq, reject, contains, omit, equals, update, times
} from 'rambda';
import type { JobId } from '../../types';
import { max, min, remove, slice } from 'ramda';
 
type JobOperations = {
  jobId: JobId,
  operations: Array<{ t: number }>
};
 
type JobOperationsWithC = {
  jobId: JobId,
  operations: Array<{ t: number, c: number }>
};
 
const findMinT = (operations) =>
  reduce(
    (t1, t2) => t1.t < t2.t ? t1 : t2,
    { t: Infinity },
    operations
  );
 
const findMinJob = (jobsOperations: JobOperations[]): JobOperations =>
  reduce(
    (minJob: JobOperations, jobOperations: JobOperations) => {
      const minJobT = findMinT(minJob.operations);
      const jobOperationsT = findMinT(jobOperations.operations);
 
      return minJobT.t < jobOperationsT.t ? minJob : jobOperations;
    },
    { operations: [ { t: Infinity }, { t: Infinity } ] },
    jobsOperations
  );
 
export const updateCTime = (jobsOperations: JobOperations[], processorCount: number): JobOperationsWithC[] => {
  let prev = 0;
  const cj = [];
 
  cj[0] = map((jobTime: JobOperations) => {
    prev += jobTime.operations[0].t;
    const operationC = { ...jobTime.operations[0], c: prev };
    const operations = update(0, operationC, jobTime.operations);
 
    return { ...jobTime, operations };
  }, jobsOperations);
 
  if (processorCount > 1) {
    times(i => {
      const processorI = i + 1;
      prev = cj[0][0].operations[0].c;
 
      cj[processorI] = map((jobTime: JobOperations) => {
        const conflicts = filter(({ c }) => prev < c, jobTime.operations);
        if (conflicts.length) {
          const maxConflict = sort((a, b) => b.c - a.c, conflicts)[0];
          prev = maxConflict.c;
        }
 
        prev += jobTime.operations[processorI].t;
 
        const operationC = { ...jobTime.operations[processorI], c: prev };
        const operations = update(processorI, operationC, jobTime.operations);
 
        return { ...jobTime, operations };
      }, cj[i]);
    }, processorCount - 1);
  }
 
  return cj[processorCount - 1];
};
 
export const createSchedule = (jobsOperations: JobOperations[]): JobOperationsWithC[] => {
  let I = jobsOperations;
  let L = [[], []];
 
  while (I.length) {
    const minTJob = findMinJob(I);
 
    const { t: minT } = findMinT(minTJob.operations);
    const operationIndex = findIndex(o => o.t === minT, minTJob.operations);
    const minTJobIndex = findIndex(equals(minTJob), I);
 
    L[operationIndex].push(minTJob);
    I = remove(minTJobIndex, 1, I);
  }
 
  const LFinal = concat(L[0], reverse(L[1]));
 
  return updateCTime(LFinal, 2);
};
 
export default (jobsOperations: JobOperations[]) => {
  const schedule = createSchedule(jobsOperations);
 
  return times(i =>
    map((jobTime: JobOperationsWithC) => ({
      processor: i + 1,
      startTime: jobTime.operations[i].c - jobTime.operations[i].t,
      endTime: jobTime.operations[i].c,
    }), schedule)
  , 2);