package peergos.shared.user;

import peergos.shared.*;
import peergos.shared.crypto.random.*;
import peergos.shared.user.fs.*;

import java.util.*;
import java.util.concurrent.*;

public class FriendSourcedTrieNode implements TrieNode {

    private final String owner;
    private final FileTreeNode cacheDir;
    private final EntryPoint sharedDir;
    private final SafeRandom random;
    private final Fragmenter fragmenter;
    private TrieNode root;
    private long capCount;

    public FriendSourcedTrieNode(FileTreeNode cacheDir,
                                 String owner,
                                 EntryPoint sharedDir,
                                 TrieNode root,
                                 long capCount,
                                 SafeRandom random,
                                 Fragmenter fragmenter) {
        this.cacheDir = cacheDir;
        this.owner = owner;
        this.sharedDir = sharedDir;
        this.root = root;
        this.capCount = capCount;
        this.random = random;
        this.fragmenter = fragmenter;
    }

    public static CompletableFuture<Optional<FriendSourcedTrieNode>> build(FileTreeNode cacheDir,
                                                                           EntryPoint e,
                                                                           NetworkAccess network,
                                                                           SafeRandom random,
                                                                           Fragmenter fragmenter) {
        return network.retrieveEntryPoint(e)
                .thenCompose(sharedDirOpt -> {
                    if (! sharedDirOpt.isPresent())
                        return CompletableFuture.completedFuture(Optional.empty());
                    return FastSharing.loadSharingLinks(cacheDir, sharedDirOpt.get(), e.owner,
                            network, random, fragmenter, true)
                            .thenApply(caps ->
                                    Optional.of(new FriendSourcedTrieNode(cacheDir,
                                            e.owner,
                                            e,
                                            caps.getRetrievedCapabilities().stream()
                                                    .reduce(TrieNodeImpl.empty(),
                                                            (root, cap) -> root.put(trimOwner(cap.path), UserContext.convert(e.owner, cap)),
                                                            (a, b) -> a),
                                            caps.getRecordsRead(),
                                            random, fragmenter)));
                });
    }

    private synchronized CompletableFuture<Boolean> ensureUptodate(NetworkAccess network) {
        // check there are no new capabilities in the friend's shared directory
        return network.retrieveEntryPoint(sharedDir)
                .thenCompose(sharedDirOpt -> {
                    if (!sharedDirOpt.isPresent())
                        return CompletableFuture.completedFuture(true);
                    return FastSharing.getCapabilityCount(sharedDirOpt.get(), network)
                            .thenCompose(count -> {
                                if (count == capCount)
                                    return CompletableFuture.completedFuture(true);
                                return FastSharing.loadSharingLinksFromIndex(cacheDir, sharedDirOpt.get(), owner, network, random, fragmenter, capCount, true)
                                        .thenApply(newCaps -> {
                                            capCount += newCaps.getRecordsRead();
                                            root = newCaps.getRetrievedCapabilities().stream()
                                                    .reduce(root,
                                                            (root, cap) -> root.put(trimOwner(cap.path), UserContext.convert(owner, cap)),
                                                            (a, b) -> a);
                                            return true;
                                        });
                            });
                });
    }

    private static String trimOwner(String path) {
        if (path.startsWith("/"))
            path = path.substring(1);
        return path.substring(path.indexOf("/") + 1);
    }

    @Override
    public synchronized CompletableFuture<Optional<FileTreeNode>> getByPath(String path, NetworkAccess network) {
        return ensureUptodate(network).thenCompose(x -> root.getByPath(path, network));
    }

    @Override
    public synchronized CompletableFuture<Set<FileTreeNode>> getChildren(String path, NetworkAccess network) {
        return ensureUptodate(network).thenCompose(x -> root.getChildren(path, network));
    }

    @Override
    public synchronized Set<String> getChildNames() {
        return root.getChildNames();
    }

    @Override
    public synchronized TrieNode put(String path, EntryPoint e) {
        root = root.put(path, e);
        return this;
    }

    @Override
    public synchronized TrieNode put(String path, TrieNode t) {
        root = root.put(path, t);
        return this;
    }

    @Override
    public synchronized TrieNode removeEntry(String path) {
        root = root.removeEntry(path);
        return this;
    }

    @Override
    public TrieNode addPathMapping(String prefix, String target) {
        throw new IllegalStateException("Unimplemented!");
    }

    @Override
    public boolean hasWriteAccess() {
        return false;
    }

    @Override
    public boolean isEmpty() {
        return root.isEmpty();
    }
}

@@ -4217,32 +4217,783 @@
      %7D);%0A    %7D%0A%0A
+    private CompletableFuture%3COptional%3CFileTreeNode%3E%3E getFriendRoot(NetworkAccess network) %7B%0A        return network.retrieveEntryPoint(sharedDir)%0A                .thenCompose(sharedDirOpt -%3E %7B%0A                    if (! sharedDirOpt.isPresent())%0A                        return CompletableFuture.completedFuture(Optional.empty());%0A                    return sharedDirOpt.get().retrieveParent(network)%0A                            .thenCompose(sharedOpt -%3E %7B%0A                                if (! sharedOpt.isPresent())%0A                                    return CompletableFuture.completedFuture(Optional.empty());%0A                                return sharedOpt.get().retrieveParent(network);%0A                            %7D);%0A                %7D);%0A    %7D%0A%0A
     private stat
@@ -5280,32 +5280,123 @@
 cess network) %7B%0A
+        if (path.isEmpty() %7C%7C path.equals(%22/%22))%0A            return getFriendRoot(network);%0A
         return e

Handle requests for a friend's root