wongjiahau/ttap-web

View on GitHub
src/ts/redux/actions/toggleSubjectSelection.ts

Summary

Maintainability
C
1 day
Test Coverage
import { ObjectStore } from "./../../dataStructure/objectStore";
import { GetSemStartDateDialog } from "./../../react/getSemStartDateDialog";
const includes = require("lodash.includes");

import { IStringDicionary } from "../../interfaces/dictionary";
import { RawSlot } from "../../model/rawSlot";
import { ClashReport, Subject } from "../../model/subject";
import { Timetable } from "../../model/timetable";
import {
  FindTimetableVisualizer,
  NullFindTimetableVisualizer,
} from "../../permutator/findTimetableVisualizer";
import { GroupSimilarTimetables } from "../../permutator/groupSimilarTimetables";
import { BeautifySubjectName } from "../../util/beautifySubjectName";
import { NewTimetableListState } from "../reducers/timetableListState";
import {
  IMasterState,
  MasterStateAction,
  MasterStateReducer,
} from "./../reducers/masterState";
import { ToggleSubjectListViewingOptions } from "./toggleSubjectListViewingOption";

let CurrentTimetableFinder: (rawSlots: RawSlot[]) => Timetable[];
let RawSlotStore: ObjectStore<RawSlot>;

export class ToggleSubjectSelection extends MasterStateAction {
  public constructor(private subjectIndex: number) {
    super();
  }
  public TypeName(): string {
    return "toggle subject selection";
  }
  protected GenerateNewState(state: IMasterState): IMasterState {
    const visualizer = state.AlgorithmVisualizerState.isEnabled
      ? new FindTimetableVisualizer()
      : new NullFindTimetableVisualizer();

    CurrentTimetableFinder = (x) =>
      state.SettingsState.TimetableFinder(
        x,
        state.SettingsState.DisableClashChecking,
        visualizer
      );

    RawSlotStore = state.DataState.RawSlotDataRouter.GetCurrentData();
    const newSubjects = state.SubjectListState.Subjects.map((x) => ({
      ...x,
    }));
    const targetSubject = newSubjects[this.subjectIndex];
    if (targetSubject.ClashReport !== null) {
      return state;
    } else {
      const result = targetSubject.IsSelected
        ? DeselectSubject(targetSubject, newSubjects, state)
        : SelectSubject(targetSubject, newSubjects, state);
      if (state.AlgorithmVisualizerState.isEnabled) {
        visualizer.animate();
        return {
          ...result,
          AlgorithmVisualizerState: {
            ...state.AlgorithmVisualizerState,
            searchedPathCount: visualizer.getSearchedPathCount(),
            fullSearchPathCount: visualizer.getFullSearchPathCount(),
            timeTaken: visualizer.getTimeTakenInMillisecond(),
            clearAnimation: visualizer.clearPreviousAnimation,
          },
        };
      } else {
        return result;
      }
    }
  }
}

export function SelectSubject(
  subjectToBeSelected: Subject,
  allSubjects: Subject[],
  state: IMasterState
): IMasterState {
  const selectedSubjects = allSubjects.filter((x) => x.IsSelected);
  const clashReport = CheckForClashesBetween(
    subjectToBeSelected,
    selectedSubjects
  );
  const result = {
    ...state,
    SubjectListState: {
      ...state.SubjectListState,
      Subjects: allSubjects,
    },
  };
  if (clashReport) {
    subjectToBeSelected.ClashReport = clashReport;
    return result;
  }
  const selectedSlots = GetSelectedSlots(
    selectedSubjects.concat([subjectToBeSelected])
  );
  const timetables = CurrentTimetableFinder(selectedSlots);
  if (timetables.length === 0) {
    subjectToBeSelected.ClashReport = new ClashReport("group");
    return result;
  }
  subjectToBeSelected.IsSelected = true;
  return {
    ...result,
    TimetableListState: NewTimetableListState(
      GroupSimilarTimetables(timetables),
      selectedSlots
    ),
  };
}

export function DeselectSubject(
  subjectToBeDeselected: Subject,
  allSubjects: Subject[],
  state: IMasterState
): IMasterState {
  subjectToBeDeselected.IsSelected = false;
  const selectedSubjects = allSubjects.filter((x) => x.IsSelected);
  ReleaseDisabledSubjectsIfPossible(selectedSubjects, allSubjects);
  const selectedSlots = GetSelectedSlots(selectedSubjects);
  const timetables =
    selectedSubjects.length > 0 ? CurrentTimetableFinder(selectedSlots) : [];
  const result: IMasterState = {
    ...state,
    SubjectListState: {
      ...state.SubjectListState,
      Subjects: allSubjects,
    },
    TimetableListState: NewTimetableListState(
      GroupSimilarTimetables(timetables),
      selectedSlots
    ),
  };

  const allSubjectIsDeselected = allSubjects.every((x) => !x.IsSelected);
  const newIsShowSelectedSubjectOnly =
    state.SubjectListState.IsShowingSelectedSubjectOnly &&
    !allSubjectIsDeselected;
  const shouldToggleToShowAllSubject =
    state.SubjectListState.IsShowingSelectedSubjectOnly &&
    newIsShowSelectedSubjectOnly === false;
  if (shouldToggleToShowAllSubject) {
    return MasterStateReducer(result, new ToggleSubjectListViewingOptions());
  } else {
    return result;
  }
}

export function ReleaseDisabledSubjectsIfPossible(
  selectedSubjects: Subject[],
  allSubjects: Subject[]
): void {
  const disabledSubjects = allSubjects.filter((s) => s.ClashReport !== null);
  for (let i = 0; i < disabledSubjects.length; i++) {
    const s = disabledSubjects[i];
    if (s.ClashReport === null) {
      continue;
    }
    switch (s.ClashReport.Type) {
      case "single":
        let stillGotClashes = false;
        for (let j = 0; j < selectedSubjects.length; j++) {
          if (includes(selectedSubjects[j].ClashingCounterparts, s.Code)) {
            s.ClashReport = new ClashReport(
              "single",
              BeautifySubjectName(selectedSubjects[j].Name)
            );
            stillGotClashes = true;
            break;
          }
        }
        if (!stillGotClashes) {
          s.ClashReport = null;
        }
        break;
      case "group":
        if (
          CurrentTimetableFinder(GetSelectedSlots(selectedSubjects.concat([s])))
            .length > 0
        ) {
          s.ClashReport = null;
        }
        break;
    }
  }
}

export function CheckForClashesBetween(
  s: Subject,
  subjects: Subject[]
): ClashReport | null {
  for (let i = 0; i < subjects.length; i++) {
    for (let j = 0; j < subjects[i].ClashingCounterparts.length; j++) {
      if (s.Code === subjects[i].ClashingCounterparts[j]) {
        return new ClashReport("single", BeautifySubjectName(subjects[i].Name));
      }
    }
  }
  return null;
}

export function GetSelectedSlots(subjects: Subject[]): RawSlot[] {
  if (subjects.length === 0) {
    return [];
  }
  let slotIds: number[] = [];
  for (let i = 0; i < subjects.length; i++) {
    slotIds = slotIds.concat(subjects[i].SlotUids);
  }
  const result = RawSlotStore.GetBunch(slotIds);
  return result;
}