import {
  find,
  map,
  propEq,
  reduce,
  range,
  times,
  last, filter, identity
} 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, name: string }>
};

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

export const fillCorrupted = (jobsOperations: JobOperations[], processorsCount) =>
  map(
    ({ operations, ...rest }) => {
      const fixedOperations = [...operations];
      fixedOperations.length = processorsCount;

      return {
        ...rest,
        operations: map(o => o || { t: 0 }, fixedOperations)
      };
    },
    jobsOperations
  );

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

  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 jobsOperationsReordered = map(jobOperationTemplate => {
      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,
      name: jobTime.operations[i].name
    }), schedule)
  , processorsCount);
};