package de.renew.windowmanagement;

import java.awt.BorderLayout;
import java.awt.Font;
import java.awt.GraphicsEnvironment;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Optional;
import javax.swing.JLabel;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JSpinner;
import javax.swing.SpinnerNumberModel;

import org.apache.log4j.Logger;

import de.renew.plugin.Loader;
import de.renew.plugin.PluginManager;
import de.renew.propertymanagement.prop.ConfigurablePropertyManager;
import de.renew.propertymanagement.prop.PropertySaveFailedException;


/**
 * This class acts as a helper for the window management plugin to add items to the system menu,
 * realizing UI scaling functionality.
 */
public class UIScaleMenuExtender {
    private static final Logger LOGGER = Logger.getLogger(UIScaleMenuExtender.class);

    private static final String UI_SCALE_PROPERTY = "sun.java2d.uiScale";

    private static final String ITEM_TEXT = "UI Scale";
    private static final String WARNING_TEXT =
        "Fractional scaling values may be ignored. Try e.g \"200\" (for 200% scaling) instead.";
    private static final String INPUT_TITLE = "Set UI Scale (in %)";

    private static final String ERROR_TITLE = "Error";
    private static final String ERROR_TEXT = "Saving properties failed.";

    private static final String SUCCESS_TITLE = "Restart required";
    private static final String SUCCESS_MESSAGE =
        "Properties saved. A restart of Renew is required to apply the changes. Do you want to restart now?";

    /**
     * Initializes all menu items for UI scaling adjustments. Opens an input dialog for a custom scale value
     * and saves it as a user property. If it was saved successfully, it will restart Renew on request.
     *
     * @return The collection of {@code JMenuItem} objects
     */
    public static Collection<JMenuItem> createMenus() {
        List<JMenuItem> items = new ArrayList<>();
        JMenuItem item = new JMenuItem(ITEM_TEXT);
        item.addActionListener(event -> {
            double currentUIScale = Double.parseDouble(getUIScaleProperty());

            JSpinner spinner =
                new JSpinner(new SpinnerNumberModel((int) (currentUIScale * 100), 50, 500, 25));
            spinner.setEditor(new JSpinner.NumberEditor(spinner, "#"));

            JLabel warning = new JLabel(WARNING_TEXT);
            warning.setFont(warning.getFont().deriveFont(Font.ITALIC));

            JPanel panel = new JPanel(new BorderLayout(5, 5));
            panel.add(spinner, BorderLayout.NORTH);
            panel.add(warning, BorderLayout.SOUTH);

            int result = JOptionPane.showConfirmDialog(
                null, panel, INPUT_TITLE, JOptionPane.OK_CANCEL_OPTION, JOptionPane.PLAIN_MESSAGE);

            if (result != JOptionPane.OK_OPTION) {
                return;
            }
            double customUIScale = ((Number) spinner.getValue()).doubleValue() / 100;

            if (!setUIScaleProperty(Double.toString(customUIScale))) {
                JOptionPane
                    .showMessageDialog(null, ERROR_TEXT, ERROR_TITLE, JOptionPane.ERROR_MESSAGE);
                return;
            }
            int result2 = JOptionPane
                .showConfirmDialog(null, SUCCESS_MESSAGE, SUCCESS_TITLE, JOptionPane.YES_NO_OPTION);
            if (result2 == JOptionPane.YES_OPTION) {
                Thread restart = new Thread(() -> {
                    try {
                        Loader.startNewRenewInstance();
                    } catch (IOException e) {
                        LOGGER.error("Restart of Renew failed.", e);
                    }
                });
                Runtime.getRuntime().addShutdownHook(restart);
                PluginManager.getInstance().stop();
            }
        });
        items.add(item);
        return items;
    }

    /**
     * Persists the current value of the UI scale property using the {@link ConfigurablePropertyManager}.
     *
     * @param customUIScale The property to be persisted.
     *
     * @return {@code true} if the property was successfully persisted, {@code false} otherwise.
     */
    private static boolean setUIScaleProperty(String customUIScale) {
        ConfigurablePropertyManager propertyManager = ConfigurablePropertyManager.getInstance();
        propertyManager.changeProperty(UI_SCALE_PROPERTY, customUIScale);
        try {
            propertyManager.saveProperties(true); // we do not want to use installation properties
        } catch (PropertySaveFailedException e) {
            LOGGER.error(e.getMessage(), e);
            return false;
        }
        return true;
    }

    /**
     * Retrieves the current value of the UI scale property from the {@link ConfigurablePropertyManager}.
     *
     * @return The current value of the UI scale property, or {@code "100"} if the property is not set or unavailable.
     */
    private static String getUIScaleProperty() {
        try {
            //            loadProperties() already executed on start
            Optional<String> prop = ConfigurablePropertyManager.getInstance()
                .getCurrentValueForProperty(UI_SCALE_PROPERTY);
            if (prop.isPresent()) {
                return prop.get();
            }
        } catch (NoSuchElementException ignored) {
        }
        return Double.toString(getAppliedUIScale());
    }

    /**
     * Retrieves the current UI scale factor based on Java's internal view of the display scaling. Closely
     * matching the actual transformation java uses for rendering.
     *
     * @return The current UI scale factor
     */
    public static double getAppliedUIScale() {
        return GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice()
            .getDefaultConfiguration().getDefaultTransform().getScaleX();
    }
}
