package org.xbib.files;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URI;
import java.nio.file.CopyOption;
import java.nio.file.DirectoryStream;
import java.nio.file.FileVisitOption;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.FileAttribute;
import java.nio.file.attribute.PosixFileAttributes;
import java.nio.file.attribute.PosixFilePermission;
import java.time.Instant;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.ServiceLoader;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Stream;

public interface FileService {

    class Holder {

        private static FileService createFiles(URI uri, Map<String, ?> env) {
            ServiceLoader<FileServiceProvider> serviceLoader = ServiceLoader.load(FileServiceProvider.class);
            Optional<FileService> first = serviceLoader.stream()
                    .map(ServiceLoader.Provider::get)
                    .filter(Objects::nonNull)
                    .map(p -> p.provide(uri, env))
                    .filter(Objects::nonNull)
                    .findFirst();
            return first.orElse(null);
        }
    }

    static FileService newInstance(String uri, Map<String, ?> env) {
        return Holder.createFiles(URI.create(uri), env);
    }

    static FileService newInstance(URI uri, Map<String, ?> env) {
        return Holder.createFiles(uri, env);
    }

    Boolean exists(String path) throws IOException;

    Boolean isExecutable(String path) throws IOException;

    Boolean isDirectory(String path) throws IOException;

    Boolean isRegularFile(String path) throws IOException;

    Boolean isHidden(String path) throws IOException;

    Boolean isSameFile(String path1, String path2) throws IOException;

    Boolean isSymbolicLink(String path) throws IOException;

    Boolean isReadable(String path) throws IOException;

    Boolean isWritable(String path) throws IOException;

    void setAttribute(String path, String attribute, Object value) throws IOException;

    Object getAttribute(String path, String attribute) throws IOException;

    void setPermissions(String path, Set<PosixFilePermission> permissions) throws IOException;

    Set<PosixFilePermission> getPermissions(String path) throws IOException;

    void setPosixFileAttributes(String path,
                                String owner,
                                String group,
                                Instant lastModifiedTime,
                                Instant lastAccessTime,
                                Instant createTime) throws IOException;

    PosixFileAttributes getPosixFileAttributes(String path) throws IOException;

    void setOwner(String path, String owner) throws IOException;

    String getOwner(String path) throws IOException;

    void setGroup(String path, String group) throws IOException;

    String getGroup(String path) throws IOException;

    void setLastModifiedTime(String path, Instant lastModified) throws IOException;

    Instant getLastModifiedTime(String path) throws IOException;

    void createFile(String path, FileAttribute<?>... attributes) throws IOException;

    void createDirectory(String path, FileAttribute<?>... attributes) throws IOException;

    void createDirectories(String path, FileAttribute<?>... attributes) throws IOException;

    void upload(Path source,
                String target,
                CopyOption... copyOptions) throws Exception;

    void upload(Path source,
                String target,
                Set<PosixFilePermission> dirPermissions,
                Set<PosixFilePermission> filePermissions,
                CopyOption... copyOptions) throws IOException;

    void upload(Path source,
                Path target,
                CopyOption... copyOptions) throws IOException;

    void upload(Path source,
                Path target,
                Set<PosixFilePermission> dirPermissions,
                Set<PosixFilePermission> filePermissions,
                CopyOption... copyOptions) throws IOException;

    void upload(InputStream source,
                String target,
                CopyOption... copyOptions) throws IOException;

    void upload(InputStream source,
                String target,
                Set<PosixFilePermission> dirPermissions,
                Set<PosixFilePermission> filePermissions,
                CopyOption... copyOptions) throws IOException;

    void upload(InputStream source,
                Path target,
                CopyOption... copyOptions) throws IOException;

    void upload(InputStream source,
                Path target,
                Set<PosixFilePermission> dirPermissions,
                Set<PosixFilePermission> filePermissions,
                CopyOption... copyOptions) throws IOException;

    void download(String source,
                  Path target,
                  CopyOption... copyOptions) throws IOException;

    void download(Path source,
                  Path target,
                  CopyOption... copyOptions) throws IOException;

    void download(String source,
                  OutputStream target) throws IOException;

    void download(Path source,
                  OutputStream target) throws IOException;

    void rename(String source, String target, CopyOption... copyOptions) throws IOException;

    void remove(String source) throws IOException;

    void copy(String source, String target, CopyOption... copyOptions) throws IOException;

    DirectoryStream<Path> stream(String path, String glob) throws IOException;

    DirectoryStream<Path> stream(String path, DirectoryStream.Filter<Path> filter) throws IOException;

    Stream<Path> list(String path) throws IOException;

    Stream<Path> walk(String path, FileVisitOption... options) throws IOException;

    Stream<Path> walk(String path, int maxdepth, FileVisitOption... options) throws IOException;

    /**
     * Replacement for Files.walk() without AccessDeniedException.
     * Caller must use a try-with-resources statement.
     *
     * @param p the path
     * @return a stream of readable files under the path
     */
    static Stream<Path> walk(Path p) {
        if (Files.isReadable(p)) {
            if (Files.isDirectory(p)) {
                try {
                    return Files.list(p).flatMap(FileService::walk);
                } catch (IOException e) {
                    Logger.getLogger(FileService.class.getName())
                            .log(Level.FINE,"skipped " + p + " because of " + e.getMessage());
                    return Stream.empty();
                }
            } else {
                return Stream.of(p);
            }
        } else {
            Logger.getLogger(FileService.class.getName())
                    .log(Level.WARNING, "unreadable: " + p);
        }
        return Stream.empty();
    }
}
