import { isNully, sort } from 'utils/Common';
import Thrift, { UPDATE_ACTION } from 'utils/Thrift';

export function isSuccessfulUpdate(update) {
  return update.update.summary.state.status === JobUpdateStatus.ROLLED_FORWARD;
}

export function isInProgressUpdate(update) {
  return update.update.summary.state.status === JobUpdateStatus.ROLLING_FORWARD;
}

function processInstanceIdsFromRanges(ranges, fn) {
  ranges.forEach((r) => {
    for (let i = r.first; i <= r.last; i++) {
      fn(i);
    }
  });
}

function getAllInstanceIds(update) {
  const allIds = {};
  const newIds = {};
  const oldIds = {};

  if (!isNully(update.instructions.desiredState)) {
    processInstanceIdsFromRanges(update.instructions.desiredState.instances, (id) => {
      newIds[id] = true;
      allIds[id] = true;
    });
  }

  update.instructions.initialState.forEach((task) => {
    processInstanceIdsFromRanges(task.instances, (id) => {
      oldIds[id] = true;
      allIds[id] = true;
    });
  });
  return { allIds, newIds, oldIds };
}

function getLatestInstanceEvents(instanceEvents, predicate = (e) => true) {
  const events = sort(instanceEvents, (e) => e.timestampMs, true);
  const instanceMap = {};
  events.forEach((e) => {
    if (!instanceMap.hasOwnProperty(e.instanceId) && predicate(e)) {
      instanceMap[e.instanceId] = e;
    }
  });
  return instanceMap;
}

export function instanceSummary(details) {
  const instances = getAllInstanceIds(details.update);
  const latestInstanceEvents = getLatestInstanceEvents(details.instanceEvents);
  const allIds = Object.keys(instances.allIds);

  return allIds.map((i) => {
    // If there is an event, use the event to generate the instance status.
    if (latestInstanceEvents.hasOwnProperty(i)) {
      const latestEvent = latestInstanceEvents[i];
      // If instance has been updated and is in initial state, but not in desired state,
      // then it's a removed instance.
      if (latestEvent.action === JobUpdateAction.INSTANCE_UPDATED &&
          instances.oldIds.hasOwnProperty(i) &&
          !instances.newIds.hasOwnProperty(i)) {
        return {
          instanceId: i,
          className: 'removed',
          title: 'Instance Removed'
        };
      }

      // Normal case - the latest action is the current status
      return {
        instanceId: i,
        className: getClassForUpdateAction(latestEvent.action),
        title: UPDATE_ACTION[latestEvent.action]
      };
    } else {
      // No event, so it's a pending instance.
      return {
        instanceId: i,
        className: 'pending',
        title: 'Pending'
      };
    }
  });
}

function progressFromEvents(instanceEvents) {
  const success = getLatestInstanceEvents(instanceEvents,
    (e) => e.action === JobUpdateAction.INSTANCE_UPDATED);
  return Object.keys(success).length;
}

export function updateStats(details) {
  const allInstances = Object.keys(getAllInstanceIds(details.update).allIds);
  const totalInstancesToBeUpdated = allInstances.length;
  const instancesUpdated = progressFromEvents(details.instanceEvents);
  const progress = Math.round((instancesUpdated / totalInstancesToBeUpdated) * 100);
  return {
    totalInstancesToBeUpdated,
    instancesUpdated,
    progress
  };
}

export function getInProgressStates() {
  return ACTIVE_JOB_UPDATE_STATES;
}

export function getTerminalStates() {
  const active = new Set(ACTIVE_JOB_UPDATE_STATES);
  return Object.values(JobUpdateStatus).filter((k) => !active.has(k));
}

export function getClassForUpdateStatus(status) {
  if (Thrift.OKAY_UPDATE_STATUS.includes(status)) {
    return 'okay';
  } else if (Thrift.WARNING_UPDATE_STATUS.includes(status)) {
    return 'attention';
  } else if (Thrift.ERROR_UPDATE_STATUS.includes(status)) {
    return 'error';
  }
  return 'in-progress';
}

export function getClassForUpdateAction(action) {
  if (Thrift.OKAY_UPDATE_ACTION.includes(action)) {
    return 'okay';
  } else if (Thrift.WARNING_UPDATE_ACTION.includes(action)) {
    return 'attention';
  } else if (Thrift.ERROR_UPDATE_ACTION.includes(action)) {
    return 'error';
  }
  return 'in-progress';
}

export function statusDispatcher(dispatch) {
  return (update) => {
    const status = update.update.summary.state.status;
    if (Thrift.OKAY_UPDATE_STATUS.includes(status)) {
      return dispatch.success(update);
    } else if (Thrift.WARNING_UPDATE_STATUS.includes(status)) {
      return dispatch.warning(update);
    } else if (Thrift.ERROR_UPDATE_STATUS.includes(status)) {
      return dispatch.error(update);
    }
    return dispatch.inProgress(update);
  };
}

export function actionDispatcher(dispatch) {
  return (event) => {
    const action = event.action;
    if (Thrift.OKAY_UPDATE_ACTION.includes(action)) {
      return dispatch.success(event);
    } else if (Thrift.WARNING_UPDATE_ACTION.includes(action)) {
      return dispatch.warning(event);
    } else if (Thrift.ERROR_UPDATE_ACTION.includes(action)) {
      return dispatch.error(event);
    }
    return dispatch.inProgress(event);
  };
}
