package ntnu.idatt2016.v233.SmartMat.service; import ntnu.idatt2016.v233.SmartMat.dto.response.RecipeDetails; import ntnu.idatt2016.v233.SmartMat.dto.response.RecipeWithMatchCount; import ntnu.idatt2016.v233.SmartMat.entity.Recipe; import ntnu.idatt2016.v233.SmartMat.entity.user.User; import ntnu.idatt2016.v233.SmartMat.repository.RecipeRepository; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.stream.Collectors; import ntnu.idatt2016.v233.SmartMat.repository.user.UserRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Service; /** * This class defines the methods for the recipe service * * @author Stian Lyng * @version 1.0 */ @Service public class RecipeService { /** * The recipe repository */ @Autowired private RecipeRepository recipeRepository; @Autowired private UserRepository userRepository; /** * Creates a new recipe service * @param recipeRepository */ public RecipeService (RecipeRepository recipeRepository) { this.recipeRepository = recipeRepository; } /** * Gets a recipe by its id * * @param id the id of the recipe * @return an optional containing the recipe if it exists */ public Optional<Recipe> getRecipeById(Long id) { return recipeRepository.findById(id); } /** * Gets all recipes with a given name * * @param name the name of the recipe * @return a list of recipes with the given name */ public List<Recipe> getRecipesByName(String name) { return recipeRepository.findAllByName(name); } /** * Gets all recipes * * @return a list of all recipes */ public List<Recipe> getAllRecipes() { return recipeRepository.findAll(); } /** * Saves a recipe * * @param recipe the recipe to save * @return the saved recipe */ public Recipe saveRecipe(Recipe recipe) { return recipeRepository.save(recipe); } /** * Deletes a recipe * * @param recipe a recipe object to delete */ public void deleteRecipe(Recipe recipe) { recipeRepository.delete(recipe); } /** * Deletes a recipe by its id * * @param id the id of the recipe to delete */ public void deleteRecipeById(Long id) { recipeRepository.deleteById(id); } /** * Adds a user to a recipe * used for adding favorite recipes * @param recipe recipe to add user to * @param user user to add to recipe */ public void addUserToRecipe(Recipe recipe, User user){ recipe.addUser(user); recipeRepository.save(recipe); } /** * Adds a recipe to a users favorites * @param recipeId id of the recipe * @param name name of the user * @return ResponsEntity with succsess/fail message */ public ResponseEntity<String> addRecipeToFavorites(Long recipeId, String name) { Optional<Recipe> recipe = recipeRepository.findById(recipeId); Optional<User> user = userRepository.findByUsername(name); if (recipe.isEmpty()) { return ResponseEntity.status(HttpStatus.NOT_FOUND).body("Could not find Recipe"); } else if (user.isEmpty()) { return ResponseEntity.status(HttpStatus.NOT_FOUND).body("Could not find User"); } else { if (user.get().getRecipes().contains(recipe.get())) { return ResponseEntity.status(HttpStatus.CONFLICT).body("Recipe already in favorites"); } user.get().addRecipe(recipe.get()); recipe.get().addUser(user.get()); userRepository.save(user.get()); return ResponseEntity.ok("Recipe added to favorites"); } } /** * Removes a recipe from a users favorites * @param recipeId id of the recipe * @param name name of the user * @return ResponseEntity with succsess/fail message */ public ResponseEntity<String> removeRecipeFromFavorites(Long recipeId, String name) { Optional<Recipe> recipe = recipeRepository.findById(recipeId); Optional<User> user = userRepository.findByUsername(name); if (recipe.isEmpty()) { return ResponseEntity.status(HttpStatus.NOT_FOUND).body("Could not find Recipe"); } else if (user.isEmpty()) { return ResponseEntity.status(HttpStatus.NOT_FOUND).body("Could not find User"); } else { if (!user.get().getRecipes().contains(recipe.get())) { return ResponseEntity.status(HttpStatus.NOT_FOUND).body("Recipe not in favorites"); } user.get().getRecipes().remove(recipe.get()); recipe.get().getUsers().remove(user.get()); userRepository.save(user.get()); return ResponseEntity.ok("Recipe deleted from favorites"); } } /** * Returns a list of RecipeWithMatchCount objects representing the weekly menu based on ingredients in a fridge. * The list contains recipes with matched ingredients and additional recipes with no matching ingredients * (if there are less than 5 recipes with matching ingredients). Recipes with 0 match count are shuffled before returning. * * @param fridgeId The ID of the fridge to get the weekly menu for * @return A list of RecipeWithMatchCount objects representing the weekly menu */ public List<RecipeWithMatchCount> getWeeklyMenu(Integer fridgeId) { // Get the results from both repository methods List<Object[]> weeklyMenu = recipeRepository.findWeeklyMenu(fridgeId); List<Object[]> recipeProducts = recipeRepository.findRecipeProductsWithName(); // Prepare a map to store RecipeDetails with their match count Map<RecipeDetails, Integer> recipeMatchCountMap = new HashMap<>(); // Compare the item_name on both lists for (Object[] menuRow : weeklyMenu) { String menuRowItemName = (String) menuRow[0]; for (Object[] recipeProductRow : recipeProducts) { String recipeProductRowItemName = (String) recipeProductRow[4]; List<String> recipeProductWords = Arrays.asList(recipeProductRowItemName.split("\\s+")); boolean allWordsContained = recipeProductWords.stream() .allMatch(word -> menuRowItemName.contains(word)); if (allWordsContained) { Integer recipeId = ((Integer) recipeProductRow[0]).intValue(); String recipeName = (String) recipeProductRow[1]; String recipeDescription = (String) recipeProductRow[2]; String recipeImage = (String) recipeProductRow[3]; RecipeDetails recipeDetails = new RecipeDetails(recipeId, recipeName, recipeDescription, recipeImage); recipeMatchCountMap.put(recipeDetails, recipeMatchCountMap.getOrDefault(recipeDetails, 0) + 1); } } } // Get a list of unique RecipeDetails from recipeProducts List<RecipeDetails> uniqueRecipeDetails = recipeProducts.stream() .map(row -> new RecipeDetails(((Integer) row[0]).intValue(), (String) row[1], (String) row[2], (String) row[3])) .distinct() .collect(Collectors.toList()); // Convert the map to a list of RecipeWithMatchCount List<RecipeWithMatchCount> commonRecipes = recipeMatchCountMap.entrySet().stream() .map(entry -> new RecipeWithMatchCount(entry.getKey(), entry.getValue())) .collect(Collectors.toList()); // Add additional recipes from uniqueRecipeDetails with a count of 0 if the list has less than 5 recipes List<RecipeWithMatchCount> zeroMatchRecipes = new ArrayList<>(); for (RecipeDetails recipeDetails : uniqueRecipeDetails) { if (commonRecipes.size() < 5 && !recipeMatchCountMap.containsKey(recipeDetails)) { zeroMatchRecipes.add(new RecipeWithMatchCount(recipeDetails, 0)); } } // Shuffle the zeroMatchRecipes list Collections.shuffle(zeroMatchRecipes); // Combine the commonRecipes and zeroMatchRecipes lists for (RecipeWithMatchCount zeroMatchRecipe : zeroMatchRecipes) { if (commonRecipes.size() < 5) { commonRecipes.add(zeroMatchRecipe); } else { break; } } return commonRecipes; } }