package ru.kirillius.cooler.controller.UI;

import com.fazecast.jSerialComm.SerialPort;
import com.formdev.flatlaf.FlatDarculaLaf;
import com.intellij.uiDesigner.core.GridConstraints;
import com.intellij.uiDesigner.core.GridLayoutManager;
import com.intellij.uiDesigner.core.Spacer;
import ru.kirillius.cooler.controller.API.*;
import ru.kirillius.cooler.controller.Application;

import javax.swing.*;
import javax.swing.plaf.FontUIResource;
import javax.swing.text.StyleContext;
import java.awt.*;
import java.io.IOException;
import java.util.Locale;
import java.util.Objects;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;

public class MainForm extends JFrame {
    @SuppressWarnings("rawtypes")
    private final Queue<ReactiveComponentController> reactiveControllers = new ConcurrentLinkedQueue<>();
    private JPanel mainPanel;
    private JLabel temperature;
    private JLabel power;
    private JButton saveButton;
    private PortListComboBox portSelector;
    private JLabel status;
    private JSlider startTempSlider;
    private JLabel startTempLabel;
    private JSlider stopTempSlider;
    private JSlider maxTempSlider;
    private JSlider maxLevelSlider;
    private JSlider minLevelSlider;
    private JLabel stopTempLabel;
    private JLabel maxTempLabel;
    private JLabel maxLevelLabel;
    private JLabel minLevelLabel;
    private volatile MessageSystem messageSystem = null;
    private JProgressBar progress;

    public MainForm() {
        try {
            try {
                UIManager.setLookAndFeel(new FlatDarculaLaf());
            } catch (Exception ex) {
                UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
            }
            $$$setupUI$$$();
            setTitle(Application.title);
            try {
                Image icon = new ImageIcon(Objects.requireNonNull(getClass().getResource("/icon24.png"))).getImage();
                setIconImage(icon);
            } catch (Exception e) {
                MainForm.showError(e);
            }
            setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

            setContentPane(mainPanel);
            setSize(500, 500);
            setResizable(false);
            setLocationRelativeTo(null);
        } catch (Throwable t) {
            t.printStackTrace();
            showError(t);
            System.exit(1);
        }


        setListeners();
        startConnectionChecker();
        startTelemetryListeners();
        selectDefaultPort();
    }

    private static void showError(Throwable t) {
        t.printStackTrace();
        StringBuilder buffer = new StringBuilder();
        while (t != null) {
            buffer.append(t.getClass().getSimpleName());
            if (t.getMessage() != null) buffer.append(": ").append(t.getMessage());
            t = t.getCause();
            if (t != null) buffer.append("\r\nCaused by ");
        }

        JOptionPane.showMessageDialog(null, buffer.toString(), "Error", JOptionPane.ERROR_MESSAGE);
    }

    private void selectDefaultPort() {
        SerialPort selectedPort = portSelector.selectPortByName(Application.properties.getProperty("port"));
        if (selectedPort != null) {
            openConnection(selectedPort);
        }
    }

    private void startConnectionChecker() {
        asyncExecute(() -> {
            while (!Thread.interrupted()) {
                try {
                    //noinspection BusyWait
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    return;
                }
                var disconnected = (messageSystem == null || (System.currentTimeMillis() - messageSystem.getLastReceiveTime() > 10000L));
                SwingUtilities.invokeLater(() -> status.setText(disconnected ? "Disconnected" : "Connected"));
            }
        });
    }

    private void openConnection(SerialPort port) {
        try {
            if (messageSystem != null) {
                messageSystem.close();
                for (var controller : reactiveControllers) {
                    controller.destroy();
                }
                reactiveControllers.clear();
            }

            messageSystem = port != null ? new SerialMessageSystem(port) : new DummyMessageSystem();

            createReactiveControllers();
            asyncExecute(this::loadConfiguration);
        } catch (MessageException e) {
            showError(e);
        }
    }

    private void startTelemetryListeners() {
        asyncExecute(() -> {
            while (!Thread.interrupted()) {
                try {
                    //noinspection BusyWait
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    return;
                }
                if (messageSystem == null) continue;
                var temp = messageSystem.getValue(DataType.TEMP);
                temp.waitForReturn();
                if (temp.isSuccessful()) temperature.setText((0xFF & temp.getValue()) + " °C");
                var level = messageSystem.getValue(DataType.LEVEL);
                level.waitForReturn();
                if (level.isSuccessful()) power.setText(
                        (int) ((float) (level.getValue() & 0xFF) / 255f * 100) + " %"
                );
            }
        });
    }

    private void asyncExecute(Runnable runnable) {
        new Thread(() -> {
            try {
                runnable.run();
            } catch (Throwable t) {
                showError(t);
            }
        }).start();
    }

    private void createReactiveControllers() {
        reactiveControllers.add(
                new ReactiveSliderController(
                        DataType.MIN_LEVEL,
                        messageSystem,
                        minLevelSlider,
                        minLevelLabel,
                        "%",
                        0,
                        255,
                        2.55f
                )
        );
        reactiveControllers.add(
                new ReactiveSliderController(
                        DataType.MAX_LEVEL,
                        messageSystem,
                        maxLevelSlider,
                        maxLevelLabel,
                        "%",
                        0,
                        255,
                        2.55f
                )
        );
        reactiveControllers.add(
                new ReactiveSliderController(
                        DataType.START_TEMP,
                        messageSystem,
                        startTempSlider,
                        startTempLabel,
                        "°C",
                        0,
                        40,
                        1f
                )
        );
        reactiveControllers.add(
                new ReactiveSliderController(
                        DataType.STOP_TEMP,
                        messageSystem,
                        stopTempSlider,
                        stopTempLabel,
                        "°C",
                        0,
                        40,
                        1f
                )
        );
        reactiveControllers.add(
                new ReactiveSliderController(
                        DataType.MAX_TEMP,
                        messageSystem,
                        maxTempSlider,
                        maxTempLabel,
                        "°C",
                        25,
                        80,
                        1f
                )
        );
    }

    private void setProgress(int value) {
        SwingUtilities.invokeLater(() -> progress.setValue(value));
    }

    private void loadConfiguration() {
        saveButton.setEnabled(false);

        for (var controller : reactiveControllers) {
            controller.setLock(true);
        }
        var ms = messageSystem;
        setProgress(0);
        int i = 0;
        int size = reactiveControllers.size();
        for (var controller : reactiveControllers) {
            if (ms != messageSystem) return; //interrupt if MS was changed!
            controller.reloadValue();
            setProgress(100 * (++i) / size);
        }
        setProgress(100);

        for (var controller : reactiveControllers) {
            controller.setLock(false);
        }
        saveButton.setEnabled(true);
        setProgress(100);
    }

    private void setListeners() {
        portSelector.addListener(eventData -> {
            openConnection(eventData.getPort());
            Application.properties.setProperty("port", eventData.getText());
            try {
                Application.saveConfig();
            } catch (IOException e) {
                showError(e);
            }
        });
        saveButton.addActionListener(e -> {
            saveButton.setEnabled(false);
            Operation operation = messageSystem.sendCommand(ActionType.SAVE);
            operation.addCompleteListener(eventData -> {
                if (!eventData.isSuccessful()) showError(new RuntimeException("Save failed"));
                saveButton.setEnabled(true);
            });
        });
    }


    /**
     * Method generated by IntelliJ IDEA GUI Designer
     * >>> IMPORTANT!! <<<
     * DO NOT edit this method OR call it in your code!
     *
     * @noinspection ALL
     */
    private void $$$setupUI$$$() {
        mainPanel = new JPanel();
        mainPanel.setLayout(new GridLayoutManager(6, 1, new Insets(15, 15, 15, 15), -1, -1));
        final JPanel panel1 = new JPanel();
        panel1.setLayout(new GridLayoutManager(2, 2, new Insets(0, 0, 0, 0), -1, -1));
        mainPanel.add(panel1, new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, null, null, 0, false));
        temperature = new JLabel();
        Font temperatureFont = this.$$$getFont$$$(null, -1, 48, temperature.getFont());
        if (temperatureFont != null) temperature.setFont(temperatureFont);
        temperature.setHorizontalAlignment(0);
        temperature.setHorizontalTextPosition(0);
        temperature.setText("?");
        panel1.add(temperature, new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false));
        final JLabel label1 = new JLabel();
        Font label1Font = this.$$$getFont$$$(null, -1, 20, label1.getFont());
        if (label1Font != null) label1.setFont(label1Font);
        label1.setHorizontalAlignment(0);
        label1.setText("Температура");
        panel1.add(label1, new GridConstraints(1, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false));
        final JLabel label2 = new JLabel();
        Font label2Font = this.$$$getFont$$$(null, -1, 20, label2.getFont());
        if (label2Font != null) label2.setFont(label2Font);
        label2.setText("Мощность");
        panel1.add(label2, new GridConstraints(1, 1, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false));
        power = new JLabel();
        Font powerFont = this.$$$getFont$$$(null, -1, 48, power.getFont());
        if (powerFont != null) power.setFont(powerFont);
        power.setText("?");
        panel1.add(power, new GridConstraints(0, 1, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false));
        final JSeparator separator1 = new JSeparator();
        separator1.setOrientation(0);
        mainPanel.add(separator1, new GridConstraints(1, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_WANT_GROW, GridConstraints.SIZEPOLICY_FIXED, null, new Dimension(-1, 5), new Dimension(-1, 5), 0, false));
        final JSeparator separator2 = new JSeparator();
        separator2.setOrientation(0);
        mainPanel.add(separator2, new GridConstraints(3, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_WANT_GROW, GridConstraints.SIZEPOLICY_FIXED, null, new Dimension(-1, 5), new Dimension(-1, 5), 0, false));
        final JPanel panel2 = new JPanel();
        panel2.setLayout(new GridLayoutManager(1, 4, new Insets(0, 0, 0, 0), -1, -1));
        mainPanel.add(panel2, new GridConstraints(4, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, new Dimension(-1, 25), new Dimension(-1, 25), 0, false));
        saveButton = new JButton();
        saveButton.setEnabled(false);
        saveButton.setHorizontalAlignment(0);
        saveButton.setText("Схоронить");
        panel2.add(saveButton, new GridConstraints(0, 3, 1, 1, GridConstraints.ANCHOR_EAST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false));
        portSelector = new PortListComboBox();
        panel2.add(portSelector, new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false));
        final Spacer spacer1 = new Spacer();
        panel2.add(spacer1, new GridConstraints(0, 2, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_WANT_GROW, 1, null, null, null, 0, false));
        status = new JLabel();
        Font statusFont = this.$$$getFont$$$(null, Font.BOLD, 14, status.getFont());
        if (statusFont != null) status.setFont(statusFont);
        status.setHorizontalTextPosition(0);
        status.setText("Not connected");
        panel2.add(status, new GridConstraints(0, 1, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false));
        final JPanel panel3 = new JPanel();
        panel3.setLayout(new GridLayoutManager(5, 4, new Insets(0, 0, 0, 0), -1, -1));
        mainPanel.add(panel3, new GridConstraints(2, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, null, null, 0, false));
        final JLabel label3 = new JLabel();
        label3.setHorizontalAlignment(2);
        label3.setText("Температура включения");
        panel3.add(label3, new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, new Dimension(200, 19), null, 0, false));
        startTempSlider = new JSlider();
        startTempSlider.setEnabled(false);
        panel3.add(startTempSlider, new GridConstraints(0, 1, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_WANT_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false));
        startTempLabel = new JLabel();
        startTempLabel.setText("   ");
        panel3.add(startTempLabel, new GridConstraints(0, 2, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false));
        final Spacer spacer2 = new Spacer();
        panel3.add(spacer2, new GridConstraints(0, 3, 5, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_WANT_GROW, 1, null, null, null, 0, false));
        final JLabel label4 = new JLabel();
        label4.setHorizontalAlignment(2);
        label4.setText("Температура отключения");
        panel3.add(label4, new GridConstraints(1, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, new Dimension(200, 19), null, 0, false));
        final JLabel label5 = new JLabel();
        label5.setHorizontalAlignment(2);
        label5.setText("Макс. мощность при температуре");
        panel3.add(label5, new GridConstraints(2, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, new Dimension(200, 19), null, 0, false));
        final JLabel label6 = new JLabel();
        label6.setHorizontalAlignment(2);
        label6.setText("Максимальная мощность");
        panel3.add(label6, new GridConstraints(3, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, new Dimension(200, 19), null, 0, false));
        final JLabel label7 = new JLabel();
        label7.setHorizontalAlignment(2);
        label7.setText("Минимальная мощность");
        panel3.add(label7, new GridConstraints(4, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, new Dimension(200, 19), null, 0, false));
        stopTempSlider = new JSlider();
        stopTempSlider.setEnabled(false);
        panel3.add(stopTempSlider, new GridConstraints(1, 1, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_WANT_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false));
        maxTempSlider = new JSlider();
        maxTempSlider.setEnabled(false);
        panel3.add(maxTempSlider, new GridConstraints(2, 1, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_WANT_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false));
        maxLevelSlider = new JSlider();
        maxLevelSlider.setEnabled(false);
        panel3.add(maxLevelSlider, new GridConstraints(3, 1, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_WANT_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false));
        minLevelSlider = new JSlider();
        minLevelSlider.setEnabled(false);
        panel3.add(minLevelSlider, new GridConstraints(4, 1, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_WANT_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false));
        stopTempLabel = new JLabel();
        stopTempLabel.setText("      ");
        panel3.add(stopTempLabel, new GridConstraints(1, 2, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false));
        maxTempLabel = new JLabel();
        maxTempLabel.setText("      ");
        panel3.add(maxTempLabel, new GridConstraints(2, 2, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false));
        maxLevelLabel = new JLabel();
        maxLevelLabel.setText("      ");
        panel3.add(maxLevelLabel, new GridConstraints(3, 2, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false));
        minLevelLabel = new JLabel();
        minLevelLabel.setText("      ");
        panel3.add(minLevelLabel, new GridConstraints(4, 2, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false));
        progress = new JProgressBar();
        progress.setForeground(new Color(-12154416));
        mainPanel.add(progress, new GridConstraints(5, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_WANT_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false));
    }

    /**
     * @noinspection ALL
     */
    private Font $$$getFont$$$(String fontName, int style, int size, Font currentFont) {
        if (currentFont == null) return null;
        String resultName;
        if (fontName == null) {
            resultName = currentFont.getName();
        } else {
            Font testFont = new Font(fontName, Font.PLAIN, 10);
            if (testFont.canDisplay('a') && testFont.canDisplay('1')) {
                resultName = fontName;
            } else {
                resultName = currentFont.getName();
            }
        }
        Font font = new Font(resultName, style >= 0 ? style : currentFont.getStyle(), size >= 0 ? size : currentFont.getSize());
        boolean isMac = System.getProperty("os.name", "").toLowerCase(Locale.ENGLISH).startsWith("mac");
        Font fontWithFallback = isMac ? new Font(font.getFamily(), font.getStyle(), font.getSize()) : new StyleContext().getFont(font.getFamily(), font.getStyle(), font.getSize());
        return fontWithFallback instanceof FontUIResource ? fontWithFallback : new FontUIResource(fontWithFallback);
    }

    /**
     * @noinspection ALL
     */
    public JComponent $$$getRootComponent$$$() {
        return mainPanel;
    }


}
