Inventory.java

package no.ntnu.idatt1002.demo.view.scenes;

import java.util.List;
import javafx.geometry.Insets;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.ScrollPane.ScrollBarPolicy;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.scene.text.Text;
import no.ntnu.idatt1002.demo.Logger;
import no.ntnu.idatt1002.demo.UpdateableScene;
import no.ntnu.idatt1002.demo.dao.DAO;
import no.ntnu.idatt1002.demo.dao.DBConnectionProvider;
import no.ntnu.idatt1002.demo.data.InventoryItem;
import no.ntnu.idatt1002.demo.data.Item;
import no.ntnu.idatt1002.demo.repo.InventoryRegister;
import no.ntnu.idatt1002.demo.repo.ItemRegister;
import no.ntnu.idatt1002.demo.view.components.AddPopup;
import no.ntnu.idatt1002.demo.view.components.Field;
import no.ntnu.idatt1002.demo.view.components.ItemBanner;
import no.ntnu.idatt1002.demo.view.components.ItemPane;
import no.ntnu.idatt1002.demo.view.components.ItemPopup;
import no.ntnu.idatt1002.demo.view.components.ListHeader;
import no.ntnu.idatt1002.demo.view.components.ListItem;
import no.ntnu.idatt1002.demo.view.components.Toast;
import no.ntnu.idatt1002.demo.view.components.ToastProvider;
import no.ntnu.idatt1002.demo.view.components.ViewModeButton;
import no.ntnu.idatt1002.demo.view.components.ViewModeButton.ViewMode;

/**
 * The inventory page.
 */
public class Inventory extends VBox implements UpdateableScene {
  private VBox inventoryContainer = new VBox();
  private ScrollPane scrollPane;
  private List<InventoryItem> inventoryItems;
  private ViewMode mode;
  private InventoryRegister inventoryRegister;

  /**
   * The constructor for the inventory page.
   */
  public Inventory() {
    super();

    this.getStyleClass().addAll("full-width", "no-focus");

    scrollPane = new ScrollPane();
    scrollPane.setFitToWidth(true);
    scrollPane.setFitToHeight(true);
    scrollPane.setHbarPolicy(ScrollBarPolicy.NEVER);
    scrollPane.setVbarPolicy(ScrollBarPolicy.NEVER);
    scrollPane.setContent(inventoryContainer);

    // get all items
    inventoryRegister = new InventoryRegister(new DAO(new DBConnectionProvider()));
    inventoryRegister.getAllInventoryItems();
    inventoryItems = inventoryRegister.getInventoryItems();

    ListHeader inventoryHeader = new ListHeader();
    inventoryHeader.setOnViewModeChange(this::loadInventory);
    inventoryHeader.setOnSearch(this::fullSearch);
    inventoryHeader.setOnSearchQueryChange(this::search);

    inventoryHeader.setOnAdd(this::addInventoryItem);

    super.getChildren().addAll(inventoryHeader, scrollPane);
    loadInventory(ViewModeButton.ViewMode.GRID); // Default view mode
  }

  private void addInventoryItem() {
    AddPopup addPopup = new AddPopup("Item");

    ItemRegister itemRegister = new ItemRegister(new DAO(new DBConnectionProvider()));
    itemRegister.getAllItems();

    addPopup.addField(Field.ofList(
        "Item",
        itemRegister.getItems().values().stream().toList()));

    addPopup.addField(Field.ofString("Unit"));
    addPopup.addField(Field.ofNumber("Amount"));
    addPopup.addField(Field.ofNumber("Expiration date"));
    addPopup.setSpecialPrompt(new Text("Is an item missing? Add it here"), v -> {
      this.addItem();
    });

    addPopup.show(this.getScene().getWindow());
    addPopup.setOnAdd((Object[] o) -> {
      Integer id;
      String unit;
      Number amount;
      Number expirationDate;

      try {
        id = (Integer) o[0];
        unit = (String) o[1];
        amount = (Number) o[2];
        expirationDate = (Number) o[3];

        if (id == null || unit.isEmpty() || amount.intValue() < 0 || expirationDate.intValue() < 0) {
          throw new Exception("Invalid input");
        }

      } catch (Exception e) {
        ToastProvider.enqueue(new Toast("Failed to add item", "One or more fields were empty", Toast.ToastType.ERROR));
        e.printStackTrace();
        return;
      }

      try {
        inventoryRegister.addInventoryItem(id, (int) amount, unit, expirationDate.intValue());

      } catch (Exception e) {
        ToastProvider.enqueue(new Toast("Failed to add item", e.getMessage(), Toast.ToastType.ERROR));
        e.printStackTrace();
        return;
      }
      inventoryRegister.getAllInventoryItems();
      inventoryItems = inventoryRegister.getInventoryItems();
      ToastProvider.enqueue(new Toast("Success", "The item was successfully added", Toast.ToastType.SUCCESS));
      loadInventory(mode);
    });
  }

  private void addItem() {
    AddPopup addPopup = new AddPopup("Item");
    addPopup.show(this.getScene().getWindow());
    addPopup.addField(Field.ofString("Name"));
    addPopup.addField(Field.ofString("Category"));
    addPopup.addField(Field.ofString("Allergies"));
    addPopup.setOnAdd((Object[] o) -> {
      try {
        String name = (String) o[0];
        String category = (String) o[1];
        String allergies = (String) o[2];
        try {
          ItemRegister itemRegister = new ItemRegister(new DAO(new DBConnectionProvider()));
          itemRegister.addItem(name, category, allergies);
          itemRegister.getAllItems();
        } catch (Exception e) {
          Logger.fatal("Failed to add item");
          e.printStackTrace();
          ToastProvider.enqueue(
              new Toast("Failed to add item", e.getMessage(), Toast.ToastType.ERROR));
          return;
        }
        ToastProvider.enqueue(
            new Toast("Success", "The item was successfully added", Toast.ToastType.SUCCESS));
        loadInventory(mode);
      } catch (Exception e) {
        ToastProvider.enqueue(
            new Toast("Failed to add item", "One or more fields were empty", Toast.ToastType.ERROR));
        e.printStackTrace();
      }
    });

  }

  /**
   * Loads the inventory based on the mode given. This method does not handle
   * fetching items from the database. This must be done before calling this
   * method, or nothing will display.
   *
   * @param mode The mode to display the inventory in.
   */
  private void loadInventory(ViewModeButton.ViewMode mode) {
    this.mode = mode; // save the mode for later use

    inventoryContainer.getChildren().clear();

    if (mode == ViewModeButton.ViewMode.GRID) {
      // get all categories
      String[] categories = inventoryItems.stream()
          .map(Item::getCategory)
          .distinct()
          .toArray(String[]::new);

      // group items based on category
      for (String category : categories) {
        // create a banner for each category
        ItemBanner inventoryBanner = new ItemBanner();
        inventoryBanner.setTitle(category);

        // add all items of the category to the banner
        inventoryItems.stream()
            .filter(item -> item.getCategory().equals(category))
            .forEach(item -> {
              ItemPane pane = new ItemPane(
                  item.getItemId(),
                  item.getName(),
                  0,
                  0,
                  "items");
              inventoryBanner.addItem(pane);
              pane.setOnMouseClicked(v -> {
                ItemPopup itemPopup = new ItemPopup(item);
                itemPopup.setOnSave(this::updateItem);
                itemPopup.setOnDelete(this::deleteItem);
                itemPopup.show(this.getScene().getWindow());
              });
            });
        inventoryContainer.getChildren().add(inventoryBanner);
      }
    } else if (mode == ViewModeButton.ViewMode.LIST) {
      // add a header with the attributes of the items
      String[] attibutes = { "id", "name", "category", "allergies" };
      HBox header = new HBox();
      for (String attribute : attibutes) {
        Text text = new Text(attribute);
        HBox container = new HBox();
        text.getStyleClass().addAll("semibold", "text-lg", "centered");
        container.getChildren().add(text);
        container.getStyleClass().add("list-item-attribute");
        header.getChildren().add(container);
      }
      header.setSpacing(100);
      header.setPadding(new Insets(0, 0, 0, 10));
      inventoryContainer.getChildren().add(header);

      // add all items to the list - no grouping
      inventoryItems.forEach(item -> {
        ListItem<InventoryItem> listItem = new ListItem<>(item);
        listItem.setOnMouseClicked(v -> {
          ItemPopup itemPopup = new ItemPopup(item);
          itemPopup.setOnSave(this::updateItem);
          itemPopup.setOnDelete(this::deleteItem);
          itemPopup.show(this.getScene().getWindow());
        });
        inventoryContainer.getChildren().add(listItem);
      });
    }
  }

  private void fullSearch(String query) {
    search(query);
    this.requestFocus();

  }

  private void search(String query) {
    inventoryRegister.searchInventoryByName(query);
    inventoryItems = inventoryRegister.getInventoryItems();
    loadInventory(mode);
  }

  private void updateItem(int id, int itemId, String name, String category, String allergies, String unit, int amount,
      int expirationDate) {
    inventoryRegister.getAllInventoryItems();

    ItemRegister itemRegister = new ItemRegister(new DAO(new DBConnectionProvider()));
    itemRegister.getAllItems();
    itemRegister.updateItem(itemId, name, category, allergies);

    try {
      inventoryRegister.updateInventoryItem(id, itemId, amount, unit, expirationDate);
      inventoryRegister.getAllInventoryItems();
      inventoryItems = inventoryRegister.getInventoryItems();
      ToastProvider.enqueue(
          new Toast("Success", "The item was successfully updated", Toast.ToastType.SUCCESS));
      loadInventory(mode);
    } catch (Exception e) {
      ToastProvider.enqueue(
          new Toast("Failed to update item", e.getMessage(), Toast.ToastType.ERROR));
      e.printStackTrace();
    }
  }

  private void deleteItem(int id, int itemId) {

    try {
      inventoryRegister.getAllInventoryItems();
      inventoryRegister.deleteInventoryItem(id);

      inventoryRegister.getAllInventoryItems();
      inventoryItems = inventoryRegister.getInventoryItems();
      ToastProvider.enqueue(
          new Toast("Success", "The item was successfully deleted", Toast.ToastType.SUCCESS));
      loadInventory(mode);
    } catch (Exception e) {
      ToastProvider.enqueue(
          new Toast("Failed to delete item", e.getMessage(), Toast.ToastType.ERROR));
      e.printStackTrace();
    }
  }

  public void updateScene() {
    inventoryRegister.getAllInventoryItems();
    inventoryItems = inventoryRegister.getInventoryItems();
    loadInventory(mode);
  }

  public VBox createScene() {
    return this;
  }
}