CookbookRecipeComponent.java

package stud.ntnu.idatt1005.pantrypal.views.components;

import static javafx.stage.Screen.getPrimary;

import java.util.ArrayList;
import java.util.List;
import javafx.geometry.Pos;
import javafx.geometry.Rectangle2D;
import javafx.scene.control.Label;
import javafx.scene.image.Image;
import javafx.scene.layout.Background;
import javafx.scene.layout.BackgroundFill;
import javafx.scene.layout.BackgroundImage;
import javafx.scene.layout.BackgroundPosition;
import javafx.scene.layout.BackgroundRepeat;
import javafx.scene.layout.BackgroundSize;
import javafx.scene.layout.Border;
import javafx.scene.layout.BorderStroke;
import javafx.scene.layout.BorderStrokeStyle;
import javafx.scene.layout.BorderWidths;
import javafx.scene.layout.CornerRadii;
import javafx.scene.layout.StackPane;
import javafx.scene.shape.Rectangle;
import stud.ntnu.idatt1005.pantrypal.controllers.Observer;
import stud.ntnu.idatt1005.pantrypal.enums.ButtonEnum;
import stud.ntnu.idatt1005.pantrypal.models.Recipe;
import stud.ntnu.idatt1005.pantrypal.utils.ColorPalette;
import stud.ntnu.idatt1005.pantrypal.utils.FontPalette;
import stud.ntnu.idatt1005.pantrypal.views.Observable;

/**
 * This class represents a component for displaying a recipe within the cookbook.
 * It provides methods to create a visual representation of a recipe, including its image and name.
 */
public class CookbookRecipeComponent extends StackPane implements Observable {

  private final Recipe recipe;
  private final List<Observer> observers;

  /**
   * Constructs a CookbookRecipeComponent for a Recipe.
   * This constructor sets up the visual representation of the recipe
   * using the image if it exists, and the name of the recipe.
   *
   * @param recipe     The recipe to be displayed.
   */
  public CookbookRecipeComponent(Recipe recipe) {
    this.recipe = recipe;
    this.observers = new ArrayList<>();
    BackgroundSize backgroundSize = new BackgroundSize(320, 200,
            true, true, false, true);
    setUpStackPane();

    if (recipe.getImagePath() == null || recipe.getImagePath().isEmpty()) {
      BackgroundFill backgroundFill = new BackgroundFill(ColorPalette.GRAY,
              new CornerRadii(10), null);
      this.setBackground(new Background(backgroundFill));
    } else {
      Image image;
      try {
        image = new Image(recipe.getImagePath());
      } catch (IllegalArgumentException e) {
        image = new Image("images/PantryPalLogo.png");
      }
      BackgroundImage backgroundImage = new BackgroundImage(image,
              BackgroundRepeat.NO_REPEAT,
              BackgroundRepeat.NO_REPEAT,
              BackgroundPosition.CENTER,
              backgroundSize);
      this.setBackground(new Background(backgroundImage));
    }

    setLabel(recipe.getKey());
    setStarIcon(recipe.getIsFavorite());
  }


  /**
   * Sets up the BorderPane for the recipe component.
   *
   */

  private void setUpStackPane() {
    this.setMaxWidth(getComponentWidth());
    this.setMaxHeight(getComponentHeight());
    this.setMinWidth(getComponentWidth());
    this.setMinHeight(getComponentHeight());
    BorderStroke borderStroke = new BorderStroke(ColorPalette.BLACK,
            BorderStrokeStyle.SOLID,
            new CornerRadii(10),
            new BorderWidths(1));
    this.setBorder(new Border(borderStroke));
    Rectangle clip = new Rectangle(getComponentWidth(), getComponentHeight());
    clip.setArcWidth(20);
    clip.setArcHeight(20);
    this.setClip(clip);
    this.setOnMouseClicked(e -> notifyObservers(ButtonEnum.OPEN_RECIPE));
  }

  /**
   * Sets the label of the recipe component.
   *
   * @param recipeName name of the recipe
   */
  private void setLabel(String recipeName) {
    Label label = new Label(recipeName);
    label.setFont(FontPalette.BUTTON);
    StackPane.setAlignment(label, Pos.CENTER);
    this.getChildren().add(label);
  }

  /**
   * Sets the star icon of the recipe component, and places it in the top right corner.
   *
   * @param isFavorite boolean indicating if the recipe is a favorite
   */
  private void setStarIcon(boolean isFavorite) {
    StarIcon starIcon = new StarIcon(StarIcon.Variants.COOKBOOK, isFavorite);
    StackPane.setAlignment(starIcon, Pos.TOP_RIGHT);
    this.getChildren().add(starIcon);
  }

  /**
   * Calculates and returns the width of the recipe component.
   *
   * @return The width of the recipe component.
   */
  public static double getComponentWidth() {
    Rectangle2D visualBounds = getPrimary().getVisualBounds();
    return visualBounds.getWidth() / 4 - 40;
  }

  /**
   * Calculates and returns the height of the recipe component.
   *
   * @return The height of the recipe component.
   */
  public static double getComponentHeight() {
    return getComponentWidth() * 0.625;
  }

  @Override
  public void addObserver(Observer observer) {
    if (observer != null) {
      if (!observers.contains(observer)) {
        observers.add(observer);
      }
    } else {
      throw new IllegalArgumentException("Observer cannot be null");
    }
  }

  @Override
  public void removeObserver(Observer observer) {
    if (observer != null) {
      observers.remove(observer);
    } else {
      throw new IllegalArgumentException("Observer cannot be null");
    }
  }

  /**
   * Notifies the observers of the component with the given buttonEnum and
   * the recipe of the component.
   *
   * @param buttonEnum the buttonEnum to notify the observers with
   */
  protected void notifyObservers(ButtonEnum buttonEnum) {
    List<Observer> observersCopy = new ArrayList<>(this.observers);
    for (Observer observer : observersCopy) {
      observer.update(buttonEnum, this.recipe);
    }
  }
}