import {
  addIndex,
  find,
  map,
  propEq,
  reduce,
  range,
  times,
  last
} from 'rambda';
import type { JobId } from '../../types';
import { createSchedule as johnson, updateCTime } from '../johnson';
import { max } from 'ramda';

type JobOperations = {
  jobId: JobId,
  operations: Array<{ t: number }>
};

type JobOperationsWithC = {
  jobId: JobId,
  operations: Array<{ t: number, c: number }>
};

export const fillCorrupted = (jobsOperations: JobOperations[]) => {
  const maxOperationsCount = reduce((acc, { operations }) => max(operations.length, acc), 0, jobsOperations);

  return map(
    ({ operations, ...rest }) => {
      const fixedOperations = [...operations];
      fixedOperations.length = maxOperationsCount;

      return {
        ...rest,
        operations: fixedOperations.fill({ t: 0 }, operations.length, maxOperationsCount)
      };
    },
    jobsOperations
  )
};

export const createSchedule = (jobsOperations: JobOperations[], processorsCount: number): JobOperationsWithC[] => {
  jobsOperations = fillCorrupted(jobsOperations);

  const allSchedules = map(k => {
    const jobsOperationsForK = map((jobsOperation: JobOperations) => {
      const t1 = reduce((acc, i) => acc + jobsOperation.operations[i].t, 0, range(0, k));
      const t2 = reduce((acc, i) => acc + jobsOperation.operations[i].t, 0, range(processorsCount - k, processorsCount));

      const operations = [
        { t: t1 },
        { t: t2 }
      ];
      const newJobsOperations = { jobId: jobsOperation.jobId, operations };
      return newJobsOperations;
    }, jobsOperations);

    const jobsOperationsOrderTemplate = johnson(jobsOperationsForK);

    const mapWithIndex = addIndex(map);
    const jobsOperationsReordered = mapWithIndex((jobOperationTemplate, i) => {
      const { operations } = find(propEq('jobId', jobOperationTemplate.jobId), jobsOperations);
      return {
        jobId: jobOperationTemplate.jobId,
        operations
      };
    }, jobsOperationsOrderTemplate);

    const jobsOperationsReorderedWithC = updateCTime(jobsOperationsReordered, processorsCount);

    return jobsOperationsReorderedWithC;
  }, range(1, processorsCount));

  const bestSchedule = reduce((acc, schedule) => {
    const cmaxAcc = last(last(acc).operations).c;
    const cmax = last(last(schedule).operations).c;

    return (cmax < cmaxAcc) ? schedule : acc;
  }, [{ operations: [{ c: Infinity }] }], allSchedules);

  return bestSchedule;
};

export default (jobsOperations: JobOperations[], processorsCount: number) => {
  const schedule = createSchedule(jobsOperations, processorsCount);

  return times(i =>
    map((jobTime: JobOperationsWithC) => ({
      processor: i + 1,
      startTime: jobTime.operations[i].c - jobTime.operations[i].t,
      endTime: jobTime.operations[i].c,
    }), schedule)
  , processorsCount);
};