package ca.pfv.spmf.gui.visuals.histograms;

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.util.List;
import java.util.Map;
import java.util.Vector;

import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;

import ca.pfv.spmf.gui.Main;
import ca.pfv.spmf.gui.MainWindow;
import ca.pfv.spmf.gui.visuals.histograms.HistogramDistributionPanel.Order;

/*
 * Copyright (c) 2008-2024 Philippe Fournier-Viger
 *
 * This file is part of the SPMF DATA MINING SOFTWARE
 * (http://www.philippe-fournier-viger.com/spmf).
 *
 * SPMF is free software: you can redistribute it and/or modify it under the
 * terms of the GNU General Public License as published by the Free Software
 * Foundation, either version 3 of the License, or (at your option) any later
 * version.
 *
 * SPMF is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
 * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along with
 * SPMF. If not, see <http://www.gnu.org/licenses/>.
 */
/**
 * A window (JFrame) that display a histogram panel for showing some frequency
 * distribution to the user.
 * 
 * @author Philippe Fournier-Viger
 */
public class HistogramDistributionWindow extends JFrame {

	/** default serial UID */
	private static final long serialVersionUID = 4751136799631193209L;

	/** The scroll pane */
	private JScrollPane scrollPane;
	
	/** The histogram panel being displayed */
	private HistogramDistributionPanel histogram;
	
	/** Default window width */
	private static final int DEFAULT_WIDTH = 800;
	
	/** Default window height */
	private static final int DEFAULT_HEIGHT = 400;
	
	/** Default number of columns for the bar width text field */
	private static final int TEXT_FIELD_COLUMNS = 3;
	
	/** Minimum histogram height */
	private static final int MIN_HISTOGRAM_HEIGHT = 200;

	/**
	 * A constructor that takes the name of the text file and creates the panel
	 * 
	 * @param runAsStandalone set to true if this window is executed by itself as a
	 *                        standalone program, otherwise false
	 * @param yValues         the values for the Y axis
	 * @param xLabels         the corresponding values for the X axis
	 * @param title           the title of the chart
	 * @param showBarLabels   A boolean that indicate if labels for each bar should
	 *                        be displayed on the X axis
	 * @param showBarValues   A boolean that indicate if values for each bar should
	 *                        be displayed above them
	 * @param xAxisName       The x axis name
	 * @param yAxisName       The y axis name
	 * @param mapItemToString A map that associate String to values on the X axis
	 * @param order           the desired sorting order for the x axis
	 */
	public HistogramDistributionWindow(boolean runAsStandalone, int[] yValues, int[] xLabels, String title,
			boolean showBarLabels, boolean showBarValues, String xAxisName, String yAxisName,
			Map<Integer, String> mapItemToString, Order order) {

		// Create a Histogram object with the values and labels arrays
		histogram = new HistogramDistributionPanel(yValues, xLabels, title, showBarLabels,
				showBarValues, xAxisName, yAxisName, mapItemToString, order);

		initializeWindow(runAsStandalone, order);
	}

	/**
	 * A constructor that takes data records and an index
	 * 
	 * @param runAsStandalone set to true if this window is executed by itself as a
	 *                        standalone program, otherwise false
	 * @param data            a list of data records
	 * @param index           the attribute of interest in the list of data records
	 * @param title           the title of the histogram
	 * @param xAxisName       The x axis name
	 * @param yAxisName       The y axis name
	 * @param order           the desired sorting order for the x axis
	 */
	public HistogramDistributionWindow(boolean runAsStandalone, Vector<List<Object>> data, int index, String title,
			String xAxisName, String yAxisName, Order order) {
		super();

		// Create a chart panel object and add it to the frame
		histogram = new HistogramDistributionPanel(data, DEFAULT_WIDTH - 20, DEFAULT_HEIGHT + 150, index, title, true, 
				true, xAxisName, yAxisName, order);

		initializeWindow(runAsStandalone, order);
	}

	/**
	 * Initialize the window with all components and layouts
	 * 
	 * @param runAsStandalone whether to run as standalone application
	 * @param order           the initial sort order for the histogram
	 */
	private void initializeWindow(boolean runAsStandalone, Order order) {
		// Set the window icon
		setIconImage(Toolkit.getDefaultToolkit()
				.getImage(MainWindow.class.getResource("/ca/pfv/spmf/gui/icons/histogram.png")));
		
		// Set the title of the frame
		setTitle("SPMF Histogram Viewer " + Main.SPMF_VERSION);

		// Set the default close operation
		if (runAsStandalone) {
			this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		}

		// Set the layout manager
		this.setLayout(new BorderLayout());
		JPanel contentPane = (JPanel) this.getContentPane();
		contentPane.setLayout(new BoxLayout(contentPane, BoxLayout.Y_AXIS));

		// Create and configure scroll pane
		scrollPane = new JScrollPane(histogram);
		scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
		contentPane.add(scrollPane);

		// Create control panels
		JPanel controlPanel = createControlPanel(order);
		JPanel saveOptionsPanel = createSaveOptionsPanel();

		// Create bottom panel containing all controls
		JPanel bottomPanel = createBottomPanel(controlPanel, saveOptionsPanel);
		contentPane.add(bottomPanel);

		// Add component listener to the content pane to track window resizing
		addContentPaneResizeListener(contentPane);

		// Set the size and location of the JFrame
		setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);
		setPreferredSize(new Dimension(DEFAULT_WIDTH, DEFAULT_HEIGHT));
		setLocationRelativeTo(null);

		// Call the pack method on the JFrame after adding all the components
		pack();

		// Make the frame visible
		setVisible(true);
	}

	/**
	 * Create the control panel with bar width and sort order controls
	 * 
	 * @param order the initial sort order
	 * @return the configured control panel
	 */
	private JPanel createControlPanel(Order order) {
		JPanel controlPanel = new JPanel();
		controlPanel.setLayout(new FlowLayout(FlowLayout.CENTER));

		// Create bar width control
		JLabel labelWidth = new JLabel("Change bar width:");
		JTextField textFieldWidth = createBarWidthTextField();
		controlPanel.add(labelWidth);
		controlPanel.add(textFieldWidth);

		// Create sort order control
		JLabel labelSortXValues = new JLabel("Sort X axis by:");
		JComboBox<Order> sortComboBox = createSortComboBox(order);
		controlPanel.add(labelSortXValues);
		controlPanel.add(sortComboBox);

		return controlPanel;
	}

	/**
	 * Create the text field for controlling bar width
	 * 
	 * @return the configured text field
	 */
	private JTextField createBarWidthTextField() {
		JTextField textField = new JTextField(String.valueOf(histogram.getBarWidth()));
		textField.setColumns(TEXT_FIELD_COLUMNS);
		textField.setMaximumSize(textField.getPreferredSize());

		// Add action listener for Enter key
		textField.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				updateBarWidth(textField.getText());
			}
		});

		return textField;
	}

	/**
	 * Update the bar width based on user input
	 * 
	 * @param widthText the text input from user
	 */
	private void updateBarWidth(String widthText) {
		try {
			int newWidth = Integer.parseInt(widthText.trim());
			if (newWidth > 0) {
				histogram.setBarWidth(newWidth);
				refreshHistogramDisplay();
			}
		} catch (NumberFormatException ex) {
			// Invalid input - ignore or show error message
		}
	}

	/**
	 * Create the combo box for sorting order selection
	 * 
	 * @param initialOrder the initial sort order
	 * @return the configured combo box
	 */
	private JComboBox<Order> createSortComboBox(Order initialOrder) {
		JComboBox<Order> sortComboBox = new JComboBox<>(Order.values());
		sortComboBox.setSelectedItem(initialOrder);

		sortComboBox.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				Order selectedOrder = (Order) sortComboBox.getSelectedItem();
				if (selectedOrder != null) {
					histogram.setSortOrder(selectedOrder);
					refreshHistogramDisplay();
				}
			}
		});

		return sortComboBox;
	}

	/**
	 * Create the panel containing save options buttons
	 * 
	 * @return the configured save options panel
	 */
	private JPanel createSaveOptionsPanel() {
		JPanel saveOptionsPanel = new JPanel();
		saveOptionsPanel.setLayout(new FlowLayout(FlowLayout.CENTER));

		// Create save as CSV button
		JButton buttonSaveCSV = createSaveCSVButton();
		saveOptionsPanel.add(buttonSaveCSV);

		// Create export as PNG button
		JButton buttonExportPNG = createExportPNGButton();
		saveOptionsPanel.add(buttonExportPNG);

		return saveOptionsPanel;
	}

	/**
	 * Create the button for saving histogram as CSV
	 * 
	 * @return the configured button
	 */
	private JButton createSaveCSVButton() {
		JButton buttonSave = new JButton("Save as CSV");
		buttonSave.setIcon(new ImageIcon(MainWindow.class.getResource("/ca/pfv/spmf/gui/icons/save.gif")));
		
		buttonSave.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				histogram.exportAsCSV("histogram.txt");
			}
		});

		return buttonSave;
	}

	/**
	 * Create the button for exporting histogram as PNG
	 * 
	 * @return the configured button
	 */
	private JButton createExportPNGButton() {
		JButton buttonExport = new JButton("Save as PNG");
		buttonExport.setIcon(new ImageIcon(MainWindow.class.getResource("/ca/pfv/spmf/gui/icons/save.gif")));
		
		buttonExport.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				histogram.exportAsImage("histogram.png");
			}
		});

		return buttonExport;
	}

	/**
	 * Create the bottom panel containing control and save panels
	 * 
	 * @param controlPanel     the control panel
	 * @param saveOptionsPanel the save options panel
	 * @return the configured bottom panel
	 */
	private JPanel createBottomPanel(JPanel controlPanel, JPanel saveOptionsPanel) {
		JPanel bottomPanel = new JPanel();
		bottomPanel.setLayout(new BoxLayout(bottomPanel, BoxLayout.Y_AXIS));

		// Add the control panel
		bottomPanel.add(controlPanel);
		bottomPanel.add(Box.createVerticalGlue());
		
		// Add the save options panel
		bottomPanel.add(saveOptionsPanel);

		// Set the alignment to center
		bottomPanel.setAlignmentX(Component.CENTER_ALIGNMENT);

		// Set maximum sizes to match preferred sizes
		saveOptionsPanel.setMaximumSize(saveOptionsPanel.getPreferredSize());
		controlPanel.setMaximumSize(controlPanel.getPreferredSize());
		bottomPanel.setMaximumSize(bottomPanel.getPreferredSize());

		return bottomPanel;
	}

	/**
	 * Add a component listener to the content pane to handle window resize events
	 * This ensures the histogram resizes properly both when increasing and decreasing
	 * 
	 * @param contentPane the content pane to monitor
	 */
	private void addContentPaneResizeListener(JPanel contentPane) {
		contentPane.addComponentListener(new ComponentAdapter() {
			@Override
			public void componentResized(ComponentEvent e) {
				handleWindowResize();
			}
		});
	}

	/**
	 * Handle the window resize event
	 * Updates the histogram size to match the available space
	 */
	private void handleWindowResize() {
		SwingUtilities.invokeLater(new Runnable() {
			@Override
			public void run() {
				// Get the actual size available in the scroll pane
				int availableHeight = scrollPane.getViewport().getHeight();
				int availableWidth = scrollPane.getViewport().getWidth();
				
				// Calculate the histogram width based on bar count and bar width
				int histogramWidth = histogram.getPreferredSize().width;
				
				// Use the larger of available width or calculated width for horizontal scrolling
				int finalWidth = Math.max(histogramWidth, availableWidth);
				
				// Use available height but enforce minimum
				int finalHeight = Math.max(availableHeight, MIN_HISTOGRAM_HEIGHT);
				
				// Create new dimension
				Dimension newSize = new Dimension(finalWidth, finalHeight);
				
				// Update histogram size - this is key for both growing and shrinking
				histogram.setSize(newSize);
				histogram.setPreferredSize(newSize);
				histogram.setMinimumSize(new Dimension(histogramWidth, MIN_HISTOGRAM_HEIGHT));
				histogram.setMaximumSize(new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE));
				
				// Force layout update
				histogram.revalidate();
				histogram.repaint();
				
				// Update scroll pane
				scrollPane.revalidate();
				scrollPane.repaint();
			}
		});
	}

	/**
	 * Refresh the histogram display after changes
	 */
	private void refreshHistogramDisplay() {
		SwingUtilities.invokeLater(new Runnable() {
			@Override
			public void run() {
				// Trigger a resize to recalculate everything
				handleWindowResize();
			}
		});
	}
}