/*
 * 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.awt.Desktop;
import java.awt.FontMetrics;
import java.awt.Frame;
import java.awt.event.WindowEvent;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Formatter;
import java.util.Random;
import java.util.regex.Pattern;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JTable;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.filechooser.FileFilter;

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

    /**
     * Constructor
     */
    public Utilities() {
    }

    /**
     * Covert list to csv
     *
     * @param al
     * @return String of comma separated values
     */
    public static String getListAsCSV(ArrayList<String> al) {
        return al.toString().replace("[", "").replace("]", "").replace(" ", "");
    }

    /**
     * Preloads filechooser in background so it will open instantly when
     * requested
     */
    public static void preloadFileChooser() {
        Thread t = new Thread(new java.lang.Runnable() {

            @Override
            public void run() {
                JFileChooser fileChooser = new JFileChooser();
            }
        });
        t.start();
    }

    /**
     * Opens java file chooser dialog, convenience method. Returns filepath if
     * successful and null if not
     *
     * @param parent
     * @param folder
     * @param filter Extension filter
     * @param mode Fileselection mode
     * @return String Filepath
     */
    public static String openFileChooser(Frame parent, String folder, FileFilter filter, int mode) {
        JFileChooser fileChooser = new JFileChooser();
        fileChooser.setFileSelectionMode(mode);
        fileChooser.setCurrentDirectory(new File(folder));
        if (filter != null) {
            fileChooser.addChoosableFileFilter(filter);
        }
        int intReturn = fileChooser.showOpenDialog(parent);
        if (intReturn == JFileChooser.APPROVE_OPTION) {
            return fileChooser.getSelectedFile().getAbsolutePath();
        } else {
            return null;
        }
    }

    /**
     * Check for newer appversion
     *
     * @param localversion
     * @param remoteversion
     * @return True if newer
     */
    public static boolean isNewerVersion(String localversion, String remoteversion) {
        return convertBuildnumToFloat(remoteversion) > convertBuildnumToFloat(localversion);
    }

    /**
     * Convert an debian build number into a floating point number
     *
     * @param version
     * @return Float version
     */
    private static float convertBuildnumToFloat(String version) {

        float result;
        Pattern p;
        String[] parts;
        try {
            if (version.contains("-")) {
                // Handling for old non-debian compatible version
                p = Pattern.compile("-");
                parts = p.split(version);
            } else {
                // Handling for new debian compatible version
                p = Pattern.compile("\\.");
                parts = p.split(version);
                parts[0] = parts[0] + "." + parts[1];
                parts[1] = parts[2];
                parts[2] = "";
            }
            result = Float.parseFloat(parts[0]);
            result += (Float.parseFloat(parts[1]) / 100000);
        } catch (Exception ex) {
            version = version.replaceAll("[^0-9.]", "");
            result = convertVersionToFloat(version);
        }
        return result;
    }

    /**
     * Convert a windows version number into a floating point number
     *
     * @param version
     * @return Float version
     */
    private static float convertVersionToFloat(String version) {

        int c;
        float f;
        version = version.toLowerCase();
        if (version.startsWith("v")) {
            version = version.substring(1);
        }
        try {
            c = version.charAt(version.length() - 1);
            if (c > 96) {
                version = version.substring(0, version.length() - 1);
                Float fraction = (float) (c - 96) / 10000;
                f = Float.parseFloat(version) + fraction;
            } else {
                f = Float.parseFloat(version);
            }
        } catch (Exception ex) {
            f = 0;
        }
        return f;
    }

    /**
     * Open the desktop default file editor
     *
     * @param path Path to file
     */
    public static void editFile(String path) {
        if (OSFunction.isLinux()) {
            OSFunction.launchProcess("xdg-open", path);
            return;
        }
        if (OSFunction.isWindows()) {
            OSFunction.launchProcess("notepad", path);
        }
    }

    /**
     * Launch the desktops default file handling program
     *
     * @param strUrl
     * @return Returns true if successful
     */
    public static boolean openFileExternally(final String strUrl) {
        String url = strUrl.replace("\\", "/");
        try {
            if (OSFunction.isLinux()) {
                OSFunction.launchProcess("xdg-open", url);
            } else {
                if (Desktop.isDesktopSupported()) {
                    Desktop deskSupport = Desktop.getDesktop();
                    if (url.startsWith("http") || url.startsWith("ftp")) {
                        deskSupport.browse(new URI(url));
                    } else {
                        deskSupport.open(new File(url));
                    }
                }
            }
            return true;
        } catch (URISyntaxException | IOException ex) {
        }
        return false;
    }

    /**
     * Initialises the UI Look and Feel
     *
     * @param theme
     */
    public static void loadUIStyle(String theme) {

        try {
            if (theme == null) {
                UIManager.setLookAndFeel(UIManager.getCrossPlatformLookAndFeelClassName());
                return;
            }

            if (theme.contentEquals("Metal")) {
                UIManager.setLookAndFeel(UIManager.getCrossPlatformLookAndFeelClassName());
                return;
            }

            if (theme.contentEquals("Nimbus")) {
                // Switch to Nimbus theme
                UIManager.setLookAndFeel("com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel");
                return;
            }

            if (theme.contentEquals("System")) {
                try {
                    UIManager.setLookAndFeel("com.sun.java.swing.plaf.windows.WindowsLookAndFeel");
                    return;
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                }
                try {
                    UIManager.setLookAndFeel("com.sun.java.swing.plaf.gtk.GTKLookAndFeel");
                    return;
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                }
            }
        } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
        }

        // If everything else fails use default cross platform metal
        try {
            UIManager.setLookAndFeel(UIManager.getCrossPlatformLookAndFeelClassName());
        } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
        }
    }

    /**
     * Adjust specified column width based on the width of a test string
     *
     * @param table
     * @param test
     */
    public static void adjustTableColumnWidth(JTable table, String... test) {
        FontMetrics ourFontMetrics = table.getFontMetrics(table.getFont());
        int col = 0;
        for (String s : test) {
            table.getColumn(table.getColumnName(col++)).setPreferredWidth(ourFontMetrics.stringWidth(s));
        }
    }

    /**
     * Generate secret key.
     *
     * @param length
     * @return String key
     */
    public static String generateSecretKey(int length) {
        StringBuilder buffer = new StringBuilder();
        Random random = new Random();
        String chars = "abcdefghijklmnopqrstuvwxyz1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ";
        int x;
        try {
            for (int i = 0; i < length; i++) {
                x = random.nextInt(chars.length() - 1);
                buffer.append(chars.substring(x, x + 1));
            }
        } catch (Exception ex) {
        }
        return buffer.toString();
    }

    public static String getTorHashPassword(String secret) {
        byte[] key = S2KRFC2440(secret);
        return "16:" + byteToHex(key);
    }

    /**
     * Secret to key, Algorithm info taken from
     * http://sunsite.icm.edu.pl/gnupg/rfc2440-3.html
     *
     * @param secret
     * @return hashed key for the given secret
     */
    public static byte[] S2KRFC2440(String secret) {

        // Create our message digest for SHA-1
        MessageDigest md;
        try {
            md = MessageDigest.getInstance("SHA-1");
        } catch (Exception ex) {
            return null;
        }

        // Create our random S2K salt, the 9th byte is loaded with the code count which
        // determines the amount of octet hashing used.
        // This salt is eventually combined with the secret to prevent dictionary attacks
        int codecount = 32;
        byte[] s2ksalt = new byte[9];
        SecureRandom sr = new SecureRandom();
        sr.nextBytes(s2ksalt);
        s2ksalt[8] = (byte) codecount;
        // The octet hashing count is defined by the following formula where EXPBIAS is 6
        int octethashcount = (16 + (codecount & 15)) << ((codecount >> 4) + 6);

        // Merge our S2K salt and partialsecret arrays together into one array to
        // create the full secret key.
        byte[] partialsecret;
        try {
            partialsecret = secret.getBytes("UTF-8");
        } catch (UnsupportedEncodingException ex) {
            return null;
        }
        byte[] fullsecret = new byte[8 + partialsecret.length];
        System.arraycopy(s2ksalt, 0, fullsecret, 0, 8);
        System.arraycopy(partialsecret, 0, fullsecret, 8, partialsecret.length);
        // Do the hashing based on the hashcount
        while (true) {
            if (octethashcount < fullsecret.length) {
                md.update(fullsecret, 0, octethashcount);
                break;
            } else {
                md.update(fullsecret);
            }
            octethashcount -= fullsecret.length;
        }
        // Create our final key by merging s2kspecifier and digest result
        byte[] ourkey = new byte[20 + 9];
        System.arraycopy(md.digest(), 0, ourkey, 9, 20);
        System.arraycopy(s2ksalt, 0, ourkey, 0, 9);
        return ourkey;
    }

    /**
     * Get SHAHash using given password string
     *
     * @param password
     * @return SHAhash string
     */
    public static String getSHAHash(String password) {
        String sha1 = "";
        try {
            MessageDigest crypt = MessageDigest.getInstance("SHA-1");
            crypt.reset();
            crypt.update(password.getBytes("UTF-8"));
            sha1 = byteToHex(crypt.digest());
        } catch (NoSuchAlgorithmException | UnsupportedEncodingException e) {
        }
        return sha1;
    }

    /**
     * Convert byte to a hex string
     *
     * @param hash
     * @return byte as a hex string
     */
    private static String byteToHex(final byte[] hash) {
        String result;
        try (Formatter formatter = new Formatter()) {
            for (byte b : hash) {
                formatter.format("%02x", b);
            }
            result = formatter.toString();
        }
        return result;
    }

    /**
     * Retrieve text from a resource text file
     *
     * @param resourcepath
     * @return String text
     */
    public static String getTextFromResource(String resourcepath) {
        SimpleFile sf = new SimpleFile(resourcepath);
        sf.openBufferedResource();
        String text = sf.readEntireFile();
        sf.closeFile();
        return text;
    }

    /**
     * Retrieve text from a text file
     *
     * @param filepath
     * @return String text
     */
    public static String getTextFromFile(String filepath) {
        SimpleFile sf = new SimpleFile(filepath);
        String text = "";
        if (sf.exists()) {
            sf.openBufferedRead();
            text = sf.readEntireFile();
            sf.closeFile();
        }
        return text;
    }

    /**
     * Calculate the sha1 checksum of a given file
     *
     * @param filepath
     * @return Checksum as a hex string
     */
    public static String getSha1Sum(String filepath) {
        StringBuilder sb = new StringBuilder("");
        try {
            MessageDigest md = MessageDigest.getInstance("SHA1");
            FileInputStream fis = new FileInputStream(filepath);
            byte[] dataBytes = new byte[1024];
            int nread;
            while ((nread = fis.read(dataBytes)) != -1) {
                md.update(dataBytes, 0, nread);
            }
            byte[] mdbytes = md.digest();
            //convert the byte to hex format
            for (int i = 0; i < mdbytes.length; i++) {
                sb.append(Integer.toString((mdbytes[i] & 0xff) + 0x100, 16).substring(1));
            }
        } catch (NoSuchAlgorithmException | IOException ex) {
        }
        return sb.toString();
    }

    /**
     * Creates a hidden dummy window for registration to various docks/launchers
     * Fixes the neverending launching indicator
     */
    public static void registerWindow() {
        final JFrame jf = new JFrame();

        jf.addWindowListener(new java.awt.event.WindowListener() {
            @Override
            public void windowOpened(WindowEvent e) {
                jf.dispose();
            }

            @Override
            public void windowClosing(WindowEvent e) {
            }

            @Override
            public void windowClosed(WindowEvent e) {
            }

            @Override
            public void windowIconified(WindowEvent e) {
            }

            @Override
            public void windowDeiconified(WindowEvent e) {
            }

            @Override
            public void windowActivated(WindowEvent e) {
            }

            @Override
            public void windowDeactivated(WindowEvent e) {
            }
        });
        jf.setUndecorated(true);
        jf.setBounds(0, 0, 0, 0);
        jf.setVisible(true);
    }

}
