import {
  addIndex,
  findIndex,
  find,
  map,
  propEq,
  reduce,
  sort,
  range,
  concat,
  filter,
  reverse,
  prop,
  flatten,
  uniq,
  reject,
  contains,
  omit,
  equals,
  update,
  times,
  add,
  pluck,
  last, forEach, all
} from 'rambda';
import type { JobId } from '../../types';
import { groupBy, max, min, remove, slice } from 'ramda';
import { updateCTime } from '../mcnaught';

type JobTime = {
  jobId: JobId,
  t: number,
  anc: JobId[]
};

const noSuccJobs = (jobTimes: JobTime[]) => {
  const allAnc = flatten(map(jobTime => jobTime.ignore ? [] : jobTime.anc, jobTimes));
  return reject(jobTime => jobTime.ignore || contains(jobTime.jobId, allAnc), jobTimes);
};

const successors = (id: JobId, jobTimes: JobTime[]) =>
  filter(({ anc }) => contains(id, anc), jobTimes);

export const createSchedule = (jobTimes: JobTime[], m: number) => {
  let T = reduce((acc, jobTime) => acc + jobTime.t, 0, jobTimes);

  let noSucc = noSuccJobs(jobTimes);

  noSucc[0].w = reduce((acc, job) => acc + job.w, 0, noSucc[0].anc) + noSucc[0].t;

  forEach((jobTime: JobTime) => {
    jobTime.wCalc = () =>
      jobTime.t + reduce((acc, aJob) => acc + aJob.wCalc(), 0, successors(jobTime.jobId, jobTimes))
  }, jobTimes);

  forEach((jobTime: JobTime) => {
    jobTime.w = jobTime.wCalc()
  }, jobTimes);

  let c = Math.floor(T / m) + Math.sign(T - m * Math.floor(T / m));
  let assignedIds;
  let jobTimesSorted = sort((a, b) => b.w - a.w, jobTimes);

  while (c <= T) {
    assignedIds = [];
    let activeProcessor = 1;
    // console.log('procesor', activeProcessor);

    while (activeProcessor <= m) {
      let restC = c;
      let lastActiveProcessor = activeProcessor;

      forEach(withMaxW => {
        if (contains(withMaxW.jobId, assignedIds)) {
          // console.log('already assigned', withMaxW.jobId);
          return;
        }

        let wereAllAncAssigned = all(jobId => contains(jobId, assignedIds), withMaxW.anc);

        if (!wereAllAncAssigned) {
          // console.log('nemozem priradit', withMaxW.jobId);
          return;
        }

        if (withMaxW.t <= restC) {
          withMaxW.processor = activeProcessor;
          assignedIds.push(withMaxW.jobId);
          restC -= withMaxW.t;
          // console.log('priradil som', withMaxW.jobId);
        } else {
          // console.log('nezmesti sa', withMaxW.jobId);
        }
      }, jobTimesSorted);

      if (!restC || find(jobTime => jobTime.t <= restC && !contains(jobTime.jobId, assignedIds), jobTimesSorted)) {
        activeProcessor++;
        // console.log('dalsi procesor', activeProcessor);
      }

      if (activeProcessor === lastActiveProcessor) {
        // console.log('zvys takt');
        break;
      }
    }

    if (assignedIds.length === jobTimesSorted.length) {
      break;
    }

    // console.log('dalsi takt', c+1);
    c++;
  }

  if (assignedIds.length !== jobTimesSorted.length) {
    return null;
  }

  return updateCTime(
    map(omit(['wCalc', 'anc', 'w']), jobTimesSorted)
  );
};

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

  const grouped = Object.values(groupBy(jobTime => jobTime.processor, schedule));

  return map(map(job => ({
    processor: job.processor,
    startTime: job.c - job.t,
    endTime: job.c
  })), grouped);
};