package org.metaborg.core.resource;

import java.io.File;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.net.URI;
import java.net.URISyntaxException;

import org.apache.commons.vfs2.AllFileSelector;
import org.apache.commons.vfs2.FileName;
import org.apache.commons.vfs2.FileObject;
import org.apache.commons.vfs2.FileSystemException;
import org.apache.commons.vfs2.FileSystemManager;
import org.apache.commons.vfs2.FileSystemOptions;
import org.apache.commons.vfs2.FileType;
import org.apache.commons.vfs2.VFS;
import org.apache.commons.vfs2.impl.DefaultFileReplicator;
import org.apache.commons.vfs2.impl.DefaultFileSystemManager;
import org.apache.commons.vfs2.provider.FileReplicator;
import org.apache.commons.vfs2.provider.local.LocalFile;
import org.apache.commons.vfs2.provider.res.ResourceFileSystemConfigBuilder;
import org.metaborg.core.MetaborgRuntimeException;
import org.metaborg.util.file.FileUtils;
import org.metaborg.util.file.URIEncode;
import org.metaborg.util.log.ILogger;
import org.metaborg.util.log.LoggerUtils;

import com.google.inject.Inject;
import com.google.inject.name.Named;

public class ResourceService implements IResourceService {
    private static final ILogger logger = LoggerUtils.logger(ResourceService.class);

    private final FileSystemManager fileSystemManager;
    private final FileSystemOptions fileSystemOptions;


    @Inject public ResourceService(FileSystemManager fileSystemManager,
        @Named("ResourceClassLoader") ClassLoader classLoader) {
        this.fileSystemManager = fileSystemManager;
        this.fileSystemOptions = new FileSystemOptions();

        if(classLoader == null) {
            classLoader = this.getClass().getClassLoader();
        }

        ResourceFileSystemConfigBuilder.getInstance().setClassLoader(fileSystemOptions, classLoader);
    }

    @Override public void close() throws Exception {
        if(fileSystemManager instanceof DefaultFileSystemManager) {
            final DefaultFileSystemManager defaultFileSystemManager = (DefaultFileSystemManager) fileSystemManager;
            final FileReplicator replicator = defaultFileSystemManager.getReplicator();
            if(replicator instanceof DefaultFileReplicator) {
                final DefaultFileReplicator defaultFileReplicator = (DefaultFileReplicator) replicator;
                defaultFileReplicator.close();
            } else {
                logger.warn("File replicator {} does not support cleaning up generated temporary files", replicator);
            }
        } else {
            logger.warn("File system manager {} does not support cleaning up generated temporary files",
                fileSystemManager);
        }
    }


    @Override public FileObject root() {
        try {
            return fileSystemManager.getBaseFile();
        } catch(FileSystemException e) {
            throw new MetaborgRuntimeException(e);
        }
    }

    @Override public FileObject resolve(String uri) {
        try {
            final String uriEncoded = URIEncode.encode(uri);
            return fileSystemManager.resolveFile(uriEncoded, fileSystemOptions);
        } catch(FileSystemException e) {
            throw new MetaborgRuntimeException(e);
        }
    }

    @Override public FileObject resolve(File file) {
        try {
            return fileSystemManager.toFileObject(file);
        } catch(FileSystemException e) {
            throw new MetaborgRuntimeException(e);
        }
    }

    @Override public FileObject resolve(URI uri) {
        try {
            return fileSystemManager.resolveFile(uri.toString());
        } catch(FileSystemException e) {
            throw new MetaborgRuntimeException(e);
        }
    }

    @Override public FileObject resolve(FileObject parent, String path) {
        final File file = new File(path);

        try {
            final URI uri = new URI(path);
            if(uri.isAbsolute()) {
                return resolve(path);
            }
        } catch(URISyntaxException e) {
            // Ignore
        }

        if(file.isAbsolute()) {
            return resolve("file://" + path);
        }

        try {
            return parent.resolveFile(path);
        } catch(FileSystemException e) {
            throw new MetaborgRuntimeException(e);
        }
    }

    @Override public FileName resolveToName(String uri) {
        try {
            final String uriEncoded = URIEncode.encode(uri);
            return fileSystemManager.resolveURI(uriEncoded);
        } catch(FileSystemException e) {
            throw new MetaborgRuntimeException(e);
        }
    }

    @Override public FileName resolveToName(URI uri) {
        try {
            return fileSystemManager.resolveURI(uri.toString());
        } catch(FileSystemException e) {
            throw new MetaborgRuntimeException(e);
        }
    }

    @Override public File localFile(FileObject resource) {
        if(resource instanceof LocalFile) {
            return FileUtils.toFile(resource);
        }

        try {
            return resource.getFileSystem().replicateFile(resource, new AllFileSelector());
        } catch(FileSystemException e) {
            throw new MetaborgRuntimeException("Could not get local file for " + resource, e);
        }
    }

    @Override public File localFile(FileObject resource, FileObject dir) {
        if(resource instanceof LocalFile) {
            return FileUtils.toFile(resource);
        }

        final File localDir = localPath(dir);
        if(localDir == null) {
            throw new MetaborgRuntimeException("Replication directory " + dir
                + " is not on the local filesystem, cannot get local file for " + resource);
        }
        try {
            dir.createFolder();

            final FileObject copyLoc;
            if(resource.getType() == FileType.FOLDER) {
                copyLoc = dir;
            } else {
                copyLoc = dir.resolveFile(resource.getName().getBaseName());
            }
            copyLoc.copyFrom(resource, new AllFileSelector());

            return localDir;
        } catch(FileSystemException e) {
            throw new MetaborgRuntimeException("Could not get local file for " + resource, e);
        }
    }

    @Override public File localPath(FileObject resource) {
        if(resource instanceof LocalFile) {
            return FileUtils.toFile(resource);
        }
        return null;
    }


    @Deprecated @Override public FileObject userStorage() {
        try {
            final FileObject storageDir = root().resolveFile(".cache");
            storageDir.createFolder();
            return storageDir;
        } catch(FileSystemException e) {
            throw new MetaborgRuntimeException(e);
        }
    }


    public static void writeFileObject(FileObject fo, ObjectOutput out) throws IOException {
        out.writeObject(fo.getName().getURI());
    }

    public static FileObject readFileObject(ObjectInput in) throws ClassNotFoundException, IOException {
        String uri = (String) in.readObject();
        return VFS.getManager().resolveFile(uri);
    }
}

@@ -3823,51 +3823,8 @@
 ) %7B%0A
-        final File file = new File(path);%0A%0A
     
@@ -4028,32 +4028,74 @@
 nore%0A        %7D%0A%0A
+        final File file = new File(path);%0A
         if(file.

Move assignment down where it is needed.