import {
  findIndex, map, reduce, sort, concat,
  filter, reverse, equals, update, times
} from 'rambda';
import type { JobId } from '../../types';
import { remove } from 'ramda';
import { fillCorrupted } from '../campbel';

type JobOperations = {
  jobId: JobId,
  operations: [{ t: number, name: string }, { t: number, name: string }]
};

type JobOperationsWithC = {
  jobId: JobId,
  operations: [{ t: number, c: number, name: string }, { t: number, c: number, name: string }]
};

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[] => {
  jobsOperations = fillCorrupted(jobsOperations, 2);

  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,
      name: jobTime.operations[i].name
    }), schedule)
  , 2);
};