CookbookController.java
package stud.ntnu.idatt1005.pantrypal.controllers;
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.Recipe;
import stud.ntnu.idatt1005.pantrypal.registers.GroceryRegister;
import stud.ntnu.idatt1005.pantrypal.registers.RecipeRegister;
import stud.ntnu.idatt1005.pantrypal.registers.ShelfRegister;
import stud.ntnu.idatt1005.pantrypal.registers.StepRegister;
import stud.ntnu.idatt1005.pantrypal.utils.SQL;
import stud.ntnu.idatt1005.pantrypal.utils.ViewManager;
import stud.ntnu.idatt1005.pantrypal.views.CookbookView;
import stud.ntnu.idatt1005.pantrypal.views.RecipeView;
/**
* The controller for the CookBookView and RecipeView.
* This class is responsible for handling the logic for the CookBookView and RecipeView.
*/
public class CookbookController extends Controller implements Observer {
private final RecipeRegister recipeRegister;
private List<Recipe> currentSearch;
private final ShelfRegister shelfRegister;
private final GroceryRegister shoppingListRegister;
private final ShoppingListController shoppingListController;
private final CookbookView view;
/**
* Constructor for the CookbookController. The constructor takes in a ViewManager, a
* ShoppingListController, and a PantryController. The constructor creates a new RecipeRegister
* and sets the shelfRegister and shoppingListRegister to the registers in the PantryController
* and ShoppingListController. The constructor loads the recipes from the database if the user is
* logged in. The currentSearch is set to all the recipes in the recipeRegister. The view is
* created and added to the viewManager.
*
* @param viewManager the ViewManager for the application
* @param shoppingListController the ShoppingListController for the application
* @param pantryController the PantryController for the application
*/
public CookbookController(ViewManager viewManager, ShoppingListController shoppingListController,
PantryController pantryController) {
super(viewManager);
this.recipeRegister = new RecipeRegister();
this.shelfRegister = pantryController.getRegister();
this.shoppingListRegister = shoppingListController.getRegister();
this.shoppingListController = shoppingListController;
if (this.isLoggedIn()) {
this.load();
}
this.currentSearch = getRecipes().values().stream().toList();
this.view = new CookbookView(this);
this.view.addObserver(this);
this.viewManager.addView(Route.COOKBOOK, view);
this.rerender();
}
/**
* Loads the recipes from the database. This method gets all the recipes from the database and
* creates a new Recipe object for each recipe related to the user. The method also gets the
* groceries and steps for each recipe and adds them to the Recipe object. The method also
* checks if the recipe is a favorite for the user and sets the isFavorite boolean in the Recipe
* object. The Recipe object is then added to the recipeRegister.
*/
private void load() {
String query = "SELECT * FROM recipe";
List<Map<String, Object>> recipesFromDataBase = SQL.executeQuery(query);
for (Map<String, Object> recipeFromDataBase : recipesFromDataBase) {
GroceryRegister groceries = new GroceryRegister();
StepRegister steps = new StepRegister();
String id = recipeFromDataBase.get("id").toString();
String name = recipeFromDataBase.get("name").toString();
String description = recipeFromDataBase.get("description").toString();
String image;
try {
image = recipeFromDataBase.get("image").toString();
} catch (NullPointerException e) {
image = null;
}
String groceriesQuery = "SELECT * FROM recipe_grocery WHERE recipe_id = ?";
List<Map<String, Object>> groceriesFromDataBase = SQL.executeQuery(groceriesQuery, id);
for (Map<String, Object> groceryFromDataBase : groceriesFromDataBase) {
String groceryName = groceryFromDataBase.get("grocery_name").toString();
int quantity = (int) groceryFromDataBase.get("quantity");
String groceryQuery = "SELECT * FROM grocery WHERE name = ?";
List<Map<String, Object>> groceryDataFromDataBase =
SQL.executeQuery(groceryQuery, groceryName);
String unit = groceryDataFromDataBase.getFirst().get("unit").toString();
//TODO: Fix shelf
Grocery grocery = new Grocery(groceryName, quantity, unit, "", false);
groceries.addGrocery(grocery);
}
String stepsQuery = "SELECT * FROM step WHERE recipe_id = ?";
List<Map<String, Object>> stepsFromDataBase = SQL.executeQuery(stepsQuery, id);
for (Map<String, Object> stepFromDataBase : stepsFromDataBase) {
String step = stepFromDataBase.get("description").toString();
steps.addStep(step);
}
String favoriteQuery = "SELECT * FROM recipe_favorite WHERE recipe_id = ? AND user_name = ?";
List<Map<String, Object>> favorite = SQL.executeQuery(favoriteQuery, id, PantryPal.userName);
boolean isFavorite = !favorite.isEmpty();
Recipe recipe = new Recipe(name, description, groceries, steps, image, isFavorite);
this.recipeRegister.addRecipe(recipe);
}
}
/**
* Returns the register with recipes in the recipeRegister.
*
* @return the register with Recipes in the recipeRegister.
*/
public Map<String, Recipe> getRecipes() {
return this.recipeRegister.getRegister();
}
/**
* Returns the current search list.
*
* @return the current search list.
*/
public List<Recipe> getCurrentSearch() {
return currentSearch;
}
/**
* Updates the observer with the button that was pressed and the object affected. If
* the object is a Recipe, it performs the action related to the buttonEnum. If the object is not
* a Recipe, it throws an IllegalArgumentException.
*
* @param buttonEnum the button that was pressed
* @param object the object that is related to the button
*/
@Override
public void update(ButtonEnum buttonEnum, Object object) {
if (!(object instanceof Recipe recipe)) {
throw new IllegalArgumentException("Object is not of type Recipe");
}
switch (buttonEnum) {
case OPEN_RECIPE:
openRecipe(recipe);
break;
case ADD_TO_SHOPPING_LIST:
addGroceriesToShoppingList(recipe);
break;
case EDIT_FAVORITE:
toggleIsFavorite(recipe);
break;
case EDIT_RECIPE:
AddRecipeController addRecipeController = new AddRecipeController(
this.viewManager, this);
addRecipeController.setRecipeToAddRecipeView(recipe);
this.viewManager.setView(Route.ADD_RECIPE);
break;
case ADD:
this.addRecipe(recipe);
currentSearch = getRecipes().values().stream().toList();
view.render(currentSearch);
this.viewManager.setView(Route.COOKBOOK);
break;
case REMOVE:
recipeRegister.removeRecipe(recipe);
currentSearch = getRecipes().values().stream().toList();
view.render(currentSearch);
viewManager.setView(Route.COOKBOOK);
break;
default:
break;
}
}
/**
* Updates the observer with the button that was pressed. If the button is 'buttonEnum.ADD', it
* opens the AddRecipeView for the user to add a new Recipe.
*
* @param buttonEnum the button that was pressed
*/
@Override
public void update(ButtonEnum buttonEnum) {
if (Objects.requireNonNull(buttonEnum) == ButtonEnum.ADD) {
openAddRecipe();
} else {
throw new UnsupportedOperationException("Button not supported: " + buttonEnum);
}
}
/**
* Opens the AddRecipeView and sets the view to AddRecipeView.
* Creates a new AddRecipeController and AddRecipeView and
* set the view to AddRecipeView.
*/
private void openAddRecipe() {
new AddRecipeController(this.viewManager, this);
this.viewManager.setView(Route.ADD_RECIPE);
}
/**
* Searches for recipes in the recipeRegister based on the search string.
* The search string is passed to the recipeRegister, and the currentSearch is set to the result
* of the search. The view is then rendered with the currentSearch.
*
* @param search the search string to search for in the recipeRegister
*/
public void searchRecipes(String search) {
currentSearch = recipeRegister.searchRecipes(search);
view.render(currentSearch);
}
/**
* Opens a recipe in the RecipeView, and sets the view to RecipeView.
*
* @param recipe the recipe to be opened in the RecipeView.
*/
private void openRecipe(Recipe recipe) {
RecipeView recipeView = new RecipeView(this, recipe);
recipeView.addObserver(this);
this.viewManager.addView(Route.RECIPE, recipeView);
this.viewManager.setView(Route.RECIPE);
}
/**
* Adds the groceries from a recipe to the shopping list.
* This method gets the grocery from the recipe and checks
* if the grocery is already in the shopping list or pantry.
* If the grocery is in the pantry, it checks if the quantity
* is enough. If not, it adds the difference to the shopping list.
* If the grocery is not in the pantry, it adds the grocery to the
* shopping list.
*
* @param recipe the recipe to add groceries from.
*/
private void addGroceriesToShoppingList(Recipe recipe) {
for (Map.Entry<String, Grocery> entry : recipe.getRecipeGroceries().getRegister().entrySet()) {
String groceryName = entry.getKey();
String groceryShelf = entry.getValue().getShelf();
int quantityNeeded = entry.getValue().getQuantity();
Grocery[] shelfGroceries = shelfRegister.getAllGroceries();
int quantityInShelf = 0;
for (Grocery grocery : shelfGroceries) {
if (grocery.getKey().equals(groceryName)) {
quantityInShelf += grocery.getQuantity();
}
}
Grocery shoppingListGrocery = null;
int quantityInShoppingList;
try {
shoppingListGrocery = shoppingListRegister.getGrocery(groceryName);
quantityInShoppingList = shoppingListGrocery.getQuantity();
} catch (Exception e) {
quantityInShoppingList = 0;
}
int totalQuantityAvailable = quantityInShelf + quantityInShoppingList;
if (quantityNeeded > totalQuantityAvailable) {
int quantityToAdd = quantityNeeded - totalQuantityAvailable;
if (shoppingListGrocery != null) {
shoppingListGrocery.setQuantity(shoppingListGrocery.getQuantity() + quantityToAdd);
} else {
shoppingListController.addGrocery(
new Grocery(groceryName, quantityToAdd, "g", groceryShelf, false));
}
}
}
shoppingListController.rerender();
}
/**
* Adds a recipe to the recipeRegister and the database.
*
* @param recipe the recipe to add to the recipeRegister and the database.
*/
private void addRecipe(Recipe recipe) {
if (recipe == null) {
throw new IllegalArgumentException("Recipe cannot be null");
}
if (getRecipes().containsKey(recipe.getKey())) {
getRecipes().remove(recipe.getKey());
}
String query = "INSERT INTO recipe (name, description, image) VALUES (?, ?, ?)";
SQL.executeUpdate(query, recipe.getKey(), recipe.getDescription(), recipe.getImagePath());
for (Grocery grocery : recipe.getRecipeGroceries().getRegister().values()) {
String groceryQuery = "SELECT * FROM grocery WHERE name = ?";
List<Map<String, Object>> groceryDataFromDataBase =
SQL.executeQuery(groceryQuery, grocery.getKey());
if (groceryDataFromDataBase.isEmpty()) {
String insertGroceryQuery = "INSERT INTO grocery (name, unit) VALUES (?, ?)";
SQL.executeUpdate(insertGroceryQuery, grocery.getKey(), grocery.getUnit());
}
String insertGroceryRecipeQuery = "INSERT INTO recipe_grocery "
+ "(recipe_id, grocery_name, quantity) VALUES (?, ?, ?)";
SQL.executeUpdate(insertGroceryRecipeQuery, recipe.getKey(),
grocery.getKey(), grocery.getQuantity());
}
for (String step : recipe.getRecipeSteps()) {
String insertStepQuery = "INSERT INTO step (recipe_id, description) VALUES (?, ?)";
SQL.executeUpdate(insertStepQuery, recipe.getKey(), step);
}
recipeRegister.addRecipe(recipe);
}
/**
* Toggles the favorite status of a recipe.
*
* @param recipe the recipe to toggle the favorite status of.
*/
private void toggleIsFavorite(Recipe recipe) {
recipe.toggleIsFavorite();
if (this.isLoggedIn()) {
String name = recipe.getKey();
//TODO: fix recipe to use id, not name
String idQuery = "SELECT id FROM recipe WHERE name = ?";
List<Map<String, Object>> idResult = SQL.executeQuery(idQuery, name);
String id = idResult.getFirst().get("id").toString();
String checkQuery = "SELECT * FROM recipe_favorite WHERE recipe_id = ? AND user_name = ?";
List<Map<String, Object>> favorite = SQL.executeQuery(checkQuery, id, PantryPal.userName);
if (favorite.isEmpty()) {
String insertQuery = "INSERT INTO recipe_favorite (recipe_id, user_name) VALUES (?, ?)";
SQL.executeUpdate(insertQuery, id, PantryPal.userName);
} else {
String deleteQuery = "DELETE FROM recipe_favorite WHERE recipe_id = ? AND user_name = ?";
SQL.executeUpdate(deleteQuery, id, PantryPal.userName);
}
}
view.render(currentSearch);
}
/**
* Re-renders the view with the currentSearch.
*/
public void rerender() {
view.render(currentSearch);
}
}