code: /*
   * Agenda.java
   *
   * Copyright (C) 2020 Vladislav Nikolov Vasilev
   *
   * This program is free software: you can redistribute it and/or modify
   * it under the terms of the GNU General Public License as published by
   * the Free Software Foundation, either version 3 of the License, or
   * (at your option) any later version.
   *
   * This program is distributed in the hope that it will be useful,
   * but WITHOUT ANY WARRANTY; without even the implied warranty of
   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   * GNU General Public License for more details.
   *
   * You should have received a copy of the GNU General Public License
   * along with this program.  If not, see https://www.gnu.org/licenses/gpl-3.0.html.
   */
  
  /**
   * Package that contains the planning agent along with its data structures.
   */
  package controller;
  
  import java.util.Comparator;
  import java.util.LinkedList;
  import java.util.stream.Collectors;
  
  /**
   * Class that represents an agenda like data structure. The agenda is made up
   * of a list of pending goals (those goals that haven't been explored yet),
   * a list of preempted goals (those goals that have been suspended due to a discrepancy)
   * and a list of reached goals (those goals that have been completed). Also, it stores
   * the current goal, which is the one that the agent is trying to achieve at
   * the moment.
   * <p>
   * The lists of pending goals and preempted goals are sorted by priority. See
   * {@link PDDLSingleGoal} to get more information.
   *
   * @author Vladislav Nikolov Vasilev
   */
  public class Agenda {
      private LinkedList<PDDLSingleGoal> pendingGoals;
      private LinkedList<PDDLSingleGoal> preemptedGoals;
      private LinkedList<PDDLSingleGoal> reachedGoals;
      private PDDLSingleGoal currentGoal;
  
      /**
       * Class constructor.
       *
       * @param goals List of goals
       */
      public Agenda(LinkedList<PDDLSingleGoal> goals) {
          this.pendingGoals = this.sortListByPriority(goals);
          this.preemptedGoals = new LinkedList<>();
          this.reachedGoals = new LinkedList<>();
          this.currentGoal = null;
      }
  
      public LinkedList<PDDLSingleGoal> getPendingGoals() {
          return this.pendingGoals;
      }
  
      public LinkedList<PDDLSingleGoal> getPreemptedGoals() {
          return this.preemptedGoals;
      }
  
      public LinkedList<PDDLSingleGoal> getReachedGoals() {
          return this.reachedGoals;
      }
  
      public PDDLSingleGoal getCurrentGoal() {
          return this.currentGoal;
      }
  
      public void setCurrentGoal(PDDLSingleGoal goal) {
          this.currentGoal = goal;
      }
  
      /**
       * Method that sets the current goal. It chooses between the first goal of the pending
       * goals list and the first one of the preempted goals list. The goal's priority is
       * considered when choosing between them.
       *
       * @return Returns true if the goal has been set successfully or false otherwise.
       */
      public boolean setCurrentGoal() {
          // Variable that tells if the goal has been set successfully or not
          boolean setGoal = true;
  
          if (!this.pendingGoals.isEmpty() && this.preemptedGoals.isEmpty()) {
              // If there are only pending goals, choose the first one
              this.currentGoal = this.pendingGoals.removeFirst();
          } else if (this.pendingGoals.isEmpty() && !this.preemptedGoals.isEmpty()) {
              // If there are only preempted goals, choose the first one
              this.currentGoal = this.preemptedGoals.removeFirst();
          } else if (!this.pendingGoals.isEmpty() && !this.preemptedGoals.isEmpty()) {
              // If there are both pending and preempted goals, choose the best one according
              // to their priority. Pending goals are preferred over preempted goals in case
              // their priorities are equal
              PDDLSingleGoal firstPending = this.pendingGoals.getFirst(),
                      firstPreempted = this.preemptedGoals.getFirst();
  
              if (firstPreempted.getPriority() < firstPending.getPriority()) {
                  this.currentGoal = firstPreempted;
                  this.preemptedGoals.removeFirst();
              } else {
                  this.currentGoal = firstPending;
                  this.pendingGoals.removeFirst();
              }
          } else {
              // If both lists are empty, then there's no goal to be set
              setGoal = false;
          }
  
          return setGoal;
      }
  
      /**
       * Method that allows to halt the current goal in case some discrepancy is found.
       */
      public void haltCurrentGoal() {
          // Store the current goal in the preempted goals list
          this.preemptedGoals.addLast(this.currentGoal);
          this.preemptedGoals = this.sortListByPriority(this.preemptedGoals);
  
          this.currentGoal = null;
      }
  
      /**
       * Method that allows to update the reached goals list. It should be called when the
       * current goal has been reached successfully.
       */
      public void updateReachedGoals() {
          // Add the current goal to the reached goals list
          this.reachedGoals.addLast(this.currentGoal);
  
          this.currentGoal = null;
      }
  
      /**
       * Method that allows to check whether a predicate is contained in the pending
       * goals list.
       *
       * @param predicate Predicate to be searched.
       * @return Returns a PDDLSingleGoal instance containing the predicate if it is
       * found and null otherwise.
       */
      public PDDLSingleGoal containedPredicateInPendingGoals(String predicate) {
          return this.containedPredicateInGoalsList(predicate, this.pendingGoals);
      }
  
      /**
       * Method that allows to check whether a predicate is contained in the preempted
       * goals list.
       *
       * @param predicate Predicate to be searched.
       * @return Returns a PDDLSingleGoal instance containing the predicate if it is
       * found and null otherwise.
       */
      public PDDLSingleGoal containedPredicateInPreemptedGoals(String predicate) {
          return this.containedPredicateInGoalsList(predicate, this.preemptedGoals);
      }
  
      /**
       * Method that allows to move a goal instance from the pending goals list
       * to the reached goals list.
       *
       * @param goal PDDLSingleGoal instance to be moved.
       */
      public void setReachedFromPending(PDDLSingleGoal goal) {
          this.setReachedFromList(goal, this.pendingGoals);
      }
  
      /**
       * Method that allows to move a goal instance from the preempted goals list
       * to the reached goals list.
       *
       * @param goal PDDLSingleGoal instance to be moved.
       */
      public void setReachedFromPreempted(PDDLSingleGoal goal) {
          this.setReachedFromList(goal, this.preemptedGoals);
      }
  
      @Override
      public String toString() {
          StringBuilder builder = new StringBuilder();
          builder.append("\n--------------------------------------------------------------------------------\n");
          builder.append("\n-----------------------------------  AGENDA  -----------------------------------\n");
  
          builder.append("\nList of NOT PLANNED goals:");
  
          for (PDDLSingleGoal pending : this.pendingGoals) {
              builder.append(pending.toString());
          }
  
          builder.append("\n\nList of PREEMPTED (halted) goals:");
  
          for (PDDLSingleGoal preempted : this.preemptedGoals) {
              builder.append(preempted.toString());
          }
  
          builder.append("\n\nList of REACHED goals:");
  
          for (PDDLSingleGoal reached : this.reachedGoals) {
              builder.append(reached.toString());
          }
  
          builder.append("\n\nCURRENT goal:");
  
          if (this.currentGoal != null) {
              builder.append(this.currentGoal.toString());
          }
  
          builder.append("\n\n--------------------------------------------------------------------------------\n");
  
          return builder.toString();
      }
  
      /**
       * Method that sorts a list of goals. The list is sorted by priority.
       *
       * @param list LinkedList of goals to be sorted
       * @return Returns the input list sorted by priority.
       */
      private LinkedList<PDDLSingleGoal> sortListByPriority(LinkedList<PDDLSingleGoal> list) {
          return list
                  .stream()
                  .sorted(Comparator.comparingInt(PDDLSingleGoal::getPriority))
                  .collect(Collectors.toCollection(LinkedList::new));
      }
  
      /**
       * Method used to check if a PDDL predicate is contained in some list of the
       * agenda.
       *
       * @param predicate Predicate to be searched in a given list.
       * @param goalsList List of PDDLSingleGoal in which the predicate is searched.
       * @return Returns a PDDLSingleGoal instance containing the predicate if it is
       * found and null otherwise.
       */
      private PDDLSingleGoal containedPredicateInGoalsList(String predicate, LinkedList<PDDLSingleGoal> goalsList) {
          PDDLSingleGoal containedGoal = null;
  
          for (PDDLSingleGoal goal : goalsList) {
              if (goal.getGoalPredicate().equals(predicate)) {
                  containedGoal = goal;
              }
          }
  
          return containedGoal;
      }
  
      /**
       * Method that allows to move a goal instance from a given list to the reached
       * goals list.
       *
       * @param goal      PDDLSingleGoal instance to be moved.
       * @param goalsList List from which the goal will be removed.
       */
      private void setReachedFromList(PDDLSingleGoal goal, LinkedList<PDDLSingleGoal> goalsList) {
          goalsList.remove(goal);
          this.reachedGoals.addLast(goal);
      }
  }
  