package org.xbib.files.webdav.fs;

import org.xbib.io.webdav.api.DavResource;
import org.xbib.io.webdav.client.WebDavClient;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.channels.SeekableByteChannel;
import java.nio.file.AccessMode;
import java.nio.file.CopyOption;
import java.nio.file.DirectoryStream;
import java.nio.file.DirectoryStream.Filter;
import java.nio.file.FileStore;
import java.nio.file.FileSystem;
import java.nio.file.FileSystemException;
import java.nio.file.FileSystemNotFoundException;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.NoSuchFileException;
import java.nio.file.OpenOption;
import java.nio.file.Path;
import java.nio.file.ProviderMismatchException;
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.FileAttribute;
import java.nio.file.attribute.FileAttributeView;
import java.nio.file.spi.FileSystemProvider;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

public class WebdavFileSystemProvider extends FileSystemProvider {

    private final Map<URI, WebdavFileSystem> hosts = new HashMap<>();

    @Override
    public void copy(Path fileFrom, Path fileTo, CopyOption... options) throws IOException {
        if (!(fileFrom instanceof WebdavPath)) {
            throw new IllegalArgumentException(fileFrom.toString());
        }
        if (!(fileTo instanceof WebdavPath)) {
            throw new IllegalArgumentException(fileTo.toString());
        }
        WebdavPath wPathTo = (WebdavPath) fileTo;
        WebdavFileSystem fileSystem = (WebdavFileSystem) fileTo.getFileSystem();
        WebDavClient webdav = fileSystem.getClient();
        try {
            webdav.put(wPathTo.toUri(), Files.newInputStream(fileFrom));
        } catch (InterruptedException e) {
            throw new IOException(e);
        }
    }

    @Override
    public void createDirectory(Path dir, FileAttribute<?>... attrs) throws IOException {
        if (!(dir instanceof WebdavPath)) {
            throw new IllegalArgumentException(dir.toString());
        }
        WebdavPath wDir = (WebdavPath) dir;
        WebdavFileSystem webdavHost = (WebdavFileSystem) dir.getFileSystem();
        WebDavClient webdav = webdavHost.getClient();
        createDirectoryRecursive(webdav, wDir, attrs);
    }

    private void createDirectoryRecursive(WebDavClient webdav, WebdavPath wDir, FileAttribute<?>[] attrs) {
        if (webdav.exists(wDir.toUri().toString())) {
            return;
        }
        WebdavPath parent = (WebdavPath) wDir.getParent();
        if (parent != null) {
            createDirectoryRecursive(webdav, parent, attrs);
        }
        webdav.createDirectory(wDir.toUri().toString());
    }

    @Override
    public void delete(Path dir) {
        if (!(dir instanceof WebdavPath)) {
            throw new IllegalArgumentException(dir.toString());
        }
        WebdavPath wDir = (WebdavPath) dir;
        WebdavFileSystem webdavHost = (WebdavFileSystem) dir.getFileSystem();
        WebDavClient webdav = webdavHost.getClient();
        String dirString = wDir.toUri().toString();
        webdav.delete(dirString);
    }

    @Override
    public boolean deleteIfExists(Path path) throws IOException {
        WebdavFileSystem webdavFs = (WebdavFileSystem) path.getFileSystem();
        String s = path.toUri().toString();
        return webdavFs.getClient().exists(s);
    }

    @Override
    public FileSystem getFileSystem(URI uri) {
        try {
            return getWebdavHost(uri, true);
        } catch (URISyntaxException ex) {
            throw new FileSystemNotFoundException(uri.toString());
        }
    }

    @Override
    public Path getPath(URI uri) {
        try {
            WebdavFileSystem host = getWebdavHost(uri, true);
            return new WebdavPath(host, uri.getPath());
        } catch (URISyntaxException e) {
            throw new FileSystemNotFoundException(uri.toString());
        }
    }

    private WebdavFileSystem getWebdavHost(URI uri, boolean create) throws URISyntaxException {
        String host = uri.getHost();
        int port = uri.getPort();
        if (port == -1) {
            port = 80;
        }
        String userInfo = uri.getUserInfo();
        URI serverUri = new URI(getScheme(), userInfo, host, port, null, null, null);
        synchronized (hosts) {
            WebdavFileSystem fs = hosts.get(serverUri);
            if (fs == null && create) {
                fs = new WebdavFileSystem(this, serverUri);
                hosts.put(serverUri, fs);
            }
            return fs;
        }
    }

    @Override
    public String getScheme() {
        return "webdav";
    }

    @Override
    public <V extends FileAttributeView> V getFileAttributeView(Path path, Class<V> type, LinkOption... options) {
        throw new UnsupportedOperationException();
    }

    @Override
    public FileStore getFileStore(Path path) {
        throw new UnsupportedOperationException();
    }

    @Override
    public void checkAccess(Path path, AccessMode... modes) throws IOException {
        WebdavFileSystem webdavFs = (WebdavFileSystem) path.getFileSystem();
        final String s = path.toUri().toString();
        final boolean exists = webdavFs.getClient().exists(s);
        if (!exists) {
            throw new NoSuchFileException(s);
        }
    }

    @Override
    public boolean isHidden(Path path) {
        throw new UnsupportedOperationException();
    }

    @Override
    public boolean isSameFile(Path path, Path path2) {
        throw new UnsupportedOperationException();
    }

    @Override
    public void move(Path source, Path target, CopyOption... options) {
        throw new UnsupportedOperationException();
    }

    @Override
    public SeekableByteChannel newByteChannel(Path path, Set<? extends OpenOption> options, FileAttribute<?>... attrs) {
        return new WebdavChannel((WebdavPath) path);
    }

    @Override
    public DirectoryStream<Path> newDirectoryStream(Path arg0, Filter<? super Path> arg1) {
        throw new UnsupportedOperationException();
    }

    @Override
    public FileSystem newFileSystem(URI uri, Map<String, ?> env) throws IOException {
        try {
            return getWebdavHost(uri, true);
        } catch (URISyntaxException e) {
            throw new FileSystemException(e.toString());
        }
    }

    @Override
    public <A extends BasicFileAttributes> A readAttributes(Path path, Class<A> type, LinkOption... options) {
        WebdavFileSystem wfs = (WebdavFileSystem) path.getFileSystem();
        String string = path.toUri().toString();
        List<DavResource> resources = wfs.getClient().list(string);
        if (resources.size() != 1) {
            throw new IllegalArgumentException();
        }
        final DavResource res = resources.get(0);
        if (!type.isAssignableFrom(WebdavFileAttributes.class)) {
            throw new ProviderMismatchException();
        }
        return (A) new WebdavFileAttributes(res);
    }

    @Override
    public Map<String, Object> readAttributes(Path arg0, String arg1, LinkOption... arg2) {
        throw new UnsupportedOperationException();
    }

    @Override
    public void setAttribute(Path arg0, String arg1, Object arg2, LinkOption... arg3) {
        throw new UnsupportedOperationException();
    }
}
