/*
 * Copyright 2020 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.caching.internal.controller.impl;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSortedMap;
import com.google.common.collect.Interner;
import org.gradle.caching.BuildCacheKey;
import org.gradle.caching.internal.CacheableEntity;
import org.gradle.caching.internal.controller.BuildCacheCommandFactory;
import org.gradle.caching.internal.controller.BuildCacheLoadCommand;
import org.gradle.caching.internal.controller.BuildCacheStoreCommand;
import org.gradle.caching.internal.origin.OriginMetadata;
import org.gradle.caching.internal.origin.OriginMetadataFactory;
import org.gradle.caching.internal.packaging.BuildCacheEntryPacker;
import org.gradle.internal.file.FileMetadata.AccessType;
import org.gradle.internal.file.FileType;
import org.gradle.internal.snapshot.CompleteFileSystemLocationSnapshot;
import org.gradle.internal.snapshot.CompositeFileSystemSnapshot;
import org.gradle.internal.snapshot.FileSystemSnapshot;
import org.gradle.internal.snapshot.MissingFileSnapshot;
import org.gradle.internal.vfs.FileSystemAccess;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Map;

public class DefaultBuildCacheCommandFactory implements BuildCacheCommandFactory {

    private final BuildCacheEntryPacker packer;
    private final OriginMetadataFactory originMetadataFactory;
    private final FileSystemAccess fileSystemAccess;
    private final Interner<String> stringInterner;

    public DefaultBuildCacheCommandFactory(BuildCacheEntryPacker packer, OriginMetadataFactory originMetadataFactory, FileSystemAccess fileSystemAccess, Interner<String> stringInterner) {
        this.packer = packer;
        this.originMetadataFactory = originMetadataFactory;
        this.fileSystemAccess = fileSystemAccess;
        this.stringInterner = stringInterner;
    }

    @Override
    public BuildCacheLoadCommand<LoadMetadata> createLoad(BuildCacheKey cacheKey, CacheableEntity entity) {
        return new LoadCommand(cacheKey, entity);
    }

    @Override
    public BuildCacheStoreCommand createStore(BuildCacheKey cacheKey, CacheableEntity entity, Map<String, ? extends FileSystemSnapshot> snapshots, long executionTime) {
        return new StoreCommand(cacheKey, entity, snapshots, executionTime);
    }

    private class LoadCommand implements BuildCacheLoadCommand<LoadMetadata> {

        private final BuildCacheKey cacheKey;
        private final CacheableEntity entity;

        private LoadCommand(BuildCacheKey cacheKey, CacheableEntity entity) {
            this.cacheKey = cacheKey;
            this.entity = entity;
        }

        @Override
        public BuildCacheKey getKey() {
            return cacheKey;
        }

        @Override
        public BuildCacheLoadCommand.Result<LoadMetadata> load(InputStream input) throws IOException {
            ImmutableList.Builder<String> roots = ImmutableList.builder();
            entity.visitOutputTrees((name, type, root) -> roots.add(root.getAbsolutePath()));
            // TODO: Actually unpack the roots inside of the action
            fileSystemAccess.write(roots.build(), () -> {});
            BuildCacheEntryPacker.UnpackResult unpackResult = packer.unpack(entity, input, originMetadataFactory.createReader(entity));
            // TODO: Update the snapshots from the action
            ImmutableSortedMap<String, FileSystemSnapshot> snapshots = snapshotUnpackedData(unpackResult.getSnapshots());
            return new Result<LoadMetadata>() {
                @Override
                public long getArtifactEntryCount() {
                    return unpackResult.getEntries();
                }

                @Override
                public LoadMetadata getMetadata() {
                    return new LoadMetadata() {
                        @Override
                        public OriginMetadata getOriginMetadata() {
                            return unpackResult.getOriginMetadata();
                        }

                        @Override
                        public ImmutableSortedMap<String, FileSystemSnapshot> getResultingSnapshots() {
                            return snapshots;
                        }
                    };
                }
            };
        }

        private ImmutableSortedMap<String, FileSystemSnapshot> snapshotUnpackedData(Map<String, ? extends CompleteFileSystemLocationSnapshot> treeSnapshots) {
            ImmutableSortedMap.Builder<String, FileSystemSnapshot> builder = ImmutableSortedMap.naturalOrder();
            entity.visitOutputTrees((treeName, type, root) -> {
                CompleteFileSystemLocationSnapshot treeSnapshot = treeSnapshots.get(treeName);
                String internedAbsolutePath = stringInterner.intern(root.getAbsolutePath());
                ImmutableList.Builder<FileSystemSnapshot> roots = ImmutableList.builder();

                if (treeSnapshot == null) {
                    MissingFileSnapshot missingFileSnapshot = new MissingFileSnapshot(internedAbsolutePath, AccessType.DIRECT);
                    fileSystemAccess.record(missingFileSnapshot);
                    builder.put(treeName, FileSystemSnapshot.EMPTY);
                    return;
                }

                switch (type) {
                    case FILE:
                        if (treeSnapshot.getType() != FileType.RegularFile) {
                            throw new IllegalStateException(String.format("Only a regular file should be produced by unpacking tree '%s', but saw a %s", treeName, treeSnapshot.getType()));
                        }
                        roots.add(treeSnapshot);
                        fileSystemAccess.record(treeSnapshot);
                        break;
                    case DIRECTORY:
                        roots.add(treeSnapshot);
                        fileSystemAccess.record(treeSnapshot);
                        break;
                    default:
                        throw new AssertionError();
                }
                builder.put(treeName, CompositeFileSystemSnapshot.of(roots.build()));
            });
            return builder.build();
        }
    }

    private class StoreCommand implements BuildCacheStoreCommand {

        private final BuildCacheKey cacheKey;
        private final CacheableEntity entity;
        private final Map<String, ? extends FileSystemSnapshot> fingerprints;
        private final long executionTime;

        private StoreCommand(BuildCacheKey cacheKey, CacheableEntity entity, Map<String, ? extends FileSystemSnapshot> fingerprints, long executionTime) {
            this.cacheKey = cacheKey;
            this.entity = entity;
            this.fingerprints = fingerprints;
            this.executionTime = executionTime;
        }

        @Override
        public BuildCacheKey getKey() {
            return cacheKey;
        }

        @Override
        public BuildCacheStoreCommand.Result store(OutputStream output) throws IOException {
            final BuildCacheEntryPacker.PackResult packResult = packer.pack(entity, fingerprints, output, originMetadataFactory.createWriter(entity, executionTime));
            return packResult::getEntries;
        }
    }
}

@@ -1433,51 +1433,21 @@
 nal.
-snapshot.CompleteFileSystemLocationSnapshot
+file.TreeType
 ;%0Aim
@@ -1480,27 +1480,26 @@
 napshot.Comp
-osi
+le
 teFileSystem
@@ -1490,32 +1490,40 @@
 mpleteFileSystem
+Location
 Snapshot;%0Aimport
@@ -5309,24 +5309,72 @@
 (treeName);%0A
+                if (treeSnapshot == null) %7B%0A    
             
@@ -5458,144 +5458,8 @@
 ));%0A
-                ImmutableList.Builder%3CFileSystemSnapshot%3E roots = ImmutableList.builder();%0A%0A                if (treeSnapshot == null) %7B%0A
     
@@ -5737,130 +5737,66 @@
     
-    return;%0A                %7D%0A%0A                switch (type) %7B%0A                    case FILE:%0A                        if (
+%7D else %7B%0A                    if (type == TreeType.FILE && 
 tree
@@ -5837,28 +5837,24 @@
 ularFile) %7B%0A
-    
             
@@ -6050,67 +6050,10 @@
     
-    
 %7D%0A
-                        roots.add(treeSnapshot);%0A    
     
@@ -6131,382 +6131,62 @@
     
-    break;%0A                    case DIRECTORY:%0A                        roots.add(treeSnapshot);%0A                        fileSystemAccess.record(treeSnapshot);%0A                        break;%0A                    default:%0A                        throw new AssertionError();%0A                %7D%0A                builder.put(treeName, CompositeFileSystemSnapshot.of(roots.build()));
+builder.put(treeName, treeSnapshot);%0A                %7D
 %0A   

Simplify snapshotting unpacked results