/*
 * Copyright (C) 2009-2017 Alistair Neil <info@dazzleships.net>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */
package lib;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.management.ManagementFactory;
import java.lang.management.RuntimeMXBean;
import java.util.ArrayList;
import java.util.Locale;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 *
 * @author Alistair Neil <info@dazzleships.net>
 */
public class OSFunction {

    private static ArrayList<String> alProcesses;

    /**
     * Get process id
     *
     * @return Process id or -1 if not found
     */
    public static String getOurProcessID() {
        RuntimeMXBean rtb = ManagementFactory.getRuntimeMXBean();
        String processName = rtb.getName();
        Integer pid = tryPattern(processName);
        return String.valueOf(pid);
    }

    /**
     * Get process id from a process
     *
     * @param process
     * @return process id as String
     */
    public static String getProcessID(String process) {
        Pattern pattern = Pattern.compile(" +");
        String[] result = pattern.split(process.trim());
        if (isWindows()) {
            return result[1];
        } else {
            return result[0];
        }
    }

    private static Integer tryPattern(String processName) {
        Integer result = null;
        Pattern pattern = Pattern.compile("^([0-9]+)@.+$", Pattern.CASE_INSENSITIVE);
        Matcher matcher = pattern.matcher(processName);
        if (matcher.matches()) {
            result = Integer.parseInt(matcher.group(1));
        }
        return result;
    }

    /**
     * Get OS version
     *
     * @return String OS version
     */
    public static String getOSVersion() {
        return System.getProperty("os.version");
    }

    /**
     * Get user name
     *
     * @return String User name
     */
    public static String getUserName() {
        return System.getProperty("user.name");
    }

    /**
     * Get users home folder
     *
     * @return String path to home folder
     */
    public static String getUsersHomeFolder() {
        return System.getProperty("user.home");
    }

    /**
     * Get file separator
     *
     * @return String separator
     */
    public static String getFileSeparator() {
        return System.getProperty("file.separator");
    }

    /**
     * Get users documents folder (Windows only)
     *
     * @return String path to documents folder
     */
    public static String getUsersDocFolder() {

        String strReturn = getUsersHomeFolder() + "\\Documents";
        if (isWinXP()) {
            strReturn = getUsersHomeFolder() + "\\My Documents";
        }
        return strReturn;
    }

    /**
     * Get user settings path
     *
     * @return String path to user settings folder
     */
    public static String getUserSettingsPath() {

        String userSettingsPath;
        if (isWindows()) {
            userSettingsPath = getUsersHomeFolder() + "\\Application Data\\";
        } else {
            userSettingsPath = getUsersHomeFolder() + "/";
        }
        return userSettingsPath;
    }

    /**
     * Get a specified application's settings folder, if null return this
     * applications setting folder
     *
     * @param appname Application settings folder name
     * @param appseries Application series (optional)
     * @return String Path to the applications setting folder
     */
    public static String getAppSettingsPath(String appname, String appseries) {

        String appSettingsPath = getUserSettingsPath();
        if (appname == null) {
            return appSettingsPath;
        }
        if (isLinux()) {
            appname = "." + appname;
        }
        appSettingsPath += appname + getFileSeparator();
        if (appseries != null) {
            appSettingsPath += appseries + getFileSeparator();
        }
        return appSettingsPath;
    }

    /**
     * Get users current folder
     *
     * @return String Path to current folder
     */
    public static String getUsersCurrentFolder() {
        return System.getProperty("user.dir");
    }

    /**
     * Get users temp folder
     *
     * @param path
     * @return String Path to users temp folder
     */
    public static String getTempFolder(String path) {
        String result = System.getProperty("java.io.tmpdir");
        String sep = "";
        if (isLinux()) {
            sep = getFileSeparator();
        }
        result += sep;
        if (path != null && !path.isEmpty()) {
            if (isWindows()) {
                sep = getFileSeparator();
            }
            result = result + path + sep;
        }
        return result;
    }

    /**
     * Get OS name, convenience method
     *
     * @return String
     */
    public static String getOSName() {
        return System.getProperty("os.name").toLowerCase();
    }

    /**
     * Get OS architecture, convenience method
     *
     * @return String
     */
    public static String getOSArch() {
        return System.getProperty("os.arch");
    }

    /**
     * Generic test for Windows platform
     *
     * @return boolean True if Windows
     */
    public static boolean isWindows() {
        return getOSName().contains("windows");
    }

    /**
     * Specific test for Win 7
     *
     * @return boolean True if Win 7
     */
    public static boolean isWin7() {
        return getOSName().startsWith("windows 7");
    }

    /**
     * Specific test for Win 8
     *
     * @return boolean True if Win 8
     */
    public static boolean isWin8() {
        return getOSName().startsWith("windows 8");
    }

    /**
     * Specific test for Win 10
     *
     * @return boolean True if Win 10
     */
    public static boolean isWin10() {
        return getOSName().startsWith("windows 10");
    }

    /**
     * Specific test for Win XP
     *
     * @return boolean True if Win XP
     */
    public static boolean isWinXP() {
        return getOSName().contentEquals("windows");
    }

    /**
     * Generic test for Linux platform
     *
     * @return boolean True if Linux
     */
    public static boolean isLinux() {
        return getOSName().contains("linux");
    }

    /**
     * Find a specific file, requires a series of paths where it might be found
     * to be provided
     *
     * @param fname
     * @param paths
     * @return String path to file or null
     */
    public static String findFile(String fname, String... paths) {
        for (String s : paths) {
            if (new File(s + fname).exists()) {
                return s + fname;
            }
        }
        return null;
    }

    /**
     * Test to see if their are multiple instances of the this app running
     *
     * @param appname
     * @return True if multiple instance
     */
    public static boolean isMultipleInstance(String appname) {
        if (alProcesses == null) {
            alProcesses = getLiveProcesses();
        }
        ArrayList<String> processes = OSFunction.filterProcesses(appname);
        return OSFunction.filterProcesses(processes, "java").size() > 1;
    }

    /**
     * Cached list of processes
     *
     * @return list
     */
    public static ArrayList<String> getCachedProcesses() {
        return alProcesses;
    }

    /**
     * Find processes matching the contents of filter
     *
     * @param processes
     * @param filter
     * @return ArrayList of matching processes
     */
    public static ArrayList<String> filterProcesses(ArrayList<String> processes, String filter) {
        ArrayList<String> result = new ArrayList<>();
        if (processes != null && !processes.isEmpty()) {
            if (filter != null) {
                for (String s : processes) {
                    if (s.contains(filter)) {
                        result.add(s);
                    }
                }
            }
        }
        return result;
    }

    /**
     * Find processes matching the contents of filter
     *
     * @param filter
     * @return ArrayList of matching processes
     */
    public static ArrayList<String> filterProcesses(String filter) {
        ArrayList<String> result = new ArrayList<>();
        if (!alProcesses.isEmpty()) {
            if (filter != null) {
                for (String s : alProcesses) {
                    if (s.contains(filter)) {
                        result.add(s);
                    }
                }
            }
        }
        return result;
    }

    /**
     * Generic kill process with matching pid
     *
     * @param pid
     * @return String kill result or null if failed
     */
    public static String killProcess(String pid) {
        if (isWindows()) {
            return killProcessWindows(pid);
        }
        if (isLinux()) {
            return killProcessLinux(pid);
        }
        return null;
    }

    /**
     * Windows specific kill process with matching pid
     *
     * @param pid
     * @return String kill result or null if failed
     */
    private static String killProcessWindows(String pid) {
        Process processKill;
        try {
            processKill = Runtime.getRuntime().exec("taskkill /F /pid " + pid);
            return getProcessResult(processKill);
        } catch (IOException ex) {
            Logger.getGlobal().logp(Level.WARNING, OSFunction.class.getName(), "killProcessWindows", "", ex);
        }
        try {
            processKill = Runtime.getRuntime().exec("tskill " + pid);
            return getProcessResult(processKill);
        } catch (IOException ex) {
            Logger.getGlobal().logp(Level.WARNING, OSFunction.class.getName(), "killProcessWindows", "", ex);
        }
        return null;
    }

    /**
     * Linux specific specific kill process with matching pid
     *
     * @param pid
     * @return String kill result or null if failed
     */
    private static String killProcessLinux(String pid) {
        Process processKill = null;
        try {
            processKill = Runtime.getRuntime().exec("kill -9 " + pid);
        } catch (IOException ex) {
            Logger.getGlobal().logp(Level.WARNING, OSFunction.class.getName(), "killProcessLinux", "", ex);
        }
        return getProcessResult(processKill);
    }

    /**
     * Set a files hidden status (Windows only)
     *
     * @param filepath Path to file includes filename
     * @param hidden True/False
     */
    public static void setFileHidden(String filepath, boolean hidden) {

        if (isWindows()) {
            try {
                if (hidden) {
                    Runtime.getRuntime().exec("Attrib.exe +H " + filepath).waitFor();
                } else {
                    Runtime.getRuntime().exec("Attrib.exe -H " + filepath).waitFor();
                }
            } catch (IOException | InterruptedException ex) {
                Logger.getGlobal().logp(Level.WARNING, OSFunction.class.getName(), "setFileHidden", "", ex);
            }
        }
    }

    /**
     * Get gnome 3 preference using schema name and key name
     *
     * @param schema
     * @param key
     * @return String preference value
     */
    public static String getGnome3Pref(String schema, String key) {
        Process p = launchProcess("gsettings", "get", schema, key);
        String result = getProcessResult(p);
        if (result != null) {
            result = result.replace("'", "").replace("[", "").replace("]", "");
        }
        return result;
    }

    /**
     * Set a gnome 3 boolean value using schema name and key name
     *
     * @param schema
     * @param key
     * @param value
     */
    public static void setGnome3Pref(String schema, String key, boolean value) {
        setGnome3Pref(schema, key, String.valueOf(value));
    }

    /**
     * Set a gnome 3 string value using schema name and key name
     *
     * @param schema
     * @param key
     * @param value
     */
    public static void setGnome3Pref(String schema, String key, String value) {
        if (value == null || value.isEmpty()) {
            resetGnome3Pref(schema, key);
        } else {
            launchProcess("gsettings", "set", schema, key, "'" + value + "'");
        }
    }

    /**
     * Reset a gnome3 value to its default. If key is null, it will reset the
     * whole schema tree to its defaults
     *
     * @param schema
     * @param key
     */
    public static void resetGnome3Pref(String schema, String key) {
        if (key == null) {
            launchProcess("gsettings", "reset", schema);
        } else {
            launchProcess("gsettings", "reset", schema, key);
        }
    }

    /**
     * Get a kde preference string value using group name, key name and return
     * contents of def on failure
     *
     * @param group
     * @param key
     * @param def
     * @return String Preference value
     */
    public static String getKDEPref(String group, String key, String def) {

        // If exists read the file contents and find the matching key
        String strKdePath = getKDEProxyPath();
        if (strKdePath == null) {
            return null;
        }

        String strTemp = null;
        SimpleFile sf = new SimpleFile(strKdePath);
        sf.openBufferedRead();
        while ((strTemp = sf.readLine()) != null) {
            if (strTemp.contains("[" + group + "]")) {
                break;
            }
        }
        if (strTemp == null) {
            return def;
        }
        while ((strTemp = sf.readLine()) != null) {
            if (strTemp.contains(key)) {
                break;
            }
        }
        sf.closeFile();
        if (strTemp == null) {
            return def;
        }
        strTemp = strTemp.substring(strTemp.indexOf('=') + 1);
        return strTemp;
    }

    /**
     * Set a kde string preference value using group name, key name
     *
     * @param group
     * @param key
     * @param value
     */
    public static void setKDEPref(String group, String key, String value) {

        // If exists read the file contents and find the matching key
        String strKdePath = getKDEProxyPath();
        if (strKdePath == null) {
            return;
        }

        SimpleFile sf = new SimpleFile(strKdePath);
        // Get file contents into a array list for further processing
        String strTemp;
        ArrayList<String> listContents = new ArrayList<>();
        sf.openBufferedRead();
        while ((strTemp = sf.readLine()) != null) {
            listContents.add(strTemp);
        }
        sf.closeFile();

        // Process contents of file and write it back out
        sf.openBufferedWrite();
        // Find Group entry
        boolean entryfound = false;
        while (!listContents.isEmpty()) {
            strTemp = listContents.get(0);
            sf.writeFile(strTemp, 1);
            listContents.remove(0);
            if (strTemp.contentEquals("[" + group + "]")) {
                entryfound = true;
                break;
            }
        }

        // If no group entry found then write one
        if (!entryfound) {
            sf.writeFile("", 1);
            sf.writeFile("[" + group + "]", 1);
        }

        // Find key entry
        while (!listContents.isEmpty()) {
            strTemp = listContents.get(0);
            listContents.remove(0);
            if (strTemp.contains(key)) {
                break;
            }
            sf.writeFile(strTemp, 1);
        }

        sf.writeFile(key + "=" + value, 1);

        // Write out rest of file
        while (!listContents.isEmpty()) {
            strTemp = listContents.get(0);
            listContents.remove(0);
            sf.writeFile(strTemp, 1);
        }
        sf.closeFile();
    }

    /**
     * Set a kde boolean preference value using group name, key name
     *
     * @param group
     * @param key
     * @param value
     */
    public static void setKDEPref(String group, String key, boolean value) {
        setKDEPref(group, key, String.valueOf(value));
    }

    /**
     * Set a kde integer preference value using group name, key name
     *
     * @param group
     * @param key
     * @param value
     */
    public static void setKDEPref(String group, String key, int value) {
        setKDEPref(group, key, String.valueOf(value));
    }

    /**
     * Test for KDE installed (Linux only)
     *
     * @return String path to kde proxy file or null
     */
    public static String getKDEProxyPath() {
        if (isLinux()) {
            File f = new File(getUsersHomeFolder() + "/.config/kioslaverc");
            if (f.exists()) {
                return f.getAbsolutePath();
            }
            f = new File(getUsersHomeFolder() + "/.kde/share/config/kioslaverc");
            if (f.exists()) {
                return f.getAbsolutePath();
            }
            f = new File(getUsersHomeFolder() + "/.kde4/share/config/kioslaverc");
            if (f.exists()) {
                return f.getAbsolutePath();
            }
        }
        return null;
    }

    /**
     * Test if gsettings installed (Linux only)
     *
     * @return String path to gsetttings
     */
    public static String getGsettingsPath() {
        // Verify if gsettings is installed
        return findFile("gsettings", "/usr/bin/");
    }

    /**
     * Convenience method to get the first resultant output of an executed
     * process as a String
     *
     * @param aprocess
     * @return String Process result
     */
    public static String getProcessResult(Process aprocess) {
        if (aprocess == null) {
            return null;
        }
        return getProcessOutput(aprocess.getInputStream()).toString();
    }

    /**
     * Obtain all output of an executed process.
     *
     * @param is
     * @return ArrayList of Strings containing all generated output
     */
    public static ArrayList<String> getProcessOutput(InputStream is) {

        String line;
        ArrayList<String> arrList = new ArrayList<>();
        try {
            try (BufferedReader input = new BufferedReader(new InputStreamReader(is))) {
                while ((line = input.readLine()) != null) {
                    if (line.isEmpty()) {
                        continue;
                    }
                    arrList.add(line);
                }
            }
            return arrList;
        } catch (IOException ex) {
            Logger.getGlobal().logp(Level.SEVERE, OSFunction.class.getName(), "getProcessResults", "", ex);
        }
        return null;
    }

    /**
     * Launch an external process with arguments
     *
     * @param command
     * @return Process
     */
    public static Process launchProcess(String... command) {
        try {
            Process p = Runtime.getRuntime().exec(command);
            if (p == null) {
                Logger.getGlobal().logp(Level.WARNING, OSFunction.class.getName(), "launchProcess", "Failed Process", "");
            }
            return p;
        } catch (IOException ex) {
            Logger.getGlobal().logp(Level.WARNING, OSFunction.class.getName(), "launchProcess", "", ex);
        }
        return null;
    }

    /**
     * Gets list of active system processes
     *
     * @return Arraylist of system process
     */
    public static ArrayList<String> getLiveProcesses() {
        if (isWindows()) {
            return getActiveProcessesWindows();
        } else {
            return getActiveProcessesLinux();
        }
    }

    /**
     * Windows specific refresh or internal list of active system processes
     */
    private static ArrayList<String> getActiveProcessesWindows() {
        ArrayList<String> alResult = new ArrayList<>();
        try {
            Logger.getGlobal().info("refreshActiveProcessesWindows()");
            Process p = Runtime.getRuntime().exec(System.getenv("windir") + "\\system32\\" + "tasklist.exe /v");
            alResult = getProcessOutput(p.getInputStream());
            if (alResult.size() > 1) {
                // Remove second entry
                alResult.remove(1);
            }
            return alResult;
        } catch (IOException ex) {
            Logger.getGlobal().logp(Level.WARNING, OSFunction.class.getName(), "refreshActiveProcessesWindows", "", ex);
        }
        return alResult;
    }

    /**
     * Linux specific refresh or internal list of active system processes
     */
    private static ArrayList<String> getActiveProcessesLinux() {

        ArrayList<String> alResult = new ArrayList<>();
        try {
            Process process = Runtime.getRuntime().exec("ps ax");
            alResult = getProcessOutput(process.getInputStream());
            return alResult;
        } catch (IOException ex) {
            Logger.getGlobal().logp(Level.WARNING, OSFunction.class.getName(), "refreshActiveProcessesLinux", "", ex);
        }
        return alResult;
    }

    /**
     * Get the name of the compositor
     *
     * @return name as String
     */
    public static String getCompositor() {
        String result = "unknown";
        if (isWin7() || isWin8() || isWin10()) {
            return "DWM";
        }
        if (isWindows()) {
            return getOSName();
        }
        if (alProcesses == null) {
            alProcesses = getLiveProcesses();
        }
        for (String s : alProcesses) {
            if (s.contains("kwin")) {
                result = "kwin";
                break;
            }
            if (s.contains("compton")) {
                result = "compton";
                break;
            }
            if (s.contains("cinnamon")) {
                result = "clutter";
                break;
            }
            if (s.contains("marco")) {
                result = "marco";
                break;
            }
            if (s.contains("compiz")) {
                result = "compiz";
                break;
            }
        }
        return result;
    }

    /**
     * Get the name of the active desktop
     *
     * @return name as String
     */
    public static String getActiveDesktop() {
        if (isWindows()) {
            return getOSName();
        }
        String result = "unknown";
        if (alProcesses == null) {
            alProcesses = getLiveProcesses();
        }
        for (String s : alProcesses) {
            if (s.contains("cinnamon-session")) {
                result = "cinnamon";
                break;
            }
            if (s.contains("xfce4-session")) {
                result = "xfce";
                break;
            }
            if (s.contains("mate-session")) {
                result = "mate";
                break;
            }
            if (s.contains("gnome-shell")) {
                result = "gnome3";
                break;
            }
            if (s.contains("unity-settings-daemon")) {
                result = "unity";
                break;
            }
            if (s.contains("lxsession")) {
                result = "lxde";
                break;
            }
            if (s.contains("plasma-desktop")) {
                result = "kde4";
                break;
            }
             if (s.contains("plasmashell")) {
                result = "kde5";
                break;
            }
           if (s.contains("enlightenment")) {
                result = "enlightenment";
                break;
            }
        }
        return result;
    }

    /**
     * Clear active processes, mainly to reduce memory consumption when were
     * done with them
     */
    public static void clearActiveProcesses() {
        alProcesses.clear();
        alProcesses = null;
    }

    /**
     * Get default language iso code
     *
     * @return language iso code
     */
    public static String getLangCode() {
        return Locale.getDefault().getLanguage();
    }

}
