/*
 * Copyright 2019 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.gradle.internal.snapshot;

import org.gradle.internal.file.FileType;

import java.util.Optional;
import java.util.concurrent.atomic.AtomicBoolean;

public abstract class AbstractIncompleteSnapshotWithChildren implements FileSystemNode {
    protected final ChildMap<FileSystemNode> children;

    @SuppressWarnings("unchecked")
    public AbstractIncompleteSnapshotWithChildren(ChildMap<? extends FileSystemNode> children) {
        this.children = (ChildMap<FileSystemNode>) children;
    }

    @Override
    public ReadOnlyFileSystemNode getNode(VfsRelativePath targetPath, CaseSensitivity caseSensitivity) {
        return SnapshotUtil.getChild(children, targetPath, caseSensitivity);
    }

    @Override
    public Optional<FileSystemNode> invalidate(VfsRelativePath targetPath, CaseSensitivity caseSensitivity, SnapshotHierarchy.NodeDiffListener diffListener) {
        ChildMap<FileSystemNode> newChildren = children.invalidate(targetPath, caseSensitivity, new ChildMap.InvalidationHandler<FileSystemNode, FileSystemNode>() {
            @Override
            public Optional<FileSystemNode> handleAsDescendantOfChild(VfsRelativePath pathInChild, FileSystemNode child) {
                return child.invalidate(pathInChild, caseSensitivity, diffListener);
            }

            @Override
            public void handleAsAncestorOfChild(String childPath, FileSystemNode child) {
                diffListener.nodeRemoved(child);
            }

            @Override
            public void handleExactMatchWithChild(FileSystemNode child) {
                diffListener.nodeRemoved(child);
            }

            @Override
            public void handleUnrelatedToAnyChild() {
            }
        });
        if (newChildren.isEmpty()) {
            return withAllChildrenRemoved();
        }
        if (newChildren == children) {
            return Optional.of(withIncompleteChildren());
        }
        return Optional.of(withIncompleteChildren(newChildren));
    }

    @Override
    public FileSystemNode store(VfsRelativePath targetPath, CaseSensitivity caseSensitivity, MetadataSnapshot snapshot, SnapshotHierarchy.NodeDiffListener diffListener) {
        ChildMap<FileSystemNode> newChildren = children.store(targetPath, caseSensitivity, new ChildMap.StoreHandler<FileSystemNode>() {
            @Override
            public FileSystemNode handleAsDescendantOfChild(VfsRelativePath pathInChild, FileSystemNode child) {
                return child.store(pathInChild, caseSensitivity, snapshot, diffListener);
            }

            @Override
            public FileSystemNode handleAsAncestorOfChild(String childPath, FileSystemNode child) {
                FileSystemNode newChild = snapshot.asFileSystemNode();
                diffListener.nodeRemoved(child);
                diffListener.nodeAdded(newChild);
                return newChild;
            }

            @Override
            public FileSystemNode mergeWithExisting(FileSystemNode child) {
                if (snapshot instanceof CompleteFileSystemLocationSnapshot || !child.getSnapshot().map(oldSnapshot -> oldSnapshot instanceof CompleteFileSystemLocationSnapshot).orElse(false)) {
                    FileSystemNode newChild = snapshot.asFileSystemNode();
                    diffListener.nodeRemoved(child);
                    diffListener.nodeAdded(newChild);
                    return newChild;
                } else {
                    return child;
                }
            }

            @Override
            public FileSystemNode createChild() {
                FileSystemNode newChild = snapshot.asFileSystemNode();
                diffListener.nodeAdded(newChild);
                return newChild;
            }

            @Override
            public FileSystemNode createNodeFromChildren(ChildMap<FileSystemNode> children) {
                AtomicBoolean isDirectory = new AtomicBoolean(false);
                children.visitChildren((__, child) -> child.getSnapshot().map(this::isRegularFileOrDirectory).ifPresent(notMissing -> isDirectory.compareAndSet(false, notMissing)));
                return isDirectory.get() ? new PartialDirectorySnapshot(children) : new UnknownSnapshot(children);
            }

            private boolean isRegularFileOrDirectory(MetadataSnapshot metadataSnapshot) {
                return metadataSnapshot.getType() != FileType.Missing;
            }
        });
        if (newChildren == children) {
            return this;
        }
        return withIncompleteChildren(newChildren);
    }

    @Override
    public Optional<MetadataSnapshot> getSnapshot(VfsRelativePath targetPath, CaseSensitivity caseSensitivity) {
        return SnapshotUtil.getMetadataFromChildren(children, targetPath, caseSensitivity, Optional::empty);
    }

    /**
     * Returns an updated node with the same children. The list of children are
     * incomplete, even if they were complete before.
     */
    protected abstract FileSystemNode withIncompleteChildren();

    /**
     * Returns an updated node with an updated list of children.
     *
     * Caller must ensure the child list is not be mutated as the method
     * doesn't make a defensive copy.
     */
    protected abstract FileSystemNode withIncompleteChildren(ChildMap<? extends FileSystemNode> newChildren);

    /**
     * Returns an updated node with all children removed, or {@link Optional#empty()}
     * if the node without children would contain no useful information to keep around.
     */
    protected abstract Optional<FileSystemNode> withAllChildrenRemoved();

    @Override
    public void accept(SnapshotHierarchy.SnapshotVisitor snapshotVisitor) {
        children.visitChildren((childPath, child) -> child.accept(snapshotVisitor));
    }

    @Override
    public boolean hasDescendants() {
        AtomicBoolean hasDescendants = new AtomicBoolean();
        children.visitChildren((childPath, child) -> hasDescendants.compareAndSet(false, child.hasDescendants()));
        return hasDescendants.get();
    }
}

@@ -740,39 +740,64 @@
 til.
-concurrent.atomic.AtomicBoolean
+function.BiConsumer;%0Aimport java.util.function.Predicate
 ;%0A%0Ap
@@ -4509,31 +4509,25 @@
             
-AtomicB
+b
 oolean isDir
@@ -4539,212 +4539,109 @@
 y = 
-new AtomicBoolean(false);%0A                children.visitChildren((__, child) -%3E child.getSnapshot().map(this::isRegularFileOrDirectory).ifPresent(notMissing -%3E isDirectory.compareAndSet(false, notMissing)
+anyChildMatches(children, node -%3E node.getSnapshot().map(this::isRegularFileOrDirectory).orElse(false
 ));%0A
@@ -4674,22 +4674,16 @@
 irectory
-.get()
  ? new P
@@ -6363,211 +6363,948 @@
     
-AtomicBoolean hasDescendants = new AtomicBoolean();%0A        children.visitChildren((childPath, child) -%3E hasDescendants.compareAndSet(false, child.hasDescendants()));%0A        return hasDescendants.get();
+return anyChildMatches(children, FileSystemNode::hasDescendants);%0A    %7D%0A%0A    private boolean anyChildMatches(ChildMap%3CFileSystemNode%3E children, Predicate%3CFileSystemNode%3E predicate) %7B%0A        AnyChildHasPropertyVisitor visitor = new AnyChildHasPropertyVisitor(predicate);%0A        children.visitChildren(visitor);%0A        return visitor.isAnyChildMatches();%0A    %7D%0A%0A    public static class AnyChildHasPropertyVisitor implements BiConsumer%3CString, FileSystemNode%3E %7B%0A        private final Predicate%3CFileSystemNode%3E predicate;%0A        private boolean anyChildMatches = false;%0A%0A        public AnyChildHasPropertyVisitor(Predicate%3CFileSystemNode%3E predicate) %7B%0A            this.predicate = predicate;%0A        %7D%0A%0A        @Override%0A        public void accept(String path, FileSystemNode child) %7B%0A            anyChildMatches %7C= predicate.test(child);%0A        %7D%0A%0A        public boolean isAnyChildMatches() %7B%0A            return anyChildMatches;%0A        %7D
 %0A   

Replace AtomicBoolean by visitor