package ctbrec.ui;

import java.awt.AWTException;
import java.awt.Desktop;
import java.awt.MenuItem;
import java.awt.PopupMenu;
import java.awt.SystemTray;
import java.awt.Toolkit;
import java.awt.TrayIcon;
import java.awt.TrayIcon.MessageType;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Map;

import javax.swing.SwingUtilities;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import ctbrec.Config;
import ctbrec.OS;
import ctbrec.event.EventBusHolder;
import ctbrec.io.StreamRedirector;
import ctbrec.recorder.Recorder;
import ctbrec.ui.controls.Dialogs;
import javafx.application.Platform;
import javafx.geometry.Insets;
import javafx.scene.control.Alert;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;

public class DesktopIntegration {

    private DesktopIntegration() {}

    private static final Logger LOG = LoggerFactory.getLogger(DesktopIntegration.class);

    private static SystemTray tray;
    private static TrayIcon trayIcon;
    private static Recorder recorder;
    private static Stage primaryStage;

    public static void open(String uri) {
        try {
            CamrecApplication.hostServices.showDocument(uri);
            return;
        } catch (Exception e) {
            LOG.debug("Couldn't open URL with host services {}", uri);
        }

        // opening with HostServices failed, now try Desktop
        try {
            Desktop.getDesktop().browse(new URI(uri));
            return;
        } catch (Exception e) {
            LOG.debug("Couldn't open URL with Desktop {}", uri);
        }

        // try external helpers
        var externalHelpers = new String[] { "kde-open5", "kde-open", "gnome-open", "xdg-open" };
        var rt = Runtime.getRuntime();
        for (String helper : externalHelpers) {
            try {
                rt.exec(helper + " " + uri);
                return;
            } catch (IOException e) {
                LOG.debug("Couldn't open URL with {} {}", helper, uri);
            }
        }

        // all attempts failed, show a dialog with URL at least
        Alert info = new AutosizeAlert(Alert.AlertType.ERROR);
        info.setTitle("Open URL");
        info.setContentText("Couldn't open URL");
        var pane = new BorderPane();
        pane.setTop(new Label());
        var urlField = new TextField(uri);
        urlField.setPadding(new Insets(10));
        urlField.setEditable(false);
        pane.setCenter(urlField);
        info.getDialogPane().setExpandableContent(pane);
        info.getDialogPane().setExpanded(true);
        info.show();
    }

    public static void open(File f) {
        try {
            Desktop.getDesktop().open(f);
            return;
        } catch (Exception e) {
            LOG.debug("Couldn't open file with Desktop {}", f);
        }

        // try external helpers
        var externalHelpers = new String[] { "kde-open5", "kde-open", "gnome-open", "xdg-open" };
        var rt = Runtime.getRuntime();
        for (String helper : externalHelpers) {
            try {
                rt.exec(helper + " " + f.getAbsolutePath());
                return;
            } catch (IOException e) {
                LOG.debug("Couldn't open file with {} {}", helper, f);
            }
        }

        // all attempts failed, show a dialog with path at least
        Alert info = new AutosizeAlert(Alert.AlertType.ERROR);
        info.setTitle("Open file");
        info.setContentText("Couldn't open file");
        var pane = new BorderPane();
        pane.setTop(new Label());
        var urlField = new TextField(f.toString());
        urlField.setPadding(new Insets(10));
        urlField.setEditable(false);
        pane.setCenter(urlField);
        info.getDialogPane().setExpandableContent(pane);
        info.getDialogPane().setExpanded(true);
        info.show();
    }

    public static void notification(String title, String header, String msg) {
        if (OS.getOsType() == OS.TYPE.LINUX) {
            notifyLinux(title, header, msg);
        } else if (OS.getOsType() == OS.TYPE.WINDOWS) {
            notifyWindows(title, header, msg);
        } else if (OS.getOsType() == OS.TYPE.MAC) {
            notifyMac(title, header, msg);
        } else {
            // unknown system, try systemtray notification anyways
            notifySystemTray(title, header, msg);
        }
    }

    private static void notifyLinux(String title, String header, String msg) {
        try {
            Process p = Runtime.getRuntime().exec(new String[] {
                    "notify-send",
                    "-u", "normal",
                    "-t", "5000",
                    "-a", title,
                    header,
                    msg.replace("-", "\\\\-").replace("\\s", "\\\\ "),
                    "--icon=dialog-information"
            });
            new Thread(new StreamRedirector(p.getInputStream(), System.out)).start(); // NOSONAR
            new Thread(new StreamRedirector(p.getErrorStream(), System.err)).start(); // NOSONAR
        } catch (NullPointerException e) {
            // can happen at start, ignore
        } catch (IOException e1) {
            LOG.error("Notification failed", e1);
        }
    }

    private static void notifyWindows(String title, String header, String msg) {
        notifySystemTray(title, header, msg);
    }

    private static void notifyMac(String title, String header, String msg) {
        notifySystemTray(title, header, msg);
    }

    private static synchronized void notifySystemTray(String title, String header, String msg) {
        if (SystemTray.isSupported()) {
            createTrayIcon(primaryStage);
            trayIcon.displayMessage(header, msg, MessageType.INFO);
        } else {
            LOG.error("SystemTray notifications not supported by this OS");
        }
    }

    public static void minimizeToTray(Stage primaryStage) {
        Platform.setImplicitExit(false);
        boolean supported = createTrayIcon(primaryStage);
        if (supported) {
            primaryStage.hide();
        }
    }

    private static boolean createTrayIcon(Stage stage) {
        if (SystemTray.isSupported()) {
            if (tray == null) {
                String title = CamrecApplication.title;
                tray = SystemTray.getSystemTray();
                var image = Toolkit.getDefaultToolkit().createImage(DesktopIntegration.class.getResource("/icon64.png"));

                PopupMenu menu = createTrayContextMenu(stage);
                trayIcon = new TrayIcon(image, title, menu);
                trayIcon.setImageAutoSize(true);
                trayIcon.setToolTip(title);
                try {
                    tray.add(trayIcon);
                } catch (AWTException e) {
                    LOG.error("Couldn't add tray icon", e);
                }
                trayIcon.addMouseListener(new MouseAdapter() {
                    @Override
                    public void mousePressed(MouseEvent e) {
                        if (SwingUtilities.isLeftMouseButton(e)) {
                            toggleVisibility(stage);
                        }
                    }
                });
            }
            return true;
        } else {
            LOG.error("SystemTray notifications not supported by this OS");
            return false;
        }
    }

    private static PopupMenu createTrayContextMenu(Stage stage) {
        var menu = new PopupMenu();
        var show = new MenuItem("Show");
        show.addActionListener(evt -> restoreStage(stage));
        menu.add(show);
        menu.addSeparator();
        var pauseRecording = new MenuItem("Pause recording");
        pauseRecording.addActionListener(evt -> {
            try {
                recorder.pause();
            } catch (InvalidKeyException | NoSuchAlgorithmException | IOException e) {
                Dialogs.showError(stage.getScene(), "Pausing recording", "Pausing of the recorder failed", e);
            }
        });
        menu.add(pauseRecording);
        var resumeRecording = new MenuItem("Resume recording");
        resumeRecording.addActionListener(evt -> {
            try {
                recorder.resume();
            } catch (InvalidKeyException | NoSuchAlgorithmException | IOException e) {
                Dialogs.showError(stage.getScene(), "Resuming recording", "Resuming of the recorder failed", e);
            }
        });
        menu.add(resumeRecording);
        menu.addSeparator();
        var exit = new MenuItem("Exit");
        exit.addActionListener(evt -> exit());
        menu.add(exit);
        return menu;
    }

    private static void toggleVisibility(Stage stage) {
        if (stage.isShowing()) {
            Platform.runLater(stage::hide);
        } else {
            restoreStage(stage);
        }
    }

    private static void restoreStage(Stage stage) {
        Platform.runLater(() -> {
            stage.setX(Config.getInstance().getSettings().windowX);
            stage.setY(Config.getInstance().getSettings().windowY);
            LOG.debug("Restoring window location {},{}", stage.getX(), stage.getY());
            stage.setIconified(false);
            stage.show();
            stage.toFront();
        });
    }

    private static void exit() {
        EventBusHolder.BUS.post(Map.of("event", "shutdown"));
    }

    public static void setRecorder(Recorder recorder) {
        DesktopIntegration.recorder = recorder;
    }

    public static void setPrimaryStage(Stage primaryStage) {
        DesktopIntegration.primaryStage = primaryStage;
    }
}
