import java.util.*;
import java.io.File;
import java.io.FileNotFoundException;
import java.util.Scanner;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.geometry.Insets;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.GridPane;
import javafx.stage.Stage;
import javafx.scene.control.TextArea;

/**
 * A JavaFX GUI that simulates a small shop. 
 * Items are loaded from a CSV file, and the user can "buy" items using 
 * 
 * @author Henry Curtis
 * @version 08/10/2025
 */
public class ShopperGUI extends Application {
    private float totalCost = 0f; // Starting totalCost balance
    private Stack<Item> basket = new Stack<>(); // Stores purchased items (LIFO for undo)
    private ArrayList<ArrayList<String>> menuItems = new ArrayList<>(); // Parsed CSV rows
    private ArrayList<String> validCategories = new ArrayList<>(); // List of unique categories

    // GUI components
    private Label totalCostDisplay = new Label(String.format("%.2f dollary doos", totalCost));
    private TextArea textOutput = new TextArea();
    private GridPane pane = new GridPane();
    
    /**
     * Initializes the JavaFX stage and layout, then populates it with shop info.
     */
    @Override
    public void start(Stage stage) {
        System.out.print("\u000C"); // Clears console output

        // Layout setup
        pane.setPadding(new Insets(10));
        pane.setMinSize(300, 300);
        pane.setVgap(0);
        pane.setHgap(0);
        pane.setGridLinesVisible(false);

        // Scene setup
        Scene scene = new Scene(pane, 500, 350);
        stage.setTitle("Shopper GUI");
        stage.setScene(scene);
        stage.show();

        // Load items and build interface
        buildGUI();
    }
    
    /**
     * Reads configuration values from "Config.txt" into a HashMap.
     * If useConfig=false, the file is missing, or *any* invalid/missing entry is found,
     * all values are discarded and default layout values are used.
     */
    private Map<String, Integer> loadConfig() {
        Map<String, Integer> config = new HashMap<>();
        File cfgFile = new File("Config.txt");
        boolean useConfig = false;
        boolean invalidFound = false;
    
        try (Scanner scan = new Scanner(cfgFile)) {
            while (scan.hasNextLine()) {
                String line = scan.nextLine().trim();
                if (line.isEmpty() || line.startsWith("#")) {
                    continue; // Skip empty lines/comments
                }
    
                String[] parts = line.split("=");
                if (parts.length == 2) {
                    String key = parts[0].trim();
                    String value = parts[1].trim();
                    if (key.equalsIgnoreCase("useConfig")) {
                        // Handle useConfig separately
                        if (value.equals("false") || value.equals("true")) {
                            useConfig = Boolean.parseBoolean(value);
                        } else {
                            System.out.println("Debug: Invalid boolean for useConfig → forcing defaults.");
                            invalidFound = true;
                        }
                    } else {
                        // Handle numeric entries for config map
                        try {
                            config.put(key, Integer.parseInt(value));
                        } catch (NumberFormatException e) {
                            System.out.println("Debug: Invalid number for " + key + " → forcing defaults.");
                            invalidFound = true;
                        }
                    }
                } else {
                    System.out.println("Debug: Malformed line (missing = or too many = are present): " + line);
                    invalidFound = true;
                }
            }
        } catch (FileNotFoundException e) {
            System.out.println("Debug: Config.txt not found → using defaults.");
            invalidFound = true;
        }
    
        // --- Define required keys ---
        List<String> requiredKeys = Arrays.asList(
            "textOutputY", 
            "textOutputX", 
            "buttonYOffset", 
            "initialButtonYOffset",
            "largestCategorySize", 
            "menuItemButtonXOffset", 
            "menuItemButtonYOffset",
            "controlButtonXOffset", 
            "controlButtonYOffset"
        );
    
        // --- Check for missing keys ---
        for (String key : requiredKeys) {
            if (!config.containsKey(key)) {
                System.out.println("Debug: Missing key in config: " + key);
                invalidFound = true;
            }
        }
    
        // --- Fallback if anything invalid/missing ---
        if (!useConfig || invalidFound) {
            if(!useConfig){
                System.out.println("Debug: Config disabled → reverting to defaults.");
            }
            if(invalidFound) {
                System.out.println("Debug: Invalid/missing entry detected in config → reverting to defaults.");
            }
            config.clear();
    
            // Default safe layout
            config.put("textOutputY", 1);
            config.put("textOutputX", validCategories.size());
            config.put("buttonYOffset", 3);
            config.put("initialButtonYOffset", config.get("buttonYOffset"));
            config.put("largestCategorySize", 0);
            config.put("menuItemButtonXOffset", 0);
            config.put("menuItemButtonYOffset", 0);
            config.put("controlButtonXOffset", 0);
            config.put("controlButtonYOffset", 1);
        }
    
        System.out.println("Debug: useConfig=" + useConfig + ", invalidFound=" + invalidFound);
        return config;
    }

    /**
     * Reads "Menu_Items.txt" and stores each row as [ID, category, name, price].
     */
    public void loadMenuItems() {
        File menuFile = new File("Menu_Items.txt");
        
        int idNum = 0;
        
        // Parse each line
        try (Scanner scan = new Scanner(menuFile)) {
            while (scan.hasNextLine()) {
                String nextLine = scan.nextLine().replace("\u00A0", ""); // Remove non-breaking spaces
                
                if ( nextLine.isEmpty() || nextLine.startsWith("#")) {
                    continue;
                }
                
                String[] items = nextLine.trim().split(",");
                if (items.length == 3) {
                    ArrayList<String> row = new ArrayList<>();
                
                    String category = items[0].trim();
                    String name = items[1].trim();
                    String priceStr = items[2].trim();
                    float price = Float.parseFloat(priceStr);
                    
                    if (price <= 0) {
                        ++idNum;
                        System.out.println("Debug: skipping line " + idNum + " with invalid price: " + nextLine); // Debug output
                        continue; // Skip lines with negative prices
                    }
        
                    row.add(String.valueOf(++idNum));
                    row.add(category);
                    row.add(name);
                    row.add(String.valueOf(price));
                    
                    menuItems.add(row);
                } else {
                    ++idNum;
                    System.out.println("Debug: skipping malformed line " + idNum + " is malformed: " + nextLine); // Debug output
                    continue; // Skip malformed lines
                }
            }
            System.out.println("Debug: menuItems array = " + menuItems); // Debug output
            scan.close();
            
            // Collect distinct categories
            for (int i = 1; i < menuItems.size(); i++) {
                String category = menuItems.get(i).get(1).trim();
                if (!validCategories.contains(category)) {
                    validCategories.add(category);
                }
            }
            System.out.println("Debug: validCategories array = " + validCategories);
            System.out.println("Debug: validCategories size = " + validCategories.size());

        } catch (FileNotFoundException e) {
            System.out.println("File not found. Check the spelling and location of your file.");
        }        
        }

    /**
     * Builds the GUI:
     * - Loads menu items from CSV (using loadMenuItems function)
     * - Creates buttons for each category and item
     * - Adds totalCost display, text area, and control buttons (Undo, Clear, Finish).
     */
    public void buildGUI() {
        loadMenuItems(); // Populate menuItems from file
        Map<String, Integer> config = loadConfig(); // Populates config map from file      
    
        ArrayList<String> completedCategories = new ArrayList<>(); // List of completed categories
    
        // Create item buttons by category
        for (String focusCategory : validCategories) {
            config.put("buttonYOffset", config.get("initialButtonYOffset")); // Reset buttonYOffset's value
            int categorySize = 0;
    
            for (ArrayList<String> row : menuItems) {
                int ID = Integer.parseInt(row.get(0));
                String category = row.get(1);
                String name = row.get(2);
                float price = Float.parseFloat(row.get(3));
    
                if (!category.equals(focusCategory)) {
                    continue; // Skip items not in this category
                }
    
                categorySize++;
                if (categorySize > config.get("largestCategorySize")) {
                    config.put("largestCategorySize", categorySize);
                }
    
                // Create button for this item
                Item menuItem = new Item(ID, name, price, category);
                Button itemButton = new Button(name + " ($" + price + ")");
                itemButton.setOnAction(e -> handlePurchase(menuItem));
    
                config.put("buttonYOffset", config.get("buttonYOffset") + 1); // +1 to buttonYOffset
    
                pane.add(
                    itemButton,
                    completedCategories.size() + config.get("menuItemButtonXOffset"), // X for itemButton
                    config.get("buttonYOffset") + config.get("menuItemButtonYOffset") // Y for itemButton
                );
            }
    
            completedCategories.add(focusCategory);
        }
    
        // Reset vertical offset for bottom elements
        config.put("buttonYOffset", config.get("initialButtonYOffset"));
    
        // Add totalCost display
        pane.add(totalCostDisplay, 0, 0);
    
        // Add text output (spans across categories)
        pane.add(textOutput, 0, 1, config.get("textOutputX"), config.get("textOutputY"));
        defaultTextOutput();
    
        // --- Control Buttons ---
        Button undoButton = new Button("Undo");
        undoButton.setOnAction(e -> handleUndo());
        pane.add(
            undoButton,
            0 + config.get("controlButtonXOffset"),                                                              // X for undoButton
            config.get("largestCategorySize") + config.get("controlButtonYOffset") + config.get("buttonYOffset") // Y for undoButton
        );
    
        Button clearButton = new Button("Clear");
        clearButton.setOnAction(e -> handleClear());
        pane.add(
            clearButton,
            1 + config.get("controlButtonXOffset"),                                                              // X for clearButton
            config.get("largestCategorySize") + config.get("controlButtonYOffset") + config.get("buttonYOffset") // Y for clearButton
        );
    
        Button finishButton = new Button("Finish");
        finishButton.setOnAction(e -> handleFinish());
        pane.add(
            finishButton,
            2 + config.get("controlButtonXOffset"),                                                              // X for finishButton
            config.get("largestCategorySize") + config.get("controlButtonYOffset") + config.get("buttonYOffset") // Y for finishButton
        );
    }

    /**
     * Attempts to purchase an item:
     * Deducts totalCost, adds item to basket, and updates display.
     */
    private void handlePurchase(Item item) {
        totalCost += item.getPrice();
        basket.push(item);
        totalCostDisplay.setText(String.format("%.2f dollary doos", totalCost));
        printText("Added to basket: " + item.getName() + " ($" + item.getPrice() + ")");
        scrollToBottom();
    }
    
    /**
     * Resets the text area to default instructions and prints menu items.
     */
    private void defaultTextOutput() {
        textOutput.clear();
        
        printText("Welcome to the shop!");
        printText("Undo will remove the most recent item from the basket.");
        printText("Clear will empty out the basket.");
        printText("Click finish when you are done to see your order summary!");
        
        printText("\n--- Menu Items ---");
        for (ArrayList<String> row : menuItems) {
            printText(String.join(" | ", row));
        }
        scrollToBottom();
    }

    /**
     * Removes the most recent item from basket and refunds its price.
     */
    private void handleUndo() {
        if (!basket.isEmpty()) {
            Item item = basket.pop();
            totalCost -= item.getPrice();
            totalCostDisplay.setText(String.format("%.2f dollary doos", totalCost));
            printText("Removed from basket: " + item.getName() + " ($" + item.getPrice() + ")");
            scrollToBottom();
        } else {
            printText("Nothing to undo.");
            scrollToBottom();
        }
    }

    /**
     * Clears all items from basket, refunds totalCost, and resets text area.
     */
    private void handleClear() {
        defaultTextOutput();
        if (!basket.isEmpty()) {
            while (!basket.isEmpty()) {
                basket.pop();
            }
            printText("Basket cleared. Refunded $" + String.format("%.2f", totalCost));
            totalCost = 0f;
            totalCostDisplay.setText(String.format("%.2f dollary doos", totalCost));
            scrollToBottom();
        } else {
            printText("Basket is already empty.");
            scrollToBottom();
        }
    }

    /**
     * Prints a condensed order summary of all items in the basket,
     * grouping identical items together and showing their total cost.
     */
    private void handleFinish() {
        defaultTextOutput();
    
        if (!basket.isEmpty()) {
            Map<String, Integer> itemCount = new LinkedHashMap<>(); // Keeps track of the items and the quantity they are bought in
            Map<String, Float> itemPrice = new LinkedHashMap<>(); // Keeps track of the items and their associated price
    
            // Count duplicates and store prices
            for (Item item : basket) {
                String name = item.getName();
                float price = item.getPrice();
    
                // Increase count if item exists, else add new entry
                if (itemCount.containsKey(name)) {
                    itemCount.put(name, itemCount.get(name) + 1);
                } else {
                    itemCount.put(name, 1);
                    itemPrice.put(name, price);
                }
            }
    
            // Print condensed receipt
            printText("\n--- Order Summary ---");
            for (String name : itemCount.keySet()) {
                int count = itemCount.get(name);
                float price = itemPrice.get(name);
                float total = count * price;
    
                printText(count + "x " + name + " ($" + String.format("%.2f", price) + " each) → $" + String.format("%.2f", total));
            }
    
            printText("Total: $" + String.format("%.2f", totalCost));
    
            // Clear basket and reset total
            basket.clear();
            totalCost = 0f;
            totalCostDisplay.setText(String.format("%.2f dollary doos", totalCost));
            scrollToBottom();
        } else {
            printText("Basket is empty. Nothing to checkout.");
            scrollToBottom();
        }
    }

    /**
     * Appends text to the text area.
     */
    private void printText(String text) {
        textOutput.setText(textOutput.getText() + text + "\n");
    }
    
    /**
     * Auto-scrolls the text area to the bottom
     */
    private void scrollToBottom() {
        textOutput.positionCaret(textOutput.getLength());
        textOutput.setScrollTop(Double.MAX_VALUE);
    }

}