SearchedItemPanel.java

package org.troy.capstone.ui_components.items;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import org.troy.capstone.constants.UISizeControl;
import org.troy.capstone.constants.TestFXId;
import org.troy.capstone.entities.Item;
import org.troy.capstone.interfaces.SearchedItemPanelInteractor;
import org.troy.capstone.utils.UIUtils;

import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.CacheHint;
import javafx.scene.control.Label;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;

/**
 * The {@code SearchedItemPanel} class represents a UI component that displays the details of a single item in the search results.
 */
public class SearchedItemPanel extends HBox{


    /** List of interactors to handle interactions with the item panel, such as adding the item to the {@code RecentlyViewedQueue} when the panel is clicked. */
    private final List<SearchedItemPanelInteractor> interactors = new ArrayList<>();

    /**
     * Date formatter for displaying the date added attribute of the item in a user-friendly format. The format used is "MMMM dd, yyyy" (e.g., "January 01, 2020").
     */
    private static final SimpleDateFormat dateAddedFormatter = new SimpleDateFormat("MMMM dd, yyyy");

    /** The container for the attributed image of the item, displaying the item's image along with any relevant attributes. */
    private final AttributedItemContainer attributedImage;  

    /** The ID of the item being displayed in this panel. Used for checking if the panel is in the recently viewed queue. */
    private final String itemId;

    /** Static variable to track if the first instance of {@code SearchedItemPanel} has been created, used to assign an ID to the name label of the first instance for clicking on the instance in tests. */
    public static boolean firstInstanceMade = false;

    /**
     * Creates a {@code SearchedItemPanel} for the given item, displaying its image and details in a structured layout.
     * The panel consists of a left side with the attributed image and a right side with textual details about the item.
     * It also includes styling such as borders and spacing to enhance the visual presentation of the item information.
     * 
     * @pre {@code item} should contain valid data for all attributes being displayed.
     * 
     * @param item The item whose details are being displayed in this panel, used to populate both the attributed image and the other details.
     */
    private SearchedItemPanel(Item item) {
        this.itemId = item.getId();

        //Set up the left side
        attributedImage = new AttributedItemContainer(item);

        //Set up the right side with the item details
        VBox rightPanel = makeRightPanel(item);
        
        //Add both sides to the HBox
        getChildren().addAll(attributedImage, rightPanel);
        setSpacing(20);
        setAlignment(Pos.TOP_LEFT);
                
        //Add padding inside the border
        setPadding(new Insets(UISizeControl.HEIGHT_PADDING.getValue(), UISizeControl.WIDTH_PADDING.getValue(), UISizeControl.HEIGHT_PADDING.getValue(), UISizeControl.WIDTH_PADDING.getValue()));
        
        //Optimize rendering performance
        setCache(true);
        setCacheHint(CacheHint.SPEED);
        setSnapToPixel(true);
    }

    /** Factory method to create a {@code SearchedItemPanel} instance.
     * @param item The item whose details are being displayed in this panel.
     * @return A new instance of {@code SearchedItemPanel}.
     */
    public static SearchedItemPanel create(Item item) {
        SearchedItemPanel panel = new SearchedItemPanel(item);
        UIUtils.setLineBorder(panel, 5, 2);
        return panel;
    }

    /** Sets a {@code SearchedItemPanelInteractor} to the panel to allow for interaction with the item panel
     * @pre {@code interactor} should be properly implemented to handle interactions with the item panel, and the {@code SearchedItemPanel} should be properly initialized to allow for setting the interactor.
     * @post The provided {@code interactor} is set to the {@code SearchedItemPanel}, allowing it to receive interaction events from the item panel. This enables functionality such as adding the item to the {@code RecentlyViewedQueue} when the panel is clicked.
     * @param interactor The {@code SearchedItemPanelInteractor} to set for handling interactions with the item panel.
     */
    public void addSearchedItemPanelInteractor(SearchedItemPanelInteractor interactor) {
        interactors.add(interactor);
        setOnMouseClicked(e -> interactors.forEach(i -> i.onItemSelected(itemId)));
    }

    /** Getter for the item ID of the item being displayed in this panel, used for checking if the panel is in the {@code RecentlyViewedQueue}. 
     * @return The ID of the item being displayed in this panel.
     */
    public String getItemId() {
        return itemId;
    }

    /**
     * Stops the asynchronous loading of the image in the {@code AttributedItemContainer}. This method can be called when the panel is no longer visible or needed, to free up resources and prevent unnecessary loading of images that are not being displayed.
     * 
     * @post The asynchronous image loading task for the {@code AttributedItemContainer} is stopped, preventing any further loading of the image that is not being displayed.
     */
    public void stopLoadingImage() {
        attributedImage.stopLoadingImage();
    }

    /**
     * Fills the right panel with some of the data from the item.
     * 
     * @pre item should contain valid data for all the attributes being displayed.
     *      {@code rightPanel} should be properly initialized to add the labels to.
     * 
     * @post {@code rightPanel} will contain labels displaying the name, publisher, category, price, rating, stock quantity, and date added for the item, with consistent styling and formatting.
     * @param item The {@code Item} whose data is being displayed in the right panel.
     * @return A {@code VBox} containing the labels with the item details, styled and formatted for display in the right panel of the {@code SearchedItemPanel}.
     */
    public static VBox makeRightPanel(Item item) {
        VBox rightPanel = new VBox(5);
        rightPanel.setAlignment(Pos.CENTER);
        //Name label done separately so we can style it
        Label nameLabel = new Label(item.getName());
        nameLabel.setStyle("-fx-font-weight: bold; -fx-font-size: 14;");
        nameLabel.setWrapText(true);
        nameLabel.setMaxWidth(UISizeControl.SEARCHED_ITEM_LABEL_MAX_WIDTH.getValue());
        nameLabel.setAlignment(Pos.CENTER_LEFT);
        if(!firstInstanceMade) {
            nameLabel.setId(TestFXId.FIRST_SEARCHED_ITEM_NAME_LABEL.getId());
            System.out.println("Setting ID for first searched item name label: " + nameLabel.getId());
            firstInstanceMade = true;
        }


        Label publisherLabel = createLabel("Publisher: " + item.getPublisher());
        
        Label categoryLabel = createLabel("Category: " + item.getCategory());
        
        Label priceLabel = createLabel("Price: $" + String.format("%.2f", item.getPrice()));
        
        Label ratingLabel = createLabel("Rating: " + item.getReviewScore() + "/5.0 (" + item.getReviewCount() + " reviews)");
        
        Label stockLabel = createLabel("Stock: " + item.getStockQuantity());
        
        Date dateAdded = item.getDateAdded();
        Label dateLabel = createLabel("Date Added: " + dateAddedFormatter.format(dateAdded));

        rightPanel.getChildren().addAll(
            nameLabel,
            publisherLabel,
            categoryLabel,
            priceLabel,
            ratingLabel,
            stockLabel,
            dateLabel
        );

        rightPanel.setId("rightPanel"+item.getId()); //Set an ID for testing purposes
        return rightPanel;
    }

    /**
     * Helper method to create a label with consistent styling for the item details in the right panel.
     * This method sets properties such as wrapping, maximum width, and alignment to ensure that the labels are displayed consistently.
     * 
     * @param text The text to display in the label.
     * @return A label with the specified text and consistent styling.
     */
    private static Label createLabel(String text) {
        Label label = new Label(text);
        label.setWrapText(true);
        label.setMaxWidth(UISizeControl.SEARCHED_ITEM_LABEL_MAX_WIDTH.getValue());
        label.setAlignment(Pos.CENTER_LEFT);
        return label;
    }

}