PantryController.java

package stud.ntnu.idatt1005.pantrypal.controllers;

import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import stud.ntnu.idatt1005.pantrypal.PantryPal;
import stud.ntnu.idatt1005.pantrypal.enums.ButtonEnum;
import stud.ntnu.idatt1005.pantrypal.enums.Route;
import stud.ntnu.idatt1005.pantrypal.models.Grocery;
import stud.ntnu.idatt1005.pantrypal.models.Shelf;
import stud.ntnu.idatt1005.pantrypal.registers.GroceryRegister;
import stud.ntnu.idatt1005.pantrypal.registers.ShelfRegister;
import stud.ntnu.idatt1005.pantrypal.utils.SQL;
import stud.ntnu.idatt1005.pantrypal.utils.ViewManager;
import stud.ntnu.idatt1005.pantrypal.views.PantryView;

/**
 * Controller for the PantryView.
 * This class is responsible for handling the logic for the PantryView.
 * It is responsible for adding, removing and editing shelves and groceries.
 */
public class PantryController extends Controller implements Observer {

  /**
   * The view for the PantryController.
   */
  private final PantryView view;

  private final ShelfRegister register;

  private int shelfCount = 0;

  /**
   * Constructor for the PantryController.
   *
   * @param viewManager The view manager for the application.
   */
  public PantryController(ViewManager viewManager) {
    super(viewManager);
    this.view = new PantryView(this);
    this.view.addObserver(this);
    this.viewManager.addView(Route.PANTRY, this.view);
    this.register = new ShelfRegister();

    if (this.isLoggedIn()) {
      load(PantryPal.userName);
    }

    rerender();
  }

  /**
   * Loads all shelves and groceries from the database for the specified user.
   *
   * @param username the username of the user to load shelves for.
   */
  private void load(String username) {
    String shelfQuery = "SELECT * FROM pantry_shelf WHERE user_name = ?";
    List<Map<String, Object>> shelves = SQL.executeQuery(shelfQuery, username);

    for (Map<String, Object> shelf : shelves) {
      int shelfId = (int) shelf.get("id");
      String shelfKey = String.valueOf(shelfId);
      String shelfName = shelf.get("name").toString();

      Shelf s = new Shelf(shelfKey, shelfName);

      String groceryQuery = "SELECT g.*, psg.quantity AS quantity FROM pantry_shelf_grocery psg "
          + "INNER JOIN grocery g ON g.name = psg.grocery_name "
          + "WHERE psg.pantry_shelf_id = ?";
      List<Map<String, Object>> groceries = SQL.executeQuery(groceryQuery, shelfId);

      for (Map<String, Object> grocery : groceries) {

        String groceryName = grocery.get("name").toString();
        int groceryQuantity = (int) grocery.get("quantity");
        String groceryUnit = grocery.get("unit").toString();


        Grocery g = new Grocery(groceryName, groceryQuantity, groceryUnit, shelfName, false);
        s.addGrocery(g);
      }
      this.register.addShelf(s);
    }
  }

  /**
   * Returns the register in the controller. In this case, the register is a ShelfRegister.
   *
   * @return the register in the controller
   */
  public ShelfRegister getRegister() {
    return register;
  }

  /**
   * Returns all shelves in the register.
   *
   * @return an array of Shelf objects
   */
  public Shelf[] getShelves() {
    Collection<Shelf> shelves = register.getRegister().values();

    return shelves.toArray(new Shelf[0]);
  }

  /**
   * Updates the observer based on the button pressed and the grocery item associated with the
   * action.
   * If the button pressed is ADD, the grocery item is added to the register and the view is
   * re-rendered.
   * If the button pressed is REMOVE, the grocery item is removed from the register and
   * the view is re-rendered.
   * If the object is not of type Grocery, an IllegalArgumentException is thrown.
   *
   * @param buttonEnum the button that was pressed
   * @param object     the grocery item associated with the action
   * @throws IllegalArgumentException if the object is not of type Grocery
   */
  @Override
  public void update(ButtonEnum buttonEnum, Object object) {
    if (!(object instanceof Grocery grocery)) {
      throw new IllegalArgumentException("Object is not of type Grocery");
    }
    switch (buttonEnum) {
      case ADD:
        try {
          addGrocery(grocery.getShelf(), grocery.getName(), grocery.getQuantity());
          rerender();
          break;
        } catch (IllegalArgumentException e) {
          break;
        }
      case REMOVE:
        try {
          Shelf shelf = register.getShelfByName(grocery.getShelf());
          deleteGrocery(shelf, grocery);
          rerender();
          break;
        } catch (IllegalArgumentException e) {
          break;
        }
      default:
        break;
    }
  }

  /**
   * Updates the observer based on the button pressed.
   * If the button pressed is ADD_TO_PANTRY, the view is re-rendered.
   *
   * @param buttonEnum the button that was pressed
   */
  @Override
  public void update(ButtonEnum buttonEnum) {
    if (Objects.requireNonNull(buttonEnum) == ButtonEnum.ADD_TO_PANTRY) {
      rerender();
    } else {
      throw new IllegalArgumentException("Button not supported by class");
    }
  }

  /**
   * Adds a shelf to the register.
   */
  public void addShelf() {
    shelfCount++;
    String newShelf = "New Shelf " + shelfCount;

    if (isLoggedIn()) {
      String query = "INSERT INTO pantry_shelf (name, user_name) VALUES (?, ?)";
      int id = SQL.executeUpdateWithGeneratedKeys(query,
          newShelf, PantryPal.userName);

      Shelf shelf = new Shelf(String.valueOf(id), newShelf);
      register.addShelf(shelf);
      rerender();

      return;
    }

    Shelf shelf = new Shelf(newShelf);
    register.addShelf(shelf);
    rerender();
  }

  /**
   * Adds a shelf to the register.
   *
   * @param name the name of the shelf
   */
  public Shelf addShelf(String name) {
    if (isLoggedIn()) {
      String query = "INSERT INTO pantry_shelf (name, user_name) VALUES (?, ?)";
      int id = SQL.executeUpdateWithGeneratedKeys(query, name, PantryPal.userName);

      Shelf shelf = new Shelf(String.valueOf(id), name);
      register.addShelf(shelf);
      rerender();

      return shelf;
    }

    Shelf shelf = new Shelf(name);
    register.addShelf(shelf);
    rerender();

    return shelf;
  }

  /**
   * Deletes the specified shelf.
   *
   * @param shelf the shelf to delete
   */
  public void deleteShelf(Shelf shelf) {
    register.removeShelf(shelf);
    rerender();

    if (isLoggedIn()) {
      SQL.executeUpdate("DELETE FROM pantry_shelf WHERE id = ?", shelf.getKey());
    }
  }

  /**
   * Edits the name of the specified shelf.
   *
   * @param shelf the shelf to edit
   * @param name  the new name of the shelf
   */
  public void editShelfName(Shelf shelf, String name) {
    shelf.setName(name);
    rerender();

    if (isLoggedIn()) {
      SQL.executeUpdate("UPDATE pantry_shelf SET name = ? WHERE id = ?", name,
          shelf.getKey());
    }
  }

  /**
   * Returns all groceries from the specified shelf.
   *
   * @param shelf the shelf to get the groceries from
   * @return an array of Grocery objects
   */
  public Grocery[] getGroceries(Shelf shelf) {
    return shelf.getGroceries().values().toArray(new Grocery[0]);
  }

  /**
   * Adds a grocery item to the shelf. If the grocery item already exists in the shelf, the quantity
   * of the grocery item is updated.
   *
   * @param shelf  the shelf to add the grocery item to
   * @param name   the name of the grocery item
   * @param amount the amount of the grocery item
   */
  public void addGrocery(Shelf shelf, String name, int amount) {
    //Check if grocery already exists in shelf
    if (shelf.getGroceries().containsKey(name)) {
      GroceryRegister groceryRegister = shelf.getGroceryRegister();
      Grocery grocery = groceryRegister.getGrocery(name);
      int oldAmount = grocery.getQuantity();

      if (isLoggedIn()) {
        String groceryQuery = "UPDATE pantry_shelf_grocery SET quantity = ?  "
            + "WHERE pantry_shelf_id = ? AND grocery_name = ?";
        SQL.executeUpdate(groceryQuery, oldAmount + amount, shelf.getKey(), grocery.getName());
      }
      grocery.setQuantity(oldAmount + amount);
    } else {
      if (isLoggedIn()) {
        //Check if grocery exists in grocery table
        String checkGroceryQuery = "SELECT * FROM grocery WHERE name = ?";
        List<Map<String, Object>> groceries = SQL.executeQuery(checkGroceryQuery, name);
        if (groceries.isEmpty()) {
          String groceryQuery = "INSERT INTO grocery (name, unit) VALUES (?, ?)";
          SQL.executeUpdate(groceryQuery, name, "g");

          String shelfGroceryQuery = "INSERT INTO pantry_shelf_grocery "
              + "(pantry_shelf_id, grocery_name, quantity) VALUES (?, ?, ?)";
          SQL.executeUpdate(shelfGroceryQuery, shelf.getKey(), name, amount);
        }

        Grocery grocery = new Grocery(name, amount, "g", shelf.getName(), false);
        shelf.addGrocery(grocery);
      } else {
        Grocery grocery = new Grocery(name, amount, "g", shelf.getName(), false);
        shelf.addGrocery(grocery);

      }
    }
    rerender();
  }

  /**
   * Adds a grocery item to the shelf. If the shelf does not exist in the register, a new shelf is
   * created and the grocery item is added to the shelf.
   *
   * @param shelfName the name of the shelf
   * @param name      the name of the grocery item
   * @param amount    the amount of the grocery item
   */
  public void addGrocery(String shelfName, String name, int amount) {
    Shelf shelf = null;
    try {
      shelf = register.getShelfByName(shelfName);
    } catch (IllegalArgumentException e) {
      shelf = this.addShelf(shelfName);
    } finally {
      if (shelf == null) {
        shelf = this.addShelf(shelfName);
      }
      this.addGrocery(shelf, name, amount);
    }
  }

  /**
   * Removes the grocery item from the shelf. If the shelf does not exist in the register, an
   * IllegalArgumentException is thrown.
   *
   * @param shelf   the shelf from the grocery is to be removed
   * @param grocery the grocery item to be removed
   */
  public void deleteGrocery(Shelf shelf, Grocery grocery) {
    shelf.removeGrocery(grocery);
    rerender();

    if (isLoggedIn()) {
      String query = "DELETE FROM pantry_shelf_grocery WHERE pantry_shelf_id = ? "
          + "AND grocery_name = ?";
      SQL.executeUpdate(query, shelf.getKey(), grocery.getName());
    }
  }

  /**
   * Renders the view with the updated data.
   */
  public void rerender() {
    view.render(getShelves());
  }
}