package de.duehl.basics.system;

/*
 * Copyright 2024 Christian Dühl. All rights reserved.
 *
 * This program is free software. You can redistribute it and/or
 * modify it under the same terms as perl:
 *
 * general:  http://dev.perl.org/licenses/
 * GPL:      http://dev.perl.org/licenses/gpl1.html
 * artistic: http://dev.perl.org/licenses/artistic.html
 */

import java.io.FileDescriptor;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.io.UnsupportedEncodingException;
import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.UnknownHostException;
import java.util.List;

import de.duehl.basics.collections.CollectionsHelper;
import de.duehl.basics.io.FileHelper;
import de.duehl.basics.text.Text;

import java.awt.Desktop;

/**
 * Eine Klasse mit System-Tools.
 *
 * @version 1.01     2024-12-07
 * @author Christian Dühl
 */

public class SystemTools {

    /**
     * Findet heraus, welches Betriebssystem verwendet wird.
     *
     * @return Betriebssytemart
     */
    public static OperationSystem determineOperationSystem() {
        String os = System.getProperty("os.name", "unknown system");
        //System.out.println("Operating system: " + os);

        OperationSystem ourSystem = OperationSystem.UNKNOWN;
        if ("Mac OS X".equals(os)) {
            ourSystem = OperationSystem.OSX;
        }
        else if (os.startsWith("Windows")) {   // "Windows XP"
            ourSystem = OperationSystem.WINDOWS;
        }

        return ourSystem;
    }

    /**
     * Lässt den aktuellen Thread die angegebene Zeit in Millisekunden schlafen.
     *
     * @param milliseconds
     *            Zeit in Millisekunden.
     */
    public static void sleep(long milliseconds) {
        try {
            Thread.sleep(milliseconds);
        }
        catch (InterruptedException e) {
            // do nothing
        }
    }

    /**
     * Lässt den aktuellen Thread die angegebene Zeit in Sekunden schlafen.
     *
     * @param seconds
     *            Zeit in Sekunden.
     */
    public static void sleepSeconds(long seconds) {
        sleep(1000L * seconds);
    }

    /**
     * Lässt den aktuellen Thread die angegebene Zeit in Minuten schlafen.
     *
     * @param minutes
     *            Zeit in Minuten.
     */
    public static void sleepMinutes(long minutes) {
        sleepSeconds(60L * minutes);
    }

    /** Setzt für die Ausgabe auf STDOUT die richtige Codepage für cmd.exe. */
    public static void setOutToCp850() {
        try {
            System.setOut(new PrintStream(new FileOutputStream(FileDescriptor.out), true, "CP850"));
        }
        catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
    }

//    /** Setzt für die Ausgabe auf STDOUT die richtige Codepage für cmd.exe. */
//    public static void setInToCp850() {
//        /*
//        FileInputStream stream = new FileInputStream(FileDescriptor.in);
//        InputStreamReader reader = new InputStreamReader(stream, "CP850");
//        System.setIn(reader);
//        */
//        try {
//            FileInputStream fdIn = new FileInputStream(FileDescriptor.in);
//
//            System.setIn(new BufferedInputStream(fdIn));
//        }
//        catch (UnsupportedEncodingException e) {
//            e.printStackTrace();
//        }
//    }

    /** Gibt den Namen des Benutzer (in kleine Buchstaben gewandelt) zurück. */
    public static String getUserName() {
        /*
         * Da Windows die Groß- / Kleinschreibung offenbar egal ist und mal usr514, mal USR514
         * zurückkommt, müssen wir es selbst normieren:
         */
        String name = System.getProperty("user.name", "").toLowerCase();
        return name;
    }

    /** Gibt den Namen des Rechners zurück. */
    public static String getComputerName() {
        try {
            InetAddress localMachine = java.net.InetAddress.getLocalHost();
            return localMachine.getHostName();
        }
        catch (UnknownHostException e) {
            throw new RuntimeException(e);
        }
    }

    /** Ermittelt das Home-Directory (~ unter Linux, c:\Users\UserName unter Windows). */
    public static String getHomeDirectory() {
        String home = System.getProperty("user.home", ".");
        return home;
    }

    /** Gibt die IP4-Adresse des Rechners in der Form 192.168.30.119 zurück. */
    public static String getLocalhost() {
        try {
            return tryGetLocalhost();
        }
        catch (Exception exception) {
            throw new RuntimeException(exception);
        }
    }

    private static String tryGetLocalhost() throws UnknownHostException {
        InetAddress inetAddress = InetAddress.getLocalHost();
        String address = inetAddress.getHostAddress();
        return address;
    }

    /** Öffnet die übergebene Url im Standardbrowser. */
    public static void openInBrowser(String url) {
        if (Desktop.isDesktopSupported()) {
            Desktop desktop = Desktop.getDesktop();
            openInBrowser(desktop, url);
         }
        else {
            throw new RuntimeException("Desktop wird nicht supported!");
        }
    }

    private static void openInBrowser(Desktop desktop, String url) {
        try {
            tryToOpenInBrowser(desktop, url);
        }
        catch (Exception exception) {
            throw new RuntimeException("Es gab ein Problem beim Öffnen der URL '" + url + ":",
                    exception);
        }
    }

    private static void tryToOpenInBrowser(Desktop desktop, String url)
            throws MalformedURLException, IOException, URISyntaxException {
        //öffnet die PDF-Datei im dafür im System vorhandenen Standard-Programm für PDF-Dateien
        //desktop.open(new File("MyFile.pdf"));

        //öffnet die Datei Text.odt im passenden Standard-Programm
        //desktop.edit(new File("Text.odt"));

        //öffnet eine URL im Standard-Webbrowser
        desktop.browse(new URL(url).toURI());

        //öffnet das Standard-Mail-Programm
        //desktop.mail();

        //druckt auf einem am System angeschlossenen Drucker
        //desktop.print(new File("Text.odt"));
    }

    /** Öffnet die übergebene Datei in der Standardanwendung. */
    public static void openInStandardProgram(String filename) {
        if (Desktop.isDesktopSupported()) {
            Desktop desktop = Desktop.getDesktop();
            openInStandardProgram(desktop, filename);
         }
        else {
            throw new RuntimeException("Desktop wird nicht supported!");
        }
    }

    private static void openInStandardProgram(Desktop desktop, String filename) {
        try {
            desktop.open(new java.io.File(filename));
        }
        catch (Exception exception) {
            throw new RuntimeException(
                    "Es gab ein Problem beim Öffnen der Datei '" + filename + ":", exception);
        }
    }

    /**
     * Gibt die ausführende Java.exe mit Pfad zurück(sowas wie "C:\\Program Files\\Eclipse
     * Adoptium\\jdk-17.0.1.12-hotspot\\bin\\javaw.exe").
     *
     * Kann diese nicht ermittelt werden, wird eine Ausnahme geworfen.
     */
    public static String determineRunningJavaExecutable() {
        try {
            return tryToDetermineRunningJavaExecutable();
        }
        catch (Exception exception) {
            throw new RuntimeException(
                    "Das aktuelle Arbeitsverzeichnis kann nicht ermeittel werden: "
                            + exception.getMessage(),
                    exception);
        }
    }

    private static String tryToDetermineRunningJavaExecutable() {
        String javaExecutablePath = ProcessHandle.current()
                .info()
                .command()
                .orElseThrow();
        return javaExecutablePath;
    }

    /** Gibt das aktuelle Arbeitsverzeichnis (current working directory) zurück. */
    public static String getCurrentWorkingDirectory() {
        return System.getProperty("user.dir");
    }

    private static final List<String> WINDOWS_PROGRAM_DIRECTORYS = CollectionsHelper.buildListFrom(
            toLowerNormalisedPath("C:\\Program Files"),
            toLowerNormalisedPath("C:\\Program Files (x86)"),
            toLowerNormalisedPath("C:\\Programme"),
            toLowerNormalisedPath("C:\\Programme (x86)")
            );

    private static String toLowerNormalisedPath(String dir) {
        return Text.toLowerCase(
                FileHelper.normalizeToBackSlahes(
                        FileHelper.normalisePath(dir)));
    }

    /**
     * Prüft ob das übergebene Verzeichnis unterhalb der geschützten Programm-Verzeichnisse von
     * Windows liegt.
     *
     * @param dir
     *            Das zu prüfende Verzeichnis.
     * @return Wahrheitswert, true genau dann, wenn das Verzeichnis unterhalb der geschützten
     *         Programm-Verzeichnisse von Windows liegt.
     */
    public static boolean isPathInProtectedWindowsProgramDirs(String dir) {
        String loweredNormalisedDir = toLowerNormalisedPath(dir);
        for (String windowsPath : WINDOWS_PROGRAM_DIRECTORYS) {
            if (loweredNormalisedDir.startsWith(windowsPath)) {
                return true;
            }
        }

        return false;
    }

}
