import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.util.Random;
import java.util.Set;
import java.util.TreeSet;

/**
 * Cards.
 * <p>
 * AI use summary: TODO -- describe any AI assistance used, per course policy.
 *
 * @author studentName (studentNumber)
 */
public class Solution1 extends JFrame implements ActionListener {

    private static final int DEFAULT_HAND_SIZE = 7;
    private static final int SMALL_HAND_SIZE = 5;
    private static final int LARGE_HAND_SIZE = 10;

    private CardPanel cardPanel;

    /**
     * Build the main window, its menu bar, and the card display panel.
     */
    public Solution1() {
        super("Cards");

        cardPanel = new CardPanel();

        setJMenuBar(createMenuBar());
        add(cardPanel, BorderLayout.CENTER);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        pack();
        setLocationRelativeTo(null);
    }

    /**
     * Build the File menu with the New, New 5, New 10, and Exit options.
     *
     * @return the fully assembled menu bar
     */
    private JMenuBar createMenuBar() {
        JMenuBar menuBar = new JMenuBar();
        JMenu fileMenu = new JMenu("File");

        addMenuItem(fileMenu, "New");
        addMenuItem(fileMenu, "New 5");
        addMenuItem(fileMenu, "New 10");
        fileMenu.addSeparator();
        addMenuItem(fileMenu, "Exit");

        menuBar.add(fileMenu);

        return menuBar;
    }

    /**
     * Create a menu item with the given label, register this frame as
     * its listener, and add it to the given menu.
     *
     * @param menu the menu to add the item to
     * @param label the text (and action command) of the new item
     */
    private void addMenuItem(JMenu menu, String label) {
        JMenuItem item = new JMenuItem(label);

        item.addActionListener(this);
        menu.add(item);
    }

    /**
     * Respond to a File menu selection by dealing a new hand of the
     * requested size, or by exiting the program.
     *
     * @param e the menu selection event
     */
    @Override
    public void actionPerformed(ActionEvent e) {
        String command = e.getActionCommand();

        if (command.equals("New")) {
            cardPanel.dealNewHand(DEFAULT_HAND_SIZE);
        } else if (command.equals("New 5")) {
            cardPanel.dealNewHand(SMALL_HAND_SIZE);
        } else if (command.equals("New 10")) {
            cardPanel.dealNewHand(LARGE_HAND_SIZE);
        } else if (command.equals("Exit")) {
            System.exit(0);
        }
    }

    /**
     * Panel that displays the current hand of cards along the bottom,
     * fanned out and centered, plus the single most recently clicked
     * card centered near the top of the window.
     */
    private static class CardPanel extends JPanel
            implements MouseListener, MouseMotionListener {

        private static final int MIN_VALUE = 1;
        private static final int MAX_VALUE = 20;
        private static final int CARD_WIDTH = 60;
        private static final int CARD_HEIGHT = 90;
        private static final int CARD_GAP = 24;
        private static final int RAISE_AMOUNT = 20;
        private static final int MARGIN = 20;
        private static final int PANEL_WIDTH = 700;
        private static final int PANEL_HEIGHT = 400;

        private int[] hand;
        private int hoveredIndex;
        private Integer topCard;

        /**
         * Create an empty card panel and register it for mouse events.
         */
        public CardPanel() {
            hand = new int[0];
            hoveredIndex = -1;
            topCard = null;

            setPreferredSize(new Dimension(PANEL_WIDTH, PANEL_HEIGHT));
            setBackground(new Color(0, 100, 0));
            addMouseListener(this);
            addMouseMotionListener(this);
        }

        /**
         * Deal a new hand of randomly chosen, non-duplicate card values,
         * clearing any previously clicked top card.
         *
         * @param count the number of cards to deal
         */
        public void dealNewHand(int count) {
            // generate 'count' unique random values in [MIN_VALUE, MAX_VALUE]
            // store them in ascending order
            // clear the top card and hover state
            // repaint the panel

            Set<Integer> chosenValues = new TreeSet<>();
            Random randomGenerator = new Random();

            while (chosenValues.size() < count) {
                int candidateValue = randomGenerator.nextInt(
                        MAX_VALUE - MIN_VALUE + 1) + MIN_VALUE;

                chosenValues.add(candidateValue);
            }

            hand = new int[chosenValues.size()];

            int index = 0;

            for (int value : chosenValues) {
                hand[index] = value;
                index++;
            }

            topCard = null;
            hoveredIndex = -1;
            repaint();
        }

        /**
         * Compute the on-screen bounds for a hand card at the given
         * index, raising it if it is currently the hovered card.
         *
         * @param index the position of the card within the sorted hand
         * @param total the total number of cards currently in the hand
         * @return the rectangle where the card should be drawn
         */
        private Rectangle getCardBounds(int index, int total) {
            int handWidth = CARD_GAP * (total - 1) + CARD_WIDTH;
            int startX = (getWidth() - handWidth) / 2;
            int cardX = startX + index * CARD_GAP;
            int cardY = getHeight() - CARD_HEIGHT - MARGIN;

            if (index == hoveredIndex) {
                cardY -= RAISE_AMOUNT;
            }

            return new Rectangle(cardX, cardY, CARD_WIDTH, CARD_HEIGHT);
        }

        /**
         * Compute the on-screen bounds for the top (clicked) card,
         * centered horizontally near the top of the panel.
         *
         * @return the rectangle where the top card should be drawn
         */
        private Rectangle getTopCardBounds() {
            int cardX = (getWidth() - CARD_WIDTH) / 2;
            int cardY = MARGIN;

            return new Rectangle(cardX, cardY, CARD_WIDTH, CARD_HEIGHT);
        }

        /**
         * Find which hand card, if any, contains the given point,
         * checking the topmost drawn (highest index) card first.
         *
         * @param point the point to test, in panel coordinates
         * @return the index of the topmost matching card, or -1 if none
         */
        private int findCardIndexAt(Point point) {
            int total = hand.length;

            for (int index = total - 1; index >= 0; index--) {
                Rectangle bounds = getCardBounds(index, total);

                if (bounds.contains(point)) {
                    return index;
                }
            }

            return -1;
        }

        /**
         * Remove the card at the given index from the hand, shifting
         * the remaining cards left while keeping them in ascending
         * order.
         *
         * @param index the index of the card to remove
         */
        private void removeCardAtIndex(int index) {
            int[] newHand = new int[hand.length - 1];
            int newIndex = 0;

            for (int oldIndex = 0; oldIndex < hand.length; oldIndex++) {
                if (oldIndex != index) {
                    newHand[newIndex] = hand[oldIndex];
                    newIndex++;
                }
            }

            hand = newHand;
        }

        /**
         * Draw a single card as a rounded rectangle with its value
         * centered inside it.
         *
         * @param g the graphics context to draw with
         * @param bounds the location and size of the card
         * @param value the number printed on the card
         */
        private void drawCard(Graphics g, Rectangle bounds, int value) {
            g.setColor(Color.WHITE);
            g.fillRoundRect(bounds.x, bounds.y, bounds.width,
                    bounds.height, 10, 10);

            g.setColor(Color.BLACK);
            g.drawRoundRect(bounds.x, bounds.y, bounds.width,
                    bounds.height, 10, 10);

            String label = Integer.toString(value);
            FontMetrics metrics = g.getFontMetrics();
            int labelX = bounds.x
                    + (bounds.width - metrics.stringWidth(label)) / 2;
            int labelY = bounds.y
                    + (bounds.height + metrics.getAscent()) / 2;

            g.drawString(label, labelX, labelY);
        }

        /**
         * Paint the current hand along the bottom of the panel and the
         * most recently clicked card near the top.
         *
         * @param g the graphics context to draw with
         */
        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);

            int total = hand.length;

            for (int index = 0; index < total; index++) {
                Rectangle bounds = getCardBounds(index, total);

                drawCard(g, bounds, hand[index]);
            }

            if (topCard != null) {
                Rectangle bounds = getTopCardBounds();

                drawCard(g, bounds, topCard);
            }
        }

        /**
         * Move the clicked hand card to the top-center position and
         * remove it from the hand.
         *
         * @param e the mouse click event
         */
        @Override
        public void mouseClicked(MouseEvent e) {
            int index = findCardIndexAt(e.getPoint());

            if (index != -1) {
                topCard = hand[index];
                removeCardAtIndex(index);
                hoveredIndex = -1;
                repaint();
            }
        }

        /**
         * Raise whichever hand card the cursor is currently over.
         *
         * @param e the mouse movement event
         */
        @Override
        public void mouseMoved(MouseEvent e) {
            int index = findCardIndexAt(e.getPoint());

            if (index != hoveredIndex) {
                hoveredIndex = index;
                repaint();
            }
        }

        /**
         * Lower any raised card once the cursor leaves the panel.
         *
         * @param e the mouse exit event
         */
        @Override
        public void mouseExited(MouseEvent e) {
            if (hoveredIndex != -1) {
                hoveredIndex = -1;
                repaint();
            }
        }

        /**
         * Unused; dragging does not affect the card display.
         *
         * @param e the mouse drag event
         */
        @Override
        public void mouseDragged(MouseEvent e) {
        }

        /**
         * Unused; presses alone do not affect the card display.
         *
         * @param e the mouse press event
         */
        @Override
        public void mousePressed(MouseEvent e) {
        }

        /**
         * Unused; releases alone do not affect the card display.
         *
         * @param e the mouse release event
         */
        @Override
        public void mouseReleased(MouseEvent e) {
        }

        /**
         * Unused; entering the panel alone does not raise a card.
         *
         * @param e the mouse entry event
         */
        @Override
        public void mouseEntered(MouseEvent e) {
        }
    }

    /**
     * Create and display the Cards window.
     *
     * @param args command-line arguments (unused)
     */
    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                Solution1 frame = new Solution1();

                frame.setVisible(true);
            }
        });
    }
}
