import {
  AddToIndexAction,
  IndexAction,
  IndexActionTypes,
  RemoveFromIndexBySubstringAction,
  RemoveFromIndexByValueAction
} from './index.actions';

/**
 * An enum containing all index names
 */
export enum IndexName {
  // Contains all objects in the object cache indexed by UUID
  OBJECT = 'object/uuid-to-self-link',

  // contains all requests in the request cache indexed by UUID
  REQUEST = 'get-request/href-to-uuid',

  /**
   * Contains the UUIDs of requests that were sent to the server and
   * have their responses cached, indexed by the UUIDs of requests that
   * weren't sent because the response they requested was already cached
   */
  UUID_MAPPING = 'get-request/configured-to-cache-uuid'
}

/**
 * The state of a single index
 */
export interface IndexState {
  [key: string]: string
}

/**
 * The state that contains all indices
 */
export type MetaIndexState = {
  [name in IndexName]: IndexState
}

// Object.create(null) ensures the object has no default js properties (e.g. `__proto__`)
const initialState: MetaIndexState = Object.create(null);

/**
 * The Index Reducer
 *
 * @param state
 *    the current state
 * @param action
 *    the action to perform on the state
 * @return MetaIndexState
 *    the new state
 */
export function indexReducer(state = initialState, action: IndexAction): MetaIndexState {
  switch (action.type) {

    case IndexActionTypes.ADD: {
      return addToIndex(state, action as AddToIndexAction);
    }

    case IndexActionTypes.REMOVE_BY_VALUE: {
      return removeFromIndexByValue(state, action as RemoveFromIndexByValueAction)
    }

    case IndexActionTypes.REMOVE_BY_SUBSTRING: {
      return removeFromIndexBySubstring(state, action as RemoveFromIndexBySubstringAction)
    }

    default: {
      return state;
    }
  }
}

/**
 * Add an entry to a given index
 *
 * @param state
 *    The MetaIndexState that contains all indices
 * @param action
 *    The AddToIndexAction containing the value to add, and the index to add it to
 * @return MetaIndexState
 *    the new state
 */
function addToIndex(state: MetaIndexState, action: AddToIndexAction): MetaIndexState {
  const subState = state[action.payload.name];
  const newSubState = Object.assign({}, subState, {
    [action.payload.key]: action.payload.value
  });
  const obs = Object.assign({}, state, {
    [action.payload.name]: newSubState
  });
  return obs;
}

/**
 * Remove a entries that contain a given value from a given index
 *
 * @param state
 *    The MetaIndexState that contains all indices
 * @param action
 *    The RemoveFromIndexByValueAction containing the value to remove, and the index to remove it from
 * @return MetaIndexState
 *    the new state
 */
function removeFromIndexByValue(state: MetaIndexState, action: RemoveFromIndexByValueAction): MetaIndexState {
  const subState = state[action.payload.name];
  const newSubState = Object.create(null);
  for (const value in subState) {
    if (subState[value] !== action.payload.value) {
      newSubState[value] = subState[value];
    }
  }
  return Object.assign({}, state, {
    [action.payload.name]: newSubState
  });
}

/**
 * Remove entries that contain a given substring from a given index
 *
 * @param state
 *    The MetaIndexState that contains all indices
 * @param action
 *    The RemoveFromIndexByValueAction the substring to remove, and the index to remove it from
 * @return MetaIndexState
 *    the new state
 */
function removeFromIndexBySubstring(state: MetaIndexState, action: RemoveFromIndexByValueAction | RemoveFromIndexBySubstringAction): MetaIndexState {
  const subState = state[action.payload.name];
  const newSubState = Object.create(null);
  for (const value in subState) {
    if (value.indexOf(action.payload.value) < 0) {
      newSubState[value] = subState[value];
    }
  }
  return Object.assign({}, state, {
    [action.payload.name]: newSubState
  });
}
