// 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.analysis.starlark;

import static com.google.common.truth.Truth.assertThat;
import static java.nio.charset.StandardCharsets.UTF_8;

import com.google.common.collect.ImmutableList;
import com.google.common.io.CharStreams;
import com.google.devtools.build.lib.actions.ParameterFile;
import com.google.devtools.build.lib.analysis.util.BuildViewTestCase;
import com.google.devtools.build.lib.cmdline.RepositoryMapping;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStreamReader;
import net.starlark.java.eval.Mutability;
import net.starlark.java.eval.Starlark;
import net.starlark.java.eval.StarlarkList;
import net.starlark.java.eval.StarlarkThread;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;

/**
 * Checks the result of each variant {@code Args.add*} call, in flag_per_line format. Note, writes
 * the
 */
@RunWith(JUnit4.class)
public class FlagPerLineTest extends BuildViewTestCase {

  // Initially empty, with "flag_per_line" format.
  private Args args;
  private final Mutability mutability = Mutability.create();
  private StarlarkThread thread;

  @Before
  public void initArgs() throws Exception {
    args = Args.newArgs(mutability, getStarlarkSemantics());
    args.setParamFileFormat("flag_per_line");
    thread = new StarlarkThread(mutability, getStarlarkSemantics());
  }

  @Test
  public void add_noname() throws Exception {
    args.addArgument("--foo", Starlark.UNBOUND, /* format= */ Starlark.NONE, thread);
    expectLines("--foo");
  }

  @Test
  public void add_name() throws Exception {
    args.addArgument("--foo", "bar", /* format= */ Starlark.NONE, thread);
    expectLines("--foo=bar");
  }

  @Test
  public void add_all_noname() throws Exception {
    args.addAll(
        /* argNameOrValue= */ "", // ignored
        /* values= */ StarlarkList.of(null, "--foo", "bar", "baz"),
        /* mapEach= */ Starlark.NONE,
        /* formatEach= */ Starlark.NONE,
        /* beforeEach= */ Starlark.NONE,
        /* omitIfEmpty= */ true, // the default
        /* uniquify= */ false,
        /* expandDirectories= */ false,
        /* terminateWith= */ Starlark.NONE,
        /* allowClosure= */ false,
        thread);
    // Absl would reject this line, but it's what we generate.
    expectLines("--foo bar baz");
  }

  @Test
  public void add_all_name() throws Exception {
    args.addAll(
        /* argNameOrValue= */ "--foo",
        /* values= */ StarlarkList.of(null, "bar", "baz"),
        /* mapEach= */ Starlark.NONE,
        /* formatEach= */ Starlark.NONE,
        /* beforeEach= */ Starlark.NONE,
        /* omitIfEmpty= */ true, // the default
        /* uniquify= */ false,
        /* expandDirectories= */ false,
        /* terminateWith= */ Starlark.NONE,
        /* allowClosure= */ false,
        thread);
    // Absl interprets this as a single value "bar baz" for the flag "--foo",
    // which is probably not what was intended.
    expectLines("--foo=bar baz");
  }

  @Test
  public void add_joined_noname() throws Exception {
    args.addJoined(
        /* argNameOrValue= */ "", // ignored
        /* values= */ StarlarkList.of(null, "--foo", "bar", "baz"),
        /* joinWith= */ ",",
        /* mapEach= */ Starlark.NONE,
        /* formatEach= */ Starlark.NONE,
        /* formatJoined= */ Starlark.NONE,
        /* omitIfEmpty= */ true, // the default
        /* uniquify= */ false,
        /* expandDirectories= */ false,
        /* allowClosure= */ false,
        thread);
    // Absl would reject this line, but it's what we generate.
    expectLines("--foo,bar,baz");
  }

  @Test
  public void add_joined_name() throws Exception {
    args.addJoined(
        /* argNameOrValue= */ "--foo",
        /* values= */ StarlarkList.of(null, "bar", "baz", "woof"),
        /* joinWith= */ ",",
        /* mapEach= */ Starlark.NONE,
        /* formatEach= */ Starlark.NONE,
        /* formatJoined= */ Starlark.NONE,
        /* omitIfEmpty= */ true,
        /* uniquify= */ false,
        /* expandDirectories= */ false,
        /* allowClosure= */ false,
        thread);
    expectLines("--foo=bar,baz,woof");
  }

  /** Tests that an add_all (empty and omitted) following two adds works. */
  @Test
  public void args_combinedOmittedAddAllAndAdd() throws Exception {
    args.addAll(
        /* argNameOrValue= */ "", // ignored
        /* values= */ StarlarkList.of(null),
        /* mapEach= */ Starlark.NONE,
        /* formatEach= */ Starlark.NONE,
        /* beforeEach= */ Starlark.NONE,
        /* omitIfEmpty= */ true, // the default
        /* uniquify= */ false,
        /* expandDirectories= */ false,
        /* terminateWith= */ Starlark.NONE,
        /* allowClosure= */ false,
        thread);
    args.addArgument("--foo1", "bar", /* format= */ Starlark.NONE, thread);
    args.addArgument("--foo2", "bar", /* format= */ Starlark.NONE, thread);

    expectLines("--foo1=bar", "--foo2=bar");
  }

  private void expectLines(String... lines) throws Exception {
    assertThat(toParamFile(args)).containsExactly((Object[]) lines).inOrder();
  }

  /** Writes out the Args using ParameterFile, returns the output broken down as lines. */
  private static ImmutableList<String> toParamFile(Args args) throws Exception {
    byte[] bytes;
    try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
      ParameterFile.writeParameterFile(
          outputStream,
          args.build(() -> RepositoryMapping.ALWAYS_FALLBACK).arguments(),
          args.getParameterFileType(),
          UTF_8);
      bytes = outputStream.toByteArray();
    }
    try (ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes);
        InputStreamReader reader = new InputStreamReader(inputStream, UTF_8)) {
      return ImmutableList.copyOf(CharStreams.readLines(reader));
    }
  }
}
