/**
  * Return the sum of the integers in the list
  *
  * @param integerMap - The map of integers
  * @return - the sum of integers in the list
  */
 private int sumOf(Map<Integer, Integer> integerList) {
   int sum = 0; // The sum
   for (Integer current : integerList.keySet()) {
     sum += integerList.get(current);
   }
   return sum;
 }
  /**
   * Find out the maximum value for a given map of actions
   *
   * @param actions - The map of actions to their values
   * @return Return the action with the largest value out of all the actions
   */
  private Action maxArg(Map<Action, Double> actions) {
    Action largestValue = null; // The largest value

    for (Action current : actions.keySet()) {
      if (largestValue == null || (actions.get(largestValue) < actions.get(current))) {
        largestValue = current;
      }
    }

    return largestValue;
  }
  /**
   * Make a copy of a policy
   *
   * @param Map<State, Action> newPolicy - The new policy
   */
  private void copyPolicy(Map<State, Action> newPolicy) {
    /* Containers for clones */
    State current;
    Action currentAction;

    this.policy = new HashMap<State, Action>();

    for (State newState : newPolicy.keySet()) {
      current = new State(newState.getState());
      current.setCost(newState.getTemporaryCost());
      currentAction = new Action(newPolicy.get(newState).getPurchases());
      this.policy.put(current, currentAction);
    }
  }
  /**
   * Transition function. Currently, we only get the list of probabilities of each item.
   *
   * @param current - The current state
   * @param action - The action
   * @param possible - The possible state
   * @return The list of probabilities where each index refers to the probability of that item
   */
  private List<Double> transition(State current, Action action, State possible) {
    List<Double> probs = new ArrayList<Double>(); // Probabilities
    Map<Integer, Integer> currentStock, purchase, possibleStock; // Maps
    double currentProb; // The current probability
    Matrix currentMatrix; // The current probability matrix
    int row, column; // The row and column

    for (int i = 0; i < current.getState().size(); ++i) {
      currentProb = 0.0;
      currentStock = current.getState();
      purchase = action.getPurchases();
      possibleStock = possible.getState();
      row = currentStock.get(i) + purchase.get(i);
      column = row - possible.getState().get(i);
      currentMatrix = this.probabilities.get(i);
      if (column < 0
          || column >= currentMatrix.getNumCols()
          || row >= currentMatrix.getNumRows()) { // Invalid state
        probs.add(0.0);
        continue;
      }
      if (possibleStock.get(i) > 0
          || (possibleStock.get(i) == 0 && column == 0)) { // Sufficiently provided
        currentProb = currentMatrix.get(row, column);
      } else if (possibleStock.get(i) == 0 && column > 0) {
        // Range of probabilities because user could have eaten plenty
        for (int j = column; j < currentMatrix.getNumCols(); ++j) {
          currentProb += currentMatrix.get(row, j);
        }
      }
      probs.add(currentProb);
    }
    return probs;
  }
  /** Generate all possible actions */
  private void generateAllPossibleActions() {
    double startTime, endTime; // Timer for generating states
    Map<Integer, Integer> action; // Action
    int maxSum = this.fridge.getMaxPurchase(); // The maximum amount we can purchase
    int maxIndex = this.fridge.getMaxTypes() - 1; // The maximum index of an action

    System.out.println("Generating all possible actions");
    startTime = endTime = Global.currentTime(); /* Set the time */
    action = new HashMap<Integer, Integer>();

    /* Generate zero case */
    for (int i = 0; i < (maxIndex + 1); ++i) {
      action.put(i, 0);
    }
    this.possibleActions.add(new Action(action));
    System.out.println("Size of action: " + action.size());

    while ((endTime - startTime) < this.MAX_GENERATION_TIME) {
      if (this.fridge.getMaxTypes() <= 0) {
        System.err.println("Invalid number of item types");
        System.exit(10);
      } else if (this.fridge.getMaxTypes() == 1) { // Only have 1 type of item
        break;
      } else { // Fridge has 2 or more types of items
        for (int i = maxSum; i >= 0; --i) {
          for (int j = 0; j < (maxIndex + 1); ++j) {
            recursiveGeneration(1, maxSum, i, j, action, this.fridge.getMaxPurchase());
            for (int k = 0; k < (maxIndex + 1); ++k) { // Reset list
              action.put(k, 0);
            }
          }
          endTime = Global.currentTime();
          if ((endTime - startTime) >= this.MAX_GENERATION_TIME) {
            break;
          }
        }
      }
      endTime = Global.currentTime(); // Get current time
      this.timeRemaining -= (endTime - startTime);
      System.out.println("Time remaining: " + this.timeRemaining);
      break;
    }
  }
  public List<Integer> generateShoppingList(List<Integer> inventory, int numWeeksLeft) {
    State current; // current state
    Map<Integer, Integer> purchases = new HashMap<Integer, Integer>(); // Purchases
    Map<Integer, Integer> map = new HashMap<Integer, Integer>(); // Mapping of integers
    List<Integer> shopping = new ArrayList<Integer>(); // Shopping items

    for (int i = 0; i < inventory.size(); ++i) {
      map.put(i, inventory.get(i));
    }

    current = new State(map);
    purchases = this.policy.get(current).getPurchases();

    for (int i = 0; i < inventory.size(); ++i) {
      shopping.add(purchases.get(i));
    }

    return shopping;
  }
  /** Generate all the possible states */
  private void generateAllPossibleStates() {
    double startTime, endTime; // Timer for generating states
    Map<Integer, Integer> state; // Action
    int maxSum = this.fridge.getCapacity(); // The maximum amount we can have in a state
    int maxIndex = this.fridge.getMaxTypes() - 1; // The maximum index of a state

    System.out.println("Generating all possible states");
    startTime = endTime = Global.currentTime(); /* Set the time */
    state = new HashMap<Integer, Integer>();

    /* Generate zero case */
    for (int i = 0; i < (maxIndex + 1); ++i) {
      state.put(i, 0);
    }
    this.possibleStates.add(new State(state));
    System.out.println("Size of state: " + state.size());

    while ((endTime - startTime) < this.MAX_GENERATION_TIME) {
      if (this.fridge.getMaxTypes() <= 0) {
        System.exit(10);
      } else if (this.fridge.getMaxTypes() == 1) { // Only have 1 type of item
        break;
      } else { // Fridge has 2 or more types of items
        for (int i = maxSum; i >= 0; --i) {
          for (int j = 0; j < (maxIndex + 1); ++j) {
            recursiveGeneration(0, maxSum, i, j, state, this.fridge.getMaxItemsPerType());
            for (int k = 0; k < (maxIndex + 1); ++k) { // Reset list
              state.put(k, 0);
            }
          }
          endTime = Global.currentTime();
          if ((endTime - startTime) >= this.MAX_GENERATION_TIME) {
            break;
          }
        }
      }
      endTime = Global.currentTime(); // Get current time
      this.timeRemaining -= (endTime - startTime);
      System.out.println("Time remaining: " + this.timeRemaining);
      break;
    }
  }
  /**
   * Recursively generate list based on given values
   *
   * @param int generateType - The type we are currently generating
   * @param int maxSum - The maximum sum
   * @param int remaining - The remaining value
   * @param int currentIndex - The current index we are looking at
   * @param Map<Integer, Integer> currentMap - Current map being modified
   * @param int maxVal - The maximum value
   */
  private void recursiveGeneration(
      int generateType,
      int maxSum,
      int remaining,
      int currentIndex,
      Map<Integer, Integer> currentMap,
      int maxVal) {
    int localCurrentIndex = currentIndex; // The current index

    if (remaining < 0 || currentIndex > (this.fridge.getMaxTypes() - 1)) {
      addToList(generateType, currentMap);
      return;
    } else {
      for (int i = remaining; i >= 0; --i) {
        if (i >= maxVal) { // Too much remaining
          currentMap.put(currentIndex, maxVal);
          addToList(generateType, currentMap);
          recursiveGeneration(
              generateType,
              maxSum - maxVal,
              maxSum - maxVal,
              localCurrentIndex + 1,
              currentMap,
              maxVal);
        } else if (i == 0 && maxSum == 0) { // We are done
          clearFromCurrent(currentIndex, currentMap);
          addToList(generateType, currentMap);
          return;
        } else { // Just the right amount
          currentMap.put(currentIndex, i);
          addToList(generateType, currentMap);
          recursiveGeneration(
              generateType, maxSum - i, maxSum - i, localCurrentIndex + 1, currentMap, maxVal);
        }
      }
    }
  }
  /** Generate optimal policy */
  private void generateOptimalPolicy() {
    double startTime, currentTime; // Timer things
    boolean alreadyRunThrough = false; // Record if we've already done one run
    Set<State> toLookup; // The states to lookup
    Map<Action, Double> differentValues =
        new HashMap<Action, Double>(); // The different values for the current state
    Map<State, Action> newPolicy = new HashMap<State, Action>(); // New policy
    State newState; // New state for new policy
    Action best; // The best action

    startTime = Global.currentTime();
    currentTime = Global.currentTime();
    while ((currentTime - startTime) <= this.timeRemaining) {
      if (alreadyRunThrough) {
        toLookup = this.policy.keySet();
      } else {
        toLookup = this.possibleStates;
      }
      for (State currentState : toLookup) {
        differentValues.clear(); // Reset different values
        for (Action currentAction : this.possibleActions) {
          if (!validAction(currentAction, currentState)) {
            continue;
          }
          differentValues.put(
              currentAction, valueGeneration(currentState, currentAction, toLookup));
        }
        best = maxArg(differentValues);
        if (isBetterPolicy(currentState, differentValues.get(best))) {
          newState = new State(currentState.getState());
          newState.setTemporaryCost(differentValues.get(best));
          newPolicy.put(newState, best);
        }
      }
      if (CheckDifference(0.1, newPolicy)) {
        copyPolicy(newPolicy);
        newPolicy.clear();
        alreadyRunThrough = true;
      } else {
        currentTime = Global.currentTime();
        break;
      }
    }
  }
  /**
   * Checks if the difference between the values of the previous policy and the new policy are <=
   * val.
   *
   * <p>Also reassigns the old policy
   *
   * @param double val - The difference
   * @param Map<State, Action> newPolicy - The new policy
   * @return true if the minimum difference > val. false otherwise
   */
  private boolean CheckDifference(double val, Map<State, Action> newPolicy) {
    Double currDiff, minDiff = null; // The minimum and current difference
    if (this.policy.size() != this.possibleStates.size()) { // Previous policy wasn't assigned yet
      return true;
    }

    for (State current : newPolicy.keySet()) {
      for (State old : this.policy.keySet()) {
        if (current.equals(old)) {
          currDiff = Math.abs(current.getTemporaryCost() - old.getCost());
          if (minDiff == null || currDiff <= minDiff) {
            minDiff = currDiff;
          }
          break;
        }
      }
    }

    if (minDiff > val) {
      return true;
    }

    return false;
  }
 /**
  * Set to 0 everything from index current to size of map
  *
  * @param int current - The current index
  * @param Map<Integer, Integer> mapToClear - The map to clear
  */
 private void clearFromCurrent(int current, Map<Integer, Integer> mapToClear) {
   for (int i = current; i < mapToClear.size(); ++i) {
     mapToClear.put(i, 0);
   }
 }