View.java

package stud.ntnu.idatt1005.pantrypal.views;

import static javafx.stage.Screen.getPrimary;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.ScrollPane;
import javafx.scene.layout.Background;
import javafx.scene.layout.BackgroundFill;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.CornerRadii;
import javafx.scene.layout.VBox;
import javafx.scene.text.Text;
import stud.ntnu.idatt1005.pantrypal.controllers.Controller;
import stud.ntnu.idatt1005.pantrypal.controllers.Observer;
import stud.ntnu.idatt1005.pantrypal.enums.ButtonEnum;
import stud.ntnu.idatt1005.pantrypal.enums.Route;
import stud.ntnu.idatt1005.pantrypal.utils.ColorPalette;
import stud.ntnu.idatt1005.pantrypal.utils.FontPalette;
import stud.ntnu.idatt1005.pantrypal.views.components.NavBar;



/**
 * This class represents a View in the application. It extends the Scene class from JavaFX and
 * provides a base structure for other views. It has two types of views: HOME and DEFAULT. The HOME
 * view includes a title and a navigation bar. The DEFAULT view only includes a navigation bar.
 */
public class View extends Scene implements Observable {
  private final Route route;
  private final Controller controller;
  private final BorderPane root;

  /**
   * List of observers observing this view.
   */
  protected final List<Observer> observers = new ArrayList<>();

  /**
   * Constructs a View object with a specified controller, route, and style path.
   * The view is created with a BorderPane as the root node
   * and styled with a style sheet based on the route.
   * The view is associated with a controller to handle the logic and actions.
   * The view is created with a size to match the screen size.
   *
   * @param controller  The controller associated with the view.
   * @param route       The route of the view.
   * @param stylePath   The path to the style sheet for the view.
   */
  public View(Controller controller, Route route, String stylePath) {
    super(new BorderPane(), getPrimary().getVisualBounds().getWidth(),
            getPrimary().getVisualBounds().getHeight());
    this.getStylesheets().add(Objects.requireNonNull(getClass()
            .getResource("/styles/components.css")).toExternalForm());
    this.getStylesheets().add(Objects.requireNonNull(getClass()
            .getResource(stylePath)).toExternalForm());
    this.route = route;
    this.controller = controller;
    this.widthProperty().lessThanOrEqualTo(getPrimary().getVisualBounds().getWidth() - 100);
    root = (BorderPane) getRoot();
    BorderPane borderPane = new BorderPane();
    borderPane.setPadding(new Insets(0));
    borderPane.maxWidthProperty().bind(root.widthProperty());
    BackgroundFill backgroundColor = new BackgroundFill(ColorPalette.PRIMARY_LIGHT,
            CornerRadii.EMPTY, Insets.EMPTY);
    root.setBackground(new Background(backgroundColor));
    this.setFill(ColorPalette.PRIMARY_LIGHT);
    this.view(root);
  }

  /**
   * Sets up the view for the scene based on the route of the scene.
   * If the route is HOME, the view includes a navigation bar and a title.
   * For other routes, the view only includes a navigation bar.
   *
   * @param borderPane The BorderPane to set the view in.
   */
  public void view(BorderPane borderPane) {
    if (route == Route.HOME) {
      VBox topContainer = new VBox(0);
      topContainer.setAlignment(javafx.geometry.Pos.CENTER);
      Text title = new Text("Pantry Pal");
      title.setFont(FontPalette.HEADER);
      topContainer.getChildren().addAll(
              title,
              new NavBar(controller)
      );
      borderPane.setTop(topContainer);
    } else {
      borderPane.setTop(new NavBar(controller));
    }
  }

  /**
   * Sets the root to a ScrollPane, and sets the current root to the content of the ScrollPane.
   */
  public void setScrollPane() {
    ScrollPane scrollPane = new ScrollPane();
    scrollPane.setContent(root);
    setRoot(scrollPane);
  }

  /**
   * Get the root node of the scene as a BorderPane.
   *
   * @return The root node of the scene.
   */
  public BorderPane getBorderPane() {
    return root;
  }

  /**
   * Adds an observer to the list of observers for this view.
   *
   * @param observer The observer to be added.
   * @throws IllegalArgumentException If the observer is null.
   */
  @Override
  public void addObserver(Observer observer) throws IllegalArgumentException {
    if (observer != null) {
      if (!observers.contains(observer)) {
        observers.add(observer);
      }
    } else {
      throw new IllegalArgumentException("Observer cannot be null");
    }
  }

  /**
   * Removes an observer from the list of observers for this view.
   *
   * @param observer The observer to be removed.
   * @throws IllegalArgumentException If the observer is null.
   */
  @Override
  public void removeObserver(Observer observer) throws IllegalArgumentException {
    if (observer != null) {
      observers.remove(observer);
    } else {
      throw new IllegalArgumentException("Observer cannot be null");
    }
  }

  /**
   * Notifies all observers with the given ButtonEnum.
   *
   * @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);
    }
  }

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