/*-
 * Copyright (C) 2014 Erik Larsson
 *
 * 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 3 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, see <http://www.gnu.org/licenses/>.
 */

package org.catacombae.hfsexplorer;

import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;

/**
 * @author <a href="https://catacombae.org" target="_top">Erik Larsson</a>
 */
public class Java7Util {
    /**
     * Tests whether or not the current VM is a Java 7 or higher VM. This method
     * should always be invoked to check that the version number of the
     * currently running JRE is higher than or equal to 1.7 before invoking any
     * of the methods in Java7Specific.
     *
     * @return whether or not the current VM is a Java 7 or higher VM.
     */
    public static boolean isJava7OrHigher() {
    	return System.getProperty("java.specification.version").
                compareTo("1.7") >= 0;
    }

    public static void setFileTimes(String path, Date creationTime,
            Date lastAccessTime, Date lastModifiedTime)
            throws ClassNotFoundException, NoSuchMethodException,
            IllegalAccessException, IllegalArgumentException,
            InvocationTargetException, NoSuchFieldException
    {
        Class<?> pathClass = Class.forName("java.nio.file.Path");
        Class<?> fileSystemsClass = Class.forName("java.nio.file.FileSystems");
        Class<?> fileSystemClass = Class.forName("java.nio.file.FileSystem");
        Class<?> basicFileAttributeViewClass =
                Class.forName("java.nio.file.attribute.BasicFileAttributeView");
        Class<?> filesClass = Class.forName("java.nio.file.Files");
        Class<?> linkOptionClass = Class.forName("java.nio.file.LinkOption");
        Class<?> fileTimeClass =
                Class.forName("java.nio.file.attribute.FileTime");

        /* FileSystem defaultFileSystem = FileSystems.getDefault(); */
        Method fileSystemsGetDefaultMethod =
                fileSystemsClass.getMethod("getDefault");
        Object defaultFileSystemObject =
                fileSystemsGetDefaultMethod.invoke(null);

        /* Path p = defaultFileSystem.getPath(path); */
        Method fileSystemGetPathMethod =
                fileSystemClass.getMethod("getPath", String.class,
                String[].class);
        Object pObject =
                fileSystemGetPathMethod.invoke(defaultFileSystemObject, path,
                new String[0]);

        /* BasicFileAttributeView attrView =
         *         Files.getFileAttributeView(p, BasicFileAttributeView.class,
         *         LinkOption.NOFOLLOW_LINKS); */
        Field noFollowLinksField = linkOptionClass.getField("NOFOLLOW_LINKS");
        Object noFollowLinksObject = noFollowLinksField.get(null);

        Object linkOptionsArray = Array.newInstance(linkOptionClass, 1);
        Array.set(linkOptionsArray, 0, noFollowLinksObject);

        Method getFileAttributeViewMethod =
                filesClass.getMethod("getFileAttributeView", pathClass,
                Class.class, linkOptionsArray.getClass());
        Object attrViewObject =
                getFileAttributeViewMethod.invoke(null, pObject,
                basicFileAttributeViewClass, linkOptionsArray);

        /*
         * FileTime creationFileTime;
         * if(creationTime != null) {
         *     creationFileTime = FileTime.fromMillis(creationTime.getTime());
         * }
         * else {
         *     creationFileTime = null;
         * }
         */
        Object creationFileTimeObject;
        if(creationTime != null) {
            Method fileTimefromMillisMethod =
                    fileTimeClass.getMethod("fromMillis", long.class);
            creationFileTimeObject =
                    fileTimefromMillisMethod.invoke(null,
                    Long.valueOf(creationTime.getTime()));
        }
        else {
            creationFileTimeObject = null;
        }

        /*
         * FileTime lastAccessFileTime;
         * if(lastAccessTime != null) {
         *     lastAccessFileTime =
         *         FileTime.fromMillis(lastAccessTime.getTime());
         * }
         * else {
         *     lastAccessFileTime = null;
         * }
         */
        Object lastAccessFileTimeObject;
        if(lastAccessTime != null) {
            Method fileTimefromMillisMethod =
                    fileTimeClass.getMethod("fromMillis", long.class);
            lastAccessFileTimeObject =
                    fileTimefromMillisMethod.invoke(null,
                    Long.valueOf(lastAccessTime.getTime()));
        }
        else {
            lastAccessFileTimeObject = null;
        }

        /*
         * FileTime lastModifiedFileTime;
         * if(lastModifiedTime != null) {
         *     lastModifiedFileTime =
         *         FileTime.fromMillis(lastModifiedTime.getTime());
         * }
         * else {
         *     lastModifiedFileTime = null;
         * }
         */
        Object lastModifiedFileTimeObject;
        if(lastModifiedTime != null) {
            Method fileTimefromMillisMethod =
                    fileTimeClass.getMethod("fromMillis", long.class);
            lastModifiedFileTimeObject =
                    fileTimefromMillisMethod.invoke(null,
                    Long.valueOf(lastModifiedTime.getTime()));
        }
        else {
            lastModifiedFileTimeObject = null;
        }

        /* attrView.setTimes(lastModifiedFileTime, lastAccessFileTime,
         *     creationFileTime); */
        Method basicFileAttributeViewSetTimesMethod =
                basicFileAttributeViewClass.getMethod("setTimes", fileTimeClass,
                fileTimeClass, fileTimeClass);
        try {
            basicFileAttributeViewSetTimesMethod.invoke(attrViewObject,
                    lastModifiedFileTimeObject, lastAccessFileTimeObject,
                    creationFileTimeObject);
        } catch(InvocationTargetException ex) {
            final Throwable cause = ex.getCause();
            if(cause instanceof ClassNotFoundException) {
                throw (ClassNotFoundException) cause;
            }
            else if(cause instanceof NoSuchMethodException) {
                throw (NoSuchMethodException) cause;
            }
            else if(cause instanceof IllegalAccessException) {
                throw (IllegalAccessException) cause;
            }
            else if(cause instanceof IllegalArgumentException) {
                throw (IllegalArgumentException) cause;
            }
            else if(cause instanceof InvocationTargetException) {
                throw (InvocationTargetException) cause;
            }
            else if(cause instanceof NoSuchFieldException) {
                throw (NoSuchFieldException) cause;
            }
            else if(cause instanceof RuntimeException) {
                throw (RuntimeException) cause;
            }
            else {
                throw ex;
            }
        }
    }

    public static void setPosixPermissions(String path,
            boolean ownerRead, boolean ownerWrite, boolean ownerExecute,
            boolean groupRead, boolean groupWrite, boolean groupExecute,
            boolean othersRead, boolean othersWrite, boolean othersExecute)
            throws ClassNotFoundException, NoSuchMethodException,
            IllegalAccessException, IllegalArgumentException,
            InvocationTargetException, NoSuchFieldException
    {
        Class<?> fileSystemsClass = Class.forName("java.nio.file.FileSystems");
        Class<?> fileSystemClass = Class.forName("java.nio.file.FileSystem");
        Class<?> pathClass = Class.forName("java.nio.file.Path");
        Class<?> posixFileAttributeViewClass =
                Class.forName("java.nio.file.attribute.PosixFileAttributeView");
        Class<?> filesClass = Class.forName("java.nio.file.Files");
        Class<?> linkOptionClass = Class.forName("java.nio.file.LinkOption");
        Class<?> posixFilePermissionClass =
                Class.forName("java.nio.file.attribute.PosixFilePermission");

        /* java.nio.file.FileSystem defaultFileSystem =
                   java.nio.file.FileSystems.getDefault(); */
        Method fileSystemsGetDefaultMethod =
                fileSystemsClass.getMethod("getDefault");
        Object defaultFileSystemObject =
                fileSystemsGetDefaultMethod.invoke(null);

        /* java.nio.file.Path p = defaultFileSystem.getPath(path); */
        Method fileSystemGetPathMethod =
                fileSystemClass.getMethod("getPath", String.class,
                String[].class);
        Object pObject =
                fileSystemGetPathMethod.invoke(defaultFileSystemObject, path,
                new String[0]);

        /* java.nio.file.attribute.PosixFileAttributeView attrView =
         *         java.nio.file.Files.getFileAttributeView(p,
         *         java.nio.file.attribute.PosixFileAttributeView.class,
         *         java.nio.file.LinkOption.NOFOLLOW_LINKS); */
        Field noFollowLinksField = linkOptionClass.getField("NOFOLLOW_LINKS");
        Object noFollowLinksObject = noFollowLinksField.get(null);

        Object linkOptionsArray = Array.newInstance(linkOptionClass, 1);
        Array.set(linkOptionsArray, 0, noFollowLinksObject);

        Method getFileAttributeViewMethod =
                filesClass.getMethod("getFileAttributeView", pathClass,
                Class.class, linkOptionsArray.getClass());
        Object attrViewObject =
                getFileAttributeViewMethod.invoke(null, pObject,
                posixFileAttributeViewClass, linkOptionsArray);

        if(attrViewObject == null) {
            /* No PosixFileAttributeView available. Platform does not support
             * POSIX attributes. Just return quietly here. */
            return;
        }

        HashSet<Object> perms = new HashSet<Object>();

        if(ownerRead) {
            Field curField = posixFilePermissionClass.getField("OWNER_READ");
            Object curObject = curField.get(null);
            perms.add(curObject);
        }

        if(ownerWrite) {
            Field curField = posixFilePermissionClass.getField("OWNER_WRITE");
            Object curObject = curField.get(null);
            perms.add(curObject);
        }

        if(ownerExecute) {
            Field curField = posixFilePermissionClass.getField("OWNER_EXECUTE");
            Object curObject = curField.get(null);
            perms.add(curObject);
        }

        if(groupRead) {
            Field curField = posixFilePermissionClass.getField("GROUP_READ");
            Object curObject = curField.get(null);
            perms.add(curObject);
        }

        if(groupWrite) {
            Field curField = posixFilePermissionClass.getField("GROUP_WRITE");
            Object curObject = curField.get(null);
            perms.add(curObject);
        }

        if(groupExecute) {
            Field curField = posixFilePermissionClass.getField("GROUP_EXECUTE");
            Object curObject = curField.get(null);
            perms.add(curObject);
        }

        if(othersRead) {
            Field curField = posixFilePermissionClass.getField("OTHERS_READ");
            Object curObject = curField.get(null);
            perms.add(curObject);
        }

        if(othersWrite) {
            Field curField = posixFilePermissionClass.getField("OTHERS_WRITE");
            Object curObject = curField.get(null);
            perms.add(curObject);
        }

        if(othersExecute) {
            Field curField =
                    posixFilePermissionClass.getField("OTHERS_EXECUTE");
            Object curObject = curField.get(null);
            perms.add(curObject);
        }

        /* attrView.setPermissions(perms); */
        Method posixFileAttributeViewSetPermissionMethod =
                posixFileAttributeViewClass.getMethod("setPermissions",
                Set.class);
        try {
            posixFileAttributeViewSetPermissionMethod.invoke(attrViewObject,
                    perms);
        } catch(InvocationTargetException ex) {
            final Throwable cause = ex.getCause();
            if(cause instanceof ClassNotFoundException) {
                throw (ClassNotFoundException) cause;
            }
            else if(cause instanceof NoSuchMethodException) {
                throw (NoSuchMethodException) cause;
            }
            else if(cause instanceof IllegalAccessException) {
                throw (IllegalAccessException) cause;
            }
            else if(cause instanceof IllegalArgumentException) {
                throw (IllegalArgumentException) cause;
            }
            else if(cause instanceof InvocationTargetException) {
                throw (InvocationTargetException) cause;
            }
            else if(cause instanceof NoSuchFieldException) {
                throw (NoSuchFieldException) cause;
            }
            else if(cause instanceof RuntimeException) {
                throw (RuntimeException) cause;
            }
            else {
                throw ex;
            }
        }
    }

    public static void setPosixOwners(String path, int ownerId, int groupId)
            throws ClassNotFoundException, NoSuchMethodException,
            IllegalAccessException, IllegalArgumentException,
            InvocationTargetException, NoSuchFieldException
    {
        Class<?> fileSystemsClass = Class.forName("java.nio.file.FileSystems");
        Class<?> fileSystemClass = Class.forName("java.nio.file.FileSystem");
        Class<?> pathClass = Class.forName("java.nio.file.Path");
        Class<?> filesClass = Class.forName("java.nio.file.Files");
        Class<?> linkOptionClass = Class.forName("java.nio.file.LinkOption");

        Method fileSystemsGetDefaultMethod =
                fileSystemsClass.getMethod("getDefault");
        Method fileSystemGetPathMethod =
                fileSystemClass.getMethod("getPath", String.class,
                String[].class);

        Field noFollowLinksField = linkOptionClass.getField("NOFOLLOW_LINKS");

        Object noFollowLinksObject = noFollowLinksField.get(null);
        Object linkOptionsArray = Array.newInstance(linkOptionClass, 1);
        Array.set(linkOptionsArray, 0, noFollowLinksObject);

        /* java.nio.file.FileSystem defaultFileSystem =
                   java.nio.file.FileSystems.getDefault(); */
        Object defaultFileSystemObject =
                fileSystemsGetDefaultMethod.invoke(null);

        /* java.nio.file.Path p = defaultFileSystem.getPath(path); */
        Object pObject =
                fileSystemGetPathMethod.invoke(defaultFileSystemObject, path,
                new String[0]);

        Method filesSetAttributeMethod =
                filesClass.getMethod("setAttribute", pathClass,
                String.class, Object.class, linkOptionsArray.getClass());
        try {
            /* java.nio.file.Files.setAttribute(p, "unix:uid",
                    Integer.valueOf(ownerId),
                    java.nio.file.LinkOption.NOFOLLOW_LINKS);*/
            filesSetAttributeMethod.invoke(null, pObject, "unix:uid",
                    Integer.valueOf(ownerId), linkOptionsArray);

            /* java.nio.file.Files.setAttribute(p, "unix:gid",
                    Integer.valueOf(groupId),
                    java.nio.file.LinkOption.NOFOLLOW_LINKS); */
            filesSetAttributeMethod.invoke(null, pObject, "unix:gid",
                    Integer.valueOf(groupId), linkOptionsArray);
        } catch(InvocationTargetException ex) {
            final Throwable cause = ex.getCause();
            if(cause instanceof UnsupportedOperationException) {
                /* Do nothing. This is expected on non-UNIX platforms. */
            }
            else if(cause instanceof ClassNotFoundException) {
                throw (ClassNotFoundException) cause;
            }
            else if(cause instanceof NoSuchMethodException) {
                throw (NoSuchMethodException) cause;
            }
            else if(cause instanceof IllegalAccessException) {
                throw (IllegalAccessException) cause;
            }
            else if(cause instanceof IllegalArgumentException) {
                throw (IllegalArgumentException) cause;
            }
            else if(cause instanceof InvocationTargetException) {
                throw (InvocationTargetException) cause;
            }
            else if(cause instanceof NoSuchFieldException) {
                throw (NoSuchFieldException) cause;
            }
            else if(cause instanceof RuntimeException) {
                throw (RuntimeException) cause;
            }
            else {
                throw ex;
            }
        }
    }

    public static void createSymbolicLink(String linkPathString,
            String targetPathString)
            throws ClassNotFoundException, NoSuchMethodException,
            IllegalAccessException, IllegalArgumentException,
            InvocationTargetException, NoSuchFieldException
    {
        Class<?> fileSystemsClass = Class.forName("java.nio.file.FileSystems");
        Class<?> fileSystemClass = Class.forName("java.nio.file.FileSystem");
        Class<?> pathClass = Class.forName("java.nio.file.Path");
        Class<?> filesClass = Class.forName("java.nio.file.Files");
        Class<?> fileAttributeClass =
                Class.forName("java.nio.file.attribute.FileAttribute");

        Object emptyFileAttributeArray =
                Array.newInstance(fileAttributeClass, 0);

        Method fileSystemsGetDefaultMethod =
                fileSystemsClass.getMethod("getDefault");
        Method fileSystemGetPathMethod =
                fileSystemClass.getMethod("getPath", String.class,
                String[].class);
        Method filesCreateSymbolicLinkMethod =
                filesClass.getMethod("createSymbolicLink", pathClass,
                pathClass, emptyFileAttributeArray.getClass());

        /* java.nio.file.FileSystem defaultFileSystem =
                   java.nio.file.FileSystems.getDefault(); */
        Object defaultFileSystemObject =
                fileSystemsGetDefaultMethod.invoke(null);

        /* java.nio.file.Path linkPath =
                   defaultFileSystem.getPath(linkPathString); */
        Object linkPathObject =
                fileSystemGetPathMethod.invoke(defaultFileSystemObject,
                linkPathString, new String[0]);

        /* java.nio.file.Path targetPath =
                   defaultFileSystem.getPath(targetPathString); */
        Object targetPathObject =
                fileSystemGetPathMethod.invoke(defaultFileSystemObject,
                targetPathString, new String[0]);

        /* java.nio.file.Files.createSymbolicLink(linkPath, targetPath); */
        try {
            filesCreateSymbolicLinkMethod.invoke(null, linkPathObject,
                    targetPathObject, emptyFileAttributeArray);
        } catch(InvocationTargetException e) {
            final Throwable cause = e.getCause();
            if(cause instanceof ClassNotFoundException) {
                throw (ClassNotFoundException) cause;
            }
            else if(cause instanceof NoSuchMethodException) {
                throw (NoSuchMethodException) cause;
            }
            else if(cause instanceof IllegalAccessException) {
                throw (IllegalAccessException) cause;
            }
            else if(cause instanceof IllegalArgumentException) {
                throw (IllegalArgumentException) cause;
            }
            else if(cause instanceof InvocationTargetException) {
                throw (InvocationTargetException) cause;
            }
            else if(cause instanceof NoSuchFieldException) {
                throw (NoSuchFieldException) cause;
            }
            else if(cause instanceof RuntimeException) {
                throw (RuntimeException) cause;
            }
            else {
                throw e;
            }
        }
    }
}
