Skip to content
Snippets Groups Projects
RecipeEditorController.java 11.6 KiB
Newer Older
package mi.hdm.controllers;

import javafx.fxml.FXML;
import javafx.scene.control.*;
import javafx.scene.layout.FlowPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import mi.hdm.components.CategoryCheckBox;
import mi.hdm.components.CategoryPreviewLabel;
import mi.hdm.components.IngredientSearchResultLabel;
import mi.hdm.components.SelectedIngredientLabel;
import mi.hdm.exceptions.InvalidRecipeException;
Karsch Lukas's avatar
Karsch Lukas committed
import mi.hdm.helpers.Validation;
import mi.hdm.recipes.*;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.IntStream;

import static mi.hdm.helpers.Validation.isInteger;

public class RecipeEditorController extends BaseController {
    private static final int ELEMENTS_PER_SEARCH_PAGE = 100;

    private Recipe recipe;
    private final RecipeManager recipeManager;
    private final CategoryManager categoryManager;
    private final IngredientManager ingredientManager;
    private final RecipeSearch recipeSearch;

    private List<RecipeComponent> searchResults;
    private final List<Category> selectedCategories;
    private final List<RecipeComponent> selectedIngredients;
    private List<SelectedIngredientLabel> selectedIngredientLabels;
    private int currentPage = 0;
    private int maxPages = 0;

    private static final Logger log = LogManager.getLogger(RecipeEditorController.class);

    @FXML
    private TextField nameTextField;
    @FXML
    private TextArea descriptionTextArea;
    @FXML
    private TextField prepTimeTextField;
    @FXML
    private TextField imagePathTextField;
    @FXML
    private HBox categories;
    @FXML
    private FlowPane allCategories;
    @FXML
    private TextField ingredientSearch;
    @FXML
    private ScrollPane searchResultsPane;
    @FXML
    private TextArea preparationTextArea;
    @FXML
    private ScrollPane selectedIngredientsScrollPane;
    private final VBox selectedIngredientsVbox;



    public RecipeEditorController(Recipe recipe, RecipeManager recipeManager, IngredientManager ingredientManager, CategoryManager categoryManager) {
        this.recipe = recipe;
        this.recipeManager = recipeManager;
        this.categoryManager = categoryManager;
        this.ingredientManager = ingredientManager;

        selectedCategories = new ArrayList<>(categoryManager.getCategoriesFromKeys(recipe.getCategoryCodes()));
        log.info("Added categories to selectedCategories: {}", selectedCategories);

        searchResults = new ArrayList<>();
        selectedIngredients = new ArrayList<>();
        ingredientManager.getIngredientsFromKeys(recipe.getIngredients()).forEach((i, amount) -> {
            if (i instanceof Ingredient) {
                selectedIngredients.add(i);
            } else {
                log.error("No ingredient with code {}", i.getUniqueCode());
            }
        });
        recipeSearch = new RecipeSearch(recipeManager, ingredientManager);
        selectedIngredientsVbox = new VBox();
        selectedIngredientLabels = new ArrayList<>();
    }

    @FXML
    public void initialize() {
        mapCategories();
        displayRecipe();
        ingredientSearch.textProperty().addListener(((e) -> searchIngredients())); //adds onChange listener to the search field
        selectedIngredientsScrollPane.setContent(selectedIngredientsVbox);

    }

    private void displayRecipe() {
        nameTextField.setText(recipe.getName());
        descriptionTextArea.setText(recipe.getDescription());
        recipe.getIngredients().forEach((k, v) -> {
            HBox ingredientHBox = new HBox();
            Button deleteIngredientButton = new Button("X");
            deleteIngredientButton.setStyle("-fx-text-fill: d91c1c;" +
                    "-fx-font-size: 12;" +
                    "-fx-background-size: small;");
            deleteIngredientButton.setOnAction(h -> {
                selectedIngredients.remove(ingredientManager.getIngredient(k));
                log.debug("User deleted ingredient '{}' from recipe.", ingredientManager.getIngredient(k).get().getName());
                selectedIngredientsVbox.getChildren().remove(ingredientHBox);
            });
            SelectedIngredientLabel label = new SelectedIngredientLabel(ingredientManager.getIngredient(k).get(), v);
            selectedIngredientLabels.add(label);
            ingredientHBox.getChildren().addAll(label, deleteIngredientButton);
            selectedIngredientsVbox.getChildren().add(ingredientHBox);
        });
        preparationTextArea.setText(String.join("\n", recipe.getPreparation()));
        prepTimeTextField.setText(String.valueOf(recipe.getPreparationTimeMins()));
        imagePathTextField.setText(recipe.getImageURL().toString());
    }

    private void mapCategories() {
        for (final Category category : categoryManager.getAllCategories()) {
            CategoryCheckBox checkbox = new CategoryCheckBox(category);
            if(selectedCategories.contains(category)) {
                checkbox.setSelected(true);
                drawSelectedCategories();
            }
            checkbox.setOnAction(e -> updateSelectedCategories(checkbox));
            allCategories.getChildren().add(checkbox);
        }
    }

    private void updateSelectedCategories(CategoryCheckBox checkbox) {
        if (checkbox.isSelected()) {
            selectedCategories.add(checkbox.getAssociatedCategory());
            log.debug("Added '{}' to list of selected categories.", checkbox.getAssociatedCategory().getName());
        } else {
            selectedCategories.remove(checkbox.getAssociatedCategory());
            log.debug("Removed '{}' from list of selected categories.", checkbox.getAssociatedCategory().getName());
        }
        drawSelectedCategories();
    }

    private void drawSelectedCategories() {
        log.debug("Drawing selected categories");
        categories.getChildren().clear();
        for (final Category category : selectedCategories) {
            CategoryPreviewLabel label = new CategoryPreviewLabel(category);
            categories.getChildren().add(label);
        }
    }

    @FXML
    public void searchIngredients() {
        String input = ingredientSearch.getText();
        log.debug("Input in search field changed, searching through recipe components with query '{}'.", input);
        searchResults = recipeSearch.searchThroughNames(input);
        currentPage = 0;
        maxPages = searchResults.size() / ELEMENTS_PER_SEARCH_PAGE;
        drawIngredientSearchResults();
    }

    private void drawIngredientSearchResults() {
        log.debug("Drawing search results");
        VBox resultContainer = new VBox();
        int start = currentPage * ELEMENTS_PER_SEARCH_PAGE;
        int end = Math.min(searchResults.size(), start + ELEMENTS_PER_SEARCH_PAGE);
        IntStream.range(start, end)
                .mapToObj(searchResults::get)
                .forEach(
                        result -> {
                            IngredientSearchResultLabel resultLabel = new IngredientSearchResultLabel(result);
                            resultLabel.setOnMouseClicked(e -> {
                                log.debug("User added ingredient '{}' to recipe.", result.getName());
                                if (!selectedIngredients.contains(result)) {
                                    selectedIngredients.add(result);
                                    HBox ingredientHBox = new HBox();
                                    Button deleteIngredientButton = new Button("X");
                                    deleteIngredientButton.setStyle("-fx-text-fill: d91c1c;" +
                                            "-fx-font-size: 12;" +
                                            "-fx-background-size: small;");
                                    deleteIngredientButton.setOnAction(h -> {
                                        selectedIngredients.remove(result);
                                        log.debug("User deleted ingredient '{}' from recipe.", result.getName());
                                        selectedIngredientsVbox.getChildren().remove(ingredientHBox);
                                    });
                                    SelectedIngredientLabel label = new SelectedIngredientLabel(result);
                                    selectedIngredientLabels.add(label);
                                    ingredientHBox.getChildren().addAll(label, deleteIngredientButton);
                                    selectedIngredientsVbox.getChildren().add(ingredientHBox);
                                }
                            });
                            resultContainer.getChildren().add(resultLabel);
                        }
                );
        searchResultsPane.setContent(resultContainer);
    }

    @FXML
    public void confirmEditRecipe() {
        log.debug("User confirmed recipe editing.");

        recipe.setName(nameTextField.getText());
        Map<RecipeComponent, Integer> ingredients = new HashMap<>();
        selectedIngredientLabels.forEach(label -> {
            ingredients.put(label.getComponent(), label.getAmount());
        });
        try {
            recipe.setIngredientFromRecipeComponents(ingredients);
            recipe.setDescription(descriptionTextArea.getText());
            recipe.setPreparation(List.of(preparationTextArea.getText().split("\n")));
            recipe.setCategoriesFromObjects(List.copyOf(selectedCategories));
            String prepTime = prepTimeTextField.getText();
            recipe.setPreparationTimeMins( isInteger(prepTime) ? Integer.parseInt(prepTime) : null);
            if (Files.exists(Paths.get(new URI(imagePathTextField.getText())))) {
                recipe.setImage(new URL(imagePathTextField.getText()));
            }
            log.info("Recipe '{}' was edited.", recipe.getName());
            changeSceneToRecipe();
        } catch (InvalidRecipeException e) {
            Alert a = new Alert(Alert.AlertType.ERROR);
            a.setHeaderText("Error creating recipe");
            a.setContentText(e.getMessage());
            a.show();
            log.error(e.getMessage());
            log.error("Recipe not created.");
        } catch (RuntimeException e) {
            Alert a = new Alert(Alert.AlertType.ERROR);
            a.setHeaderText("Error creating recipe");
            a.setContentText("Something went wrong when creating the recipe. Check all your inputs!");
            a.show();
            log.error(e.getMessage());
            log.error("Recipe not created.");
        } catch (MalformedURLException | URISyntaxException e) {
            recipe.setImage(Recipe.class.getResource("/images/dish-fork-and-knife.png"));
            log.error("Invalid image path, loaded default image.");
            log.info("Recipe '{}' was created.", recipe.getName());
            changeScene(View.RECIPE_VIEW, recipe);
        }
    }

    @FXML
    public void incrementPageCounter() {
        //increment the current page and re-render
        currentPage = Math.min(++currentPage, maxPages);
        log.debug("User selected page {} / {}", currentPage, maxPages);
        drawIngredientSearchResults();
    }

    @FXML
    public void decrementPageCounter() {
        //decrement the current page and re-render
        currentPage = Math.max(0, --currentPage);
        log.debug("User selected page {} / {}", currentPage, maxPages);
        drawIngredientSearchResults();
    }

    @FXML
    public void changeSceneToRecipe() {
        changeScene(View.RECIPE_VIEW, recipe);