/**
 * Utility variable: 24 hour in milliseconds .
 * @type {number}
 */
export const DAY_TO_MS = 86400000;

/**
 * @description handle events that span over midnight
 * @param {number} start - When does the event start
 * @param {number} end  - When does the event end
 * @returns {number} normalised time
 */
export const normaliseEndTime = (start, end) =>
  end < start ? end + DAY_TO_MS : end;

/**
 * @description Sorts an array of objects by given property
 * @param {array} arr - array to be sorted
 * @param {string} property - property to compare
 * @returns {array} copy of array sorted in ascending order
 */

export const sortArrayByProperty = (arr, property) => {
  return [...arr].sort((a, b) => {
    return a[property] - b[property];
  });
};

/**
 * @description Replaces placeholder variables in string with given data
 * @param {string} str - string to analyse
 * @param {object} values - map of variables: values to use
 * @returns {string} finished string
 */

export const replacePlaceholder = (str, values) => {
  for (let [k, v] of Object.entries(values)) {
    str = str.replace(k, v);
  }
  return str;
};

/**
 * @description Used in roll mode, returns selection variables from array
 * @param {array} arr - event list
 * @param {number} now - time now in millis
 * @returns {object} object with selection variables
 */

export const getSelectionByRoll = (arr, now) => {
  // Events now
  let nowIndex = null; // index of event now
  let nowId = null; // id of event now
  let publicIndex = null; // index of public event now
  let publicTime = -1; // counter:

  // Events next
  let nextIndex = null; // index of next event
  let publicNextIndex = null; // index of next public event
  let timeToNext = null; // counter: time for next event
  let publicTimeToNext = null; // counter: time for next public event

  // current timer
  let timers = null;

  // Order events by startTime
  const orderedEvents = sortArrayByProperty(arr, 'timeStart');

  // flags: select first event if several overlapping
  let nowFound = false;

  // exit early if we are past the events
  const lastEvent = orderedEvents[orderedEvents.length - 1];
  const lastNormalEnd = normaliseEndTime(
    lastEvent.timeStart,
    lastEvent.timeEnd
  );
  if (now > lastNormalEnd) {
    return {
      nowIndex,
      nowId,
      publicIndex,
      nextIndex,
      publicNextIndex,
      timers,
      timeToNext,
    };
  }

  // loop through events, look for where we should be
  for (const e of orderedEvents) {
    // When does the event end (handle midnight)
    const normalEnd = normaliseEndTime(e.timeStart, e.timeEnd);

    if (normalEnd <= now) {
      // event ran already

      // public event might not be the one running
      if (e.isPublic && normalEnd > publicTime) {
        publicTime = normalEnd;
        publicIndex = arr.findIndex((a) => a.id === e.id);
      }
    } else if (normalEnd > now && now >= e.timeStart && !nowFound) {
      // event is running

      // it could also be public
      if (e.isPublic) {
        publicTime = normalEnd;
        publicIndex = arr.findIndex((a) => a.id === e.id);
      }

      nowIndex = arr.findIndex((a) => a.id === e.id);
      nowId = e.id;

      // set timers
      timers = {
        _startedAt: e.timeStart,
        _finishAt: e.timeEnd,
        duration: normalEnd - e.timeStart,
        current: normalEnd - now,
      };
      nowFound = true;
    } else if (normalEnd > now) {
      // event will run

      // no need to look after found first
      if (nextIndex !== null && publicNextIndex !== null) continue;

      // look for next events
      // check how far the start is from now
      const wait = e.timeStart - now;

      if (nextIndex == null || wait < timeToNext) {
        timeToNext = wait;
        nextIndex = arr.findIndex((a) => a.id === e.id);
      }
      if ((publicNextIndex === null || wait < publicTimeToNext) && e.isPublic) {
        publicTimeToNext = wait;
        publicNextIndex = arr.findIndex((a) => a.id === e.id);
      }
    }
  }
  return {
    nowIndex,
    nowId,
    publicIndex,
    nextIndex,
    publicNextIndex,
    timers,
    timeToNext,
  };
};

/**
 * @description Implements update functions for roll mode
 * @param {object} currentTimers
 * @param {object} currentTimers.selectedEventId - Id of currently selected event
 * @param {object} currentTimers.current - Running timer
 * @param {object} currentTimers._finishAt - Expected finish time
 * @param {object} currentTimers.clock - time now
 * @param {object} currentTimers.secondaryTimer - secondary timer
 * @param {object} currentTimers._secondaryTarget - finish time of secondary timer
 * @returns {object} object with selection variables
 */
export const updateRoll = (currentTimers) => {
  const {
    selectedEventId,
    current,
    _finishAt,
    clock,
    secondaryTimer,
    _secondaryTarget,
  } = currentTimers;

  // timers
  let updatedTimer = current;
  let updatedSecondaryTimer = secondaryTimer;
  // whether rollLoad should be called
  let doRollLoad = false;
  // whether runCycle should be called
  let isFinished = false;

  if (selectedEventId && current >= 0) {
    // if we have something selected and a timer, we are running
    // this is true because roll never goes into negative times

    // update timer
    updatedTimer = _finishAt - clock;
    if (updatedTimer < 0) {
      isFinished = true;
    }
  } else if (secondaryTimer >= 0) {
    // if secondaryTimer is running we are in waiting to roll

    // update secondary
    updatedSecondaryTimer = _secondaryTarget - clock;
  }

  // if nothing is running, we need to find out if
  // a) we just finished an event (finished was set to true)
  // b) we need to look for events
  // this could be caused by a secondary timer or event finished
  const secondaryRunning =
    updatedSecondaryTimer <= 0 && updatedSecondaryTimer != null;

  if (isFinished || secondaryRunning) {
    // look for events
    doRollLoad = true;
  }

  return { updatedTimer, updatedSecondaryTimer, doRollLoad, isFinished };
};
