// Copyright 2020 The Bazel Authors. All rights reserved.
//
// 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 com.google.devtools.build.lib.skyframe;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.devtools.build.lib.actions.ActionLookupKey;
import com.google.devtools.build.lib.actions.Artifact;
import com.google.devtools.build.lib.actions.Artifact.ArchivedTreeArtifact;
import com.google.devtools.build.lib.actions.Artifact.SpecialArtifact;
import com.google.devtools.build.lib.actions.Artifact.TreeFileArtifact;
import com.google.devtools.build.lib.actions.ArtifactRoot;
import com.google.devtools.build.lib.actions.FileArtifactValue;
import com.google.devtools.build.lib.actions.util.ActionsTestUtil;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.testutil.ManualClock;
import com.google.devtools.build.lib.vfs.DigestHashFunction;
import com.google.devtools.build.lib.vfs.FileStatus;
import com.google.devtools.build.lib.vfs.FileStatusWithDigestAdapter;
import com.google.devtools.build.lib.vfs.FileSystemUtils;
import com.google.devtools.build.lib.vfs.Path;
import com.google.devtools.build.lib.vfs.PathFragment;
import com.google.devtools.build.lib.vfs.Symlinks;
import com.google.devtools.build.lib.vfs.inmemoryfs.InMemoryFileSystem;
import com.google.devtools.build.skyframe.SkyFunctionName;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.annotation.Nullable;

/**
 * Helper class to allow sharing test helpers between {@link FilesystemValueCheckerTest} and {@link
 * FilesystemValueCheckerParameterizedTest}.
 */
public class FilesystemValueCheckerTestBase {

  static final int FSVC_THREADS_FOR_TEST = 200;
  static final ActionLookupKey ACTION_LOOKUP_KEY =
      new ActionLookupKey() {
        @Override
        public SkyFunctionName functionName() {
          return SkyFunctionName.FOR_TESTING;
        }

        @Nullable
        @Override
        public Label getLabel() {
          return null;
        }
      };

  final MockFileSystem fs = new MockFileSystem();

  SpecialArtifact createTreeArtifact(String relPath) throws IOException {
    String outSegment = "bin";
    Path outputDir = fs.getPath("/" + outSegment);
    Path outputPath = outputDir.getRelative(relPath);
    outputDir.createDirectory();
    ArtifactRoot derivedRoot = ArtifactRoot.asDerivedRoot(fs.getPath("/"), outSegment);
    return ActionsTestUtil.createTreeArtifactWithGeneratingAction(
        derivedRoot,
        derivedRoot.getExecPath().getRelative(derivedRoot.getRoot().relativize(outputPath)));
  }

  static ActionExecutionValue actionValueWithTreeArtifacts(List<TreeFileArtifact> contents)
      throws IOException {
    return actionValueWithTreeArtifacts(contents, ImmutableList.of());
  }

  static ActionExecutionValue actionValueWithTreeArtifacts(
      Iterable<TreeFileArtifact> contents, Iterable<ArchivedTreeArtifact> archivedTreeArtifacts)
      throws IOException {
    TreeArtifactValue.MultiBuilder treeArtifacts = TreeArtifactValue.newMultiBuilder();

    for (TreeFileArtifact output : contents) {
      treeArtifacts.putChild(output, createMetadataFromFileSystem(output));
    }

    for (ArchivedTreeArtifact archivedTreeArtifact : archivedTreeArtifacts) {
      treeArtifacts.setArchivedRepresentation(
          archivedTreeArtifact, createMetadataFromFileSystem(archivedTreeArtifact));
    }

    Map<Artifact, TreeArtifactValue> treeArtifactData = new HashMap<>();
    treeArtifacts.injectTo(treeArtifactData::put);

    return ActionExecutionValue.create(
        /*artifactData=*/ ImmutableMap.of(),
        treeArtifactData,
        /*outputSymlinks=*/ null,
        /*discoveredModules=*/ null,
        /*actionDependsOnBuildId=*/ false);
  }

  private static FileArtifactValue createMetadataFromFileSystem(Artifact artifact)
      throws IOException {
    Path path = artifact.getPath();
    FileArtifactValue noDigest =
        ActionMetadataHandler.fileArtifactValueFromArtifact(
            artifact, FileStatusWithDigestAdapter.adapt(path.statIfFound(Symlinks.NOFOLLOW)), null);
    return FileArtifactValue.createFromInjectedDigest(
        noDigest, path.getDigest(), !artifact.isConstantMetadata());
  }

  void writeFile(Path path, String... lines) throws IOException {
    // Make sure we advance the clock to detect modifications which do not change the size, which
    // rely on ctime.
    fs.advanceClockMillis(1);
    FileSystemUtils.writeIsoLatin1(path, lines);
  }

  static final class MockFileSystem extends InMemoryFileSystem {
    boolean statThrowsRuntimeException;
    boolean readlinkThrowsIoException;

    MockFileSystem() {
      this(new ManualClock());
    }

    private MockFileSystem(ManualClock clock) {
      super(clock, DigestHashFunction.SHA256);
    }

    @Override
    public FileStatus statIfFound(Path path, boolean followSymlinks) throws IOException {
      if (statThrowsRuntimeException) {
        throw new RuntimeException("bork");
      }
      return super.statIfFound(path, followSymlinks);
    }

    @Override
    protected PathFragment readSymbolicLink(Path path) throws IOException {
      if (readlinkThrowsIoException) {
        throw new IOException("readlink failed");
      }
      return super.readSymbolicLink(path);
    }

    void advanceClockMillis(int millis) {
      ((ManualClock) clock).advanceMillis(millis);
    }
  }
}
