package be.iminds.ilabt.jfed.util;

import org.slf4j.LoggerFactory;

import javax.annotation.Nonnull;
import java.io.*;
import java.net.URL;
import java.net.URLConnection;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.attribute.PosixFilePermission;
import java.util.HashSet;
import java.util.Scanner;
import java.util.Set;

/**
 * IOUtils
 */
public class IOUtils {
    private static final org.slf4j.Logger LOG = LoggerFactory.getLogger(IOUtils.class);
    private static final int READ_TIMEOUT = 60 * 1000;

    public static String askCommandLineInput(String question) {
        return askCommandLineInput(question, System.in);
    }

    public static String askCommandLineInput(String question, InputStream in) {
        Scanner input = new Scanner(in);
        System.out.print(question + ": ");
        return input.nextLine();
    }

    public static char[] askCommandLinePassword(String question) {
        return askCommandLinePassword(question, System.in);
    }

    public static char[] askCommandLinePassword(String question, InputStream in) {
        Console cons = System.console();
        if ((cons = System.console()) != null) {
            char[] passwd = cons.readPassword("%s: ", question);
            return passwd;
        } else {
            //fallback if not a console
            Scanner input = new Scanner(in);
            System.out.print("*** everything you type might be shown *** " + question + ": ");
            return input.nextLine().toCharArray();
        }
    }

    public static String fileToString(String filename) throws IOException {
        return fileToString(new File(filename));
    }

    //this version is EVIL. The changes in the file caused the signature check on a credential to fail!
//    public static String fileToString(File file) throws IOException {
//        StringBuilder res = new StringBuilder();
//        BufferedReader br = null;
//        try {
//            br = new BufferedReader(new FileReader(file));
//            String line = br.readLine();
//            while (line != null) {
//                res.append(line);
//                res.append('\n');
//                line = br.readLine();
//            }
////        } catch (Exception e) {
////            throw e;
////        } catch (IOException e) {
////            throw e;
//        } finally {
//            try {
//                if (br != null) br.close();
//            } catch(IOException ignored) { }
//        }
//        return res.toString();
//    }
    public static String fileToString(@Nonnull File file) throws IOException {
        StringBuilder res = new StringBuilder();
        FileReader fr = null;
        try {
            fr = new FileReader(file);
            char[] buffer = new char[4096];

            int read = fr.read(buffer);
            while (read != -1) {
                res.append(buffer, 0, read);
                read = fr.read(buffer);
            }
        } finally {
            try {
                if (fr != null) fr.close();
            } catch (IOException ignored) {
                LOG.debug("Exception closing file (normal if exception reading file occured. Possible problem that is not the case.)", ignored);
            }
        }
        return res.toString();
    }

    public static String resourceToString(@Nonnull String resourceName, @Nonnull Class classInResourceRoot) throws IOException {
        assert resourceName != null;
        assert classInResourceRoot != null;

        StringBuilder res = new StringBuilder();
        BufferedReader br = null;
        try {
            InputStream resourceInputStream = classInResourceRoot.getResourceAsStream(resourceName);
            if (resourceInputStream == null) {
                throw new IOException("No resource named \"" + resourceName + "\" was found");
            }
            br = new BufferedReader(new InputStreamReader(resourceInputStream));
            String line = br.readLine();
            while (line != null) {
                res.append(line);
                res.append('\n');
                line = br.readLine();
            }
        } finally {
            try {
                if (br != null) br.close();
            } catch (IOException ignored) {
            }
        }
        return res.toString();
    }

    public static byte[] fileToByteArray(File file) throws IOException {
        InputStream is = new FileInputStream(file);
        return streamToByteArray(is);
    }
    public static byte[] streamToByteArray(InputStream is) throws IOException {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        try {
            int i;
            byte[] b = new byte[1024];
            while ((i = is.read(b)) != -1) baos.write(b, 0, i);
        } finally {
            try {
                if (is != null) is.close();
            } catch (IOException ignored) {
            }
        }
        return baos.toByteArray();
    }

    public static void byteArrayToFile(File file, byte[] b) throws IOException {
        BufferedOutputStream os = null;
        try {
            os = new BufferedOutputStream(new FileOutputStream(file));
            os.write(b);
        } finally {
            try {
                if (os != null) os.close();
            } catch (IOException ignored) {
            }
        }
    }

    public static String streamToString(@Nonnull final InputStream is, String encoding/*example "UTF-8"*/) throws IOException {
        assert is != null;

        int BUFFER_SIZE = 2048;
        final char[] buffer = new char[BUFFER_SIZE];
        final StringBuilder out = new StringBuilder();
        try {
            try (Reader in = new InputStreamReader(is, encoding)) {
                for (; ; ) {
                    int rsz = in.read(buffer, 0, buffer.length);
                    if (rsz < 0)
                        break;
                    out.append(buffer, 0, rsz);
                }
            }
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException("Unexpected unsupported encoding \""+encoding+"\"", e);
        }
        return out.toString();
    }

    public static String urlToString(URL url, Charset charset) throws IOException {
        URLConnection urlConn = url.openConnection();
        urlConn.setReadTimeout(READ_TIMEOUT);

        return streamToString(urlConn.getInputStream(), charset.toString());

    }

    public static void streamToFile(InputStream is, File outFile) {
        String res = "";
        try {
            FileWriter fw = new FileWriter(outFile);

            BufferedReader br = new BufferedReader(new InputStreamReader(is));
            String line = br.readLine();
            while (line != null) {
                res += line + "\n";
                fw.write(line);
                line = br.readLine();
            }

            fw.close();
        } catch (IOException e) {
            throw new RuntimeException("Error reading inputstream '" + is + "' or writing to file \"" + outFile.getName() + "\": " + e.getMessage(), e);
        }
    }

    public static String currentStackToStacktraceString() {
        Throwable e;
        try {
            throw new RuntimeException();
        } catch (RuntimeException ex) {
            e = ex;
        }
        StringWriter sw = new StringWriter();
        PrintWriter pw = new PrintWriter(sw);
        e.printStackTrace(pw);
        pw.close();
        String stacktrace = sw.getBuffer().toString();
        return stacktrace;
    }

    public static String exceptionToStacktraceString(Throwable e) {
        StringWriter sw = new StringWriter();
        PrintWriter pw = new PrintWriter(sw);
        e.printStackTrace(pw);
        pw.close();
        String stacktrace = sw.getBuffer().toString();
        return stacktrace;
    }

    public static String threadToStacktraceString(Thread thread) {
        StackTraceElement[] stackTraceElements = thread.getStackTrace();
        String res = "";
        for (StackTraceElement e : stackTraceElements) {
            res += e+"\n";
        }
        return res;
    }

    public static void stringToFile(String filename, String text) {
        stringToFile(new File(filename), text);
    }

    public static void stringToFile(File file, String text) {
        try {
            FileWriter fw = new FileWriter(file);
            fw.write(text);
            fw.close();
        } catch (IOException e) {
            throw new RuntimeException("Error writing '" + file + "': " + e.getMessage(), e);
        }
    }

    /**
     * group or others have NO permissions
     */
    public static void assureUserOnlyPerms(File file, boolean userRead, boolean userWrite, boolean userExecute) throws IOException {
        if (OSDetector.os == OSDetector.OS.UNIX || OSDetector.os == OSDetector.OS.MAC) {
            boolean needToChange = false;
            Set<PosixFilePermission> perms = Files.getPosixFilePermissions(file.toPath());
            if (perms.contains(PosixFilePermission.GROUP_READ)) needToChange = true;
            if (perms.contains(PosixFilePermission.GROUP_WRITE)) needToChange = true;
            if (perms.contains(PosixFilePermission.GROUP_EXECUTE)) needToChange = true;
            if (perms.contains(PosixFilePermission.OTHERS_READ)) needToChange = true;
            if (perms.contains(PosixFilePermission.OTHERS_WRITE)) needToChange = true;
            if (perms.contains(PosixFilePermission.OTHERS_EXECUTE)) needToChange = true;
            if (perms.contains(PosixFilePermission.OWNER_READ) != userRead) needToChange = true;
            if (perms.contains(PosixFilePermission.OWNER_WRITE) != userWrite) needToChange = true;
            if (perms.contains(PosixFilePermission.OWNER_EXECUTE) != userExecute) needToChange = true;
            if (needToChange) setUserOnlyPerms(file, userRead, userWrite, userExecute);
        }
    }

    /**
     * group or others have NO permissions
     */
    public static void setUserOnlyPerms(String file, boolean userRead, boolean userWrite, boolean userExecute) throws IOException {
        setUserOnlyPerms(new File(file), userRead, userWrite, userExecute);
    }

    /**
     * group or others have NO permissions
     */
    public static void setUserOnlyPerms(File file, boolean userRead, boolean userWrite, boolean userExecute) throws IOException {
        if (OSDetector.os == OSDetector.OS.UNIX || OSDetector.os == OSDetector.OS.MAC) {
            //this methods works in Java 7
            Set<PosixFilePermission> perms = new HashSet<>();
            if (userRead) perms.add(PosixFilePermission.OWNER_READ);
            if (userWrite) perms.add(PosixFilePermission.OWNER_WRITE);
            if (userExecute) perms.add(PosixFilePermission.OWNER_EXECUTE);
            Files.setPosixFilePermissions(file.toPath(), perms);
        }
    }

    public static void setAllowAllRead(File file, boolean userWrite, boolean userExecute) throws IOException {
        if (OSDetector.os == OSDetector.OS.UNIX || OSDetector.os == OSDetector.OS.MAC) {
            //this methods works in Java 7
            Set<PosixFilePermission> perms = new HashSet<>();
            perms.add(PosixFilePermission.OWNER_READ);
            perms.add(PosixFilePermission.GROUP_READ);
            perms.add(PosixFilePermission.OTHERS_READ);
            if (userWrite) perms.add(PosixFilePermission.OWNER_WRITE);
            if (userExecute) perms.add(PosixFilePermission.OWNER_EXECUTE);
            Files.setPosixFilePermissions(file.toPath(), perms);
        }
    }
}
