/*
 * Copyright (C) 2025-2026  Tim Zimmermann <tim@aglais.org>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program 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 Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.aglais.zms.ui.component;

import org.aglais.zms.ui.util.TextUtil;

import javax.swing.*;
import java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.geom.Path2D;
import java.util.ResourceBundle;

/**
 * A swing component that will draw the IUCN status for a species.
 */
public class IUCNStatus extends JPanel {

    /**
     * IUCN status
     * <p>
     * NE = Not evaluated
     * DD = Data Deficient
     * LC = Least Concern
     * NT = Near Threatened
     * VU = Vulnerable
     * EN = Endangered
     * CR = Critically Endangered
     * EW = Extinct in the Wild
     * EX = Extinct
     */
    public enum Status {
        NE(Color.BLACK, Color.WHITE, false),
        DD(Color.BLACK, Color.WHITE, false),
        LC(Color.decode("#006666"), Color.WHITE),
        NT(Color.decode("#006666"), Color.decode("#99cc99")),
        VU(Color.decode("#cc9900"), Color.WHITE),
        EN(Color.decode("#cc6633"), Color.decode("#ffcc99")),
        CR(Color.decode("#cc3333"), Color.decode("#ffcccc")),
        EW(Color.BLACK, Color.WHITE),
        EX(Color.BLACK, Color.decode("#cc3333"));

        private final Color background;
        private final Color textColor;
        private final boolean displayable;

        Status(Color background, Color textColor, boolean displayable) {
            this.background = background;
            this.textColor = textColor;
            this.displayable = displayable;
        }

        Status(Color background, Color textColor) {
            this(background, textColor, true);
        }

        /**
         * Get the background color used for drawing if this status is active
         *
         * @return The background color
         */
        public Color getBackground() {
            return background;
        }

        /**
         * Get the text color used for drawing if this status is active
         *
         * @return The text color
         */
        public Color getTextColor() {
            return textColor;
        }

        /**
         * Check if this status is displayable in the {@link IUCNStatus} component
         *
         * @return True if the status is part of the {@link IUCNStatus} component, false otherwise
         */
        public boolean isDisplayable() {
            return displayable;
        }
    }

    private Status status = null;
    private boolean showHeader = true;
    private boolean invisible = false;
    private final IUCNStatusCircle[] circles = new IUCNStatusCircle[7];
    private final JPanel[] spacers = new JPanel[2];

    /**
     * Create a new {@link IUCNStatus} object.
     */
    public IUCNStatus() {
        setLayout(new GridBagLayout());
        addComponents();
    }

    /**
     * Get the active status
     *
     * @return Currently active status
     */
    public Status getStatus() {
        return status;
    }

    /**
     * Set the active status
     *
     * @param status New active status
     */
    public void setStatus(Status status) {
        this.status = status;

        for (IUCNStatusCircle circle : circles) {
            circle.setActive(circle.getStatus() == status);
        }

        repaint();
    }

    /**
     * Determines whether the header should be displayed.
     *
     * @return true if the header is to be displayed, false otherwise
     */
    public boolean isShowHeader() {
        return showHeader;
    }

    /**
     * Configures whether the header should be displayed and updates the component layout accordingly.
     * If the header visibility changes, the layout is dynamically updated by removing all components
     * and re-adding them based on the new state.
     *
     * @param showHeader A boolean indicating whether the header should be shown (true) or hidden (false).
     */
    public void setShowHeader(boolean showHeader) {
        this.showHeader = showHeader;

        removeAll();
        addComponents();
        revalidate();
        repaint();
    }

    /**
     * Indicates whether the object is currently in an invisible state.
     *
     * @return true if the object is invisible, false otherwise
     */
    public boolean isInvisible() {
        return invisible;
    }

    /**
     * Sets the visibility state of the IUCN status component without resizing it.
     *
     * @param invisible A boolean value indicating whether the component should be invisible.
     *                  If true, the component becomes invisible.
     */
    public void setInvisible(boolean invisible) {
        this.invisible = invisible;

        repaint();
    }

    private void addIUCNStatusCircle(int column, Status status) {
        GridBagConstraints constraints = new GridBagConstraints();
        constraints.gridx = column;
        constraints.gridy = showHeader ? 2 : 0;
        constraints.fill = GridBagConstraints.BOTH;
        constraints.weightx = 1.0;
        constraints.weighty = 1.0;
        constraints.insets = new Insets(1, 1, 1, 1);

        IUCNStatusCircle circle = new IUCNStatusCircle(status, status == this.status);

        add(circle, constraints);

        circles[column] = circle;
    }

    private void addSpacer(int row) {
        GridBagConstraints constraints = new GridBagConstraints();
        constraints.gridx = 0;
        constraints.gridy = row;
        constraints.gridwidth = circles.length;
        constraints.fill = GridBagConstraints.BOTH;
        constraints.weightx = 1.0;
        constraints.weighty = 1.0;

        JPanel spacer = new JPanel();
        spacer.setOpaque(false);

        add(spacer, constraints);

        spacers[row] = spacer;
    }

    private void addComponents() {
        if (showHeader) {
            addSpacer(0);
            addSpacer(1);
        }

        addIUCNStatusCircle(0, Status.EX);
        addIUCNStatusCircle(1, Status.EW);
        addIUCNStatusCircle(2, Status.CR);
        addIUCNStatusCircle(3, Status.EN);
        addIUCNStatusCircle(4, Status.VU);
        addIUCNStatusCircle(5, Status.NT);
        addIUCNStatusCircle(6, Status.LC);
    }

    private void connectCircleWithText(Graphics g, IUCNStatusCircle circle, Point endText, int margin) {
        int sizeCircle = circle.getCircleSize();
        int posXLine = circle.getX() + (sizeCircle / 2);
        g.drawLine(posXLine, endText.y + margin, posXLine, circles[6].getY() - margin);
    }

    /**
     * Draw a horizontal bracket shape.
     * The shape was taken from
     * <a href="https://upload.wikimedia.org/wikipedia/commons/4/4b/Status_iucn3.1_CR.svg">Wikimedia commons</a>.
     *
     * @param g2    The graphics object for drawing
     * @param x     The x position to draw the shape at
     * @param y     The y position to draw the shape at
     * @param width The shape's target width
     */
    private void drawHorizontalBracket(Graphics2D g2, int x, int y, int width) {
        Path2D path = new Path2D.Double();

        path.moveTo(12.531, 6.059);
        path.curveTo(9.021, 6.059, 6.261, 6.303, 4.2510004, 6.79);
        path.curveTo(2.2410007, 7.277, 0.8310003, 8.1, 0.0010004044, 9.25);
        path.curveTo(0.0010004044, 7.66, 0.5600004, 6.44, 1.6810004, 5.59);
        path.curveTo(2.8020003, 4.7400002, 4.551, 4.15, 6.921, 3.8200002);
        path.curveTo(9.291, 3.4900002, 12.500999, 3.3270001, 16.521, 3.3270001);
        path.lineTo(34.920998, 3.3270001);
        path.curveTo(37.380997, 3.3270001, 39.340996, 3.2581, 40.781, 3.1200001);
        path.curveTo(42.221, 2.9819002, 43.531, 2.6590002, 44.690998, 2.15);
        path.curveTo(46.161, 1.3440001, 46.970997, 0.6300001, 47.111, 0.0);
        path.curveTo(47.819, 1.05, 49.151, 1.88, 51.101, 2.48);
        path.curveTo(53.051003, 3.08, 55.761, 3.323, 59.461002, 3.323);
        path.lineTo(80.261, 3.323);
        path.curveTo(86.231, 3.323, 90.181, 3.8209999, 92.061005, 4.8129997);
        path.curveTo(93.94101, 5.8049994, 94.991005, 7.243, 94.991005, 9.253);
        path.curveTo(93.941, 8.043, 92.481, 7.2130003, 90.591, 6.7530003);
        path.curveTo(88.701004, 6.293, 86.011, 6.0610003, 82.50101, 6.0610003);
        path.lineTo(61.301006, 6.0610003);
        path.curveTo(57.111008, 6.0610003, 53.901005, 5.8150005, 51.681007, 5.3220005);
        path.curveTo(49.46101, 4.8290005, 47.911007, 3.8820004, 47.031006, 2.4820006);
        path.curveTo(46.210007, 3.9220006, 44.621006, 4.882001, 42.251007, 5.352);
        path.curveTo(39.88101, 5.8219995, 36.441006, 6.059, 31.951008, 6.059);
        path.closePath();

        double scale = width / path.getBounds().getWidth();
        AffineTransform at = AffineTransform.getTranslateInstance(x, y);
        at.scale(scale, scale);

        Shape transformed = at.createTransformedShape(path);

        g2.draw(transformed);
        g2.fill(transformed);
    }

    @Override
    public void paintComponent(Graphics g) {
        super.paintComponent(g);

        if (!showHeader || invisible)
            return;

        Graphics2D g2 = (Graphics2D) g;

        g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

        // Draw the texts
        g.setColor(UIManager.getColor("Label.foreground"));

        ResourceBundle uiComponent = ResourceBundle.getBundle("ui_component");
        String[] textExtinct = uiComponent.getString("iucn_extinct").split("\\s+");
        String[] textThreatened = uiComponent.getString("iucn_threatened").split("\\s+");
        String[] textLeastConcern = uiComponent.getString("iucn_least_concern").split("\\s+");

        int height = spacers[0].getHeight();
        int posY = 0;
        int widthExtinct = (int) (spacers[0].getWidth() * 1.5);
        int posXExtinct = 0;
        int widthThreatened = circles[4].getX() + circles[4].getCircleSize() - circles[2].getX();
        int posXThreatened = circles[2].getX();
        int widthLeastConcern = circles[6].getCircleSize();
        int posXLeastConcern = circles[6].getX();

        // Get smallest font size
        Font fontExtinct = TextUtil.getBestFontSize(g, g.getFont(), widthExtinct, height, false, textExtinct);
        Font fontThreatened = TextUtil.getBestFontSize(g, g.getFont(), widthThreatened, height, false,
                textThreatened);
        Font fontLeastConcern = TextUtil.getBestFontSize(g, g.getFont(), widthLeastConcern, height, false,
                textLeastConcern);
        int fontSize = Math.min(fontExtinct.getSize(), Math.min(fontThreatened.getSize(), fontLeastConcern.getSize()));

        g.setFont(g.getFont().deriveFont((float) fontSize));
        Point endExtinct = TextUtil.drawStringCentered(g, widthExtinct, height, posXExtinct, posY, false,
                textExtinct);
        TextUtil.drawStringCentered(g, widthThreatened, height, posXThreatened, posY, true, textThreatened);
        Point endLeastConcern = TextUtil.drawStringCentered(g, widthLeastConcern, height, posXLeastConcern, posY,
                true, textLeastConcern);

        // Draw connections
        FontMetrics metrics = g.getFontMetrics(g.getFont());
        int lineHeight = metrics.getAscent() + metrics.getDescent();
        int margin = lineHeight / 2;

        connectCircleWithText(g, circles[0], endExtinct, margin);
        connectCircleWithText(g, circles[6], endLeastConcern, margin);

        int widthSymbol = circles[4].getX() + circles[4].getCircleSize() - circles[2].getX();
        int xSymbol = circles[2].getX();
        int ySymbol = spacers[1].getY();

        drawHorizontalBracket(g2, xSymbol, ySymbol, widthSymbol);
    }

    @Override
    protected void paintChildren(Graphics g) {
        if (invisible)
            return;

        super.paintChildren(g);
    }
}
