/*
 * Copyright 2015-present Facebook, Inc.
 *
 * 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.facebook.buck.util.env;

import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Streams;
import java.io.File;
import java.io.IOException;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import javax.annotation.Nullable;

/** Current process classpath. Set by buckd or by launcher script. */
public class BuckClasspath {

  public static final String ENV_VAR_NAME = "BUCK_CLASSPATH";
  public static final String BOOTSTRAP_ENV_VAR_NAME = "CLASSPATH";
  public static final String TEST_ENV_VAR_NAME = "BUCK_TEST_CLASSPATH_FILE";

  public static String getBuckClasspathFromEnvVarOrThrow() {
    String envVarValue = getBuckClasspathFromEnvVarOrNull();
    if (envVarValue == null) {
      throw new IllegalStateException(ENV_VAR_NAME + " env var is not set");
    }
    if (envVarValue.isEmpty()) {
      throw new IllegalStateException(ENV_VAR_NAME + " env var is set to empty string");
    }
    return envVarValue;
  }

  private static ImmutableList<Path> getBuckTestClasspath() throws IOException {
    String envVarValue = System.getenv(TEST_ENV_VAR_NAME);
    Preconditions.checkNotNull(envVarValue);
    return readClasspaths(Paths.get(envVarValue));
  }

  @Nullable
  public static String getBuckClasspathFromEnvVarOrNull() {
    return System.getenv(ENV_VAR_NAME);
  }

  @Nullable
  public static String getBuckBootstrapClasspathFromEnvVarOrNull() {
    return System.getenv(BOOTSTRAP_ENV_VAR_NAME);
  }

  /** Returns Buck's "bootstrap" classpath. See ClassLoaderBootstrapper. */
  public static ImmutableList<Path> getBootstrapClasspath() throws IOException {
    String classpathFromEnv = getBuckBootstrapClasspathFromEnvVarOrNull();
    if (classpathFromEnv != null) {
      return Arrays.stream(classpathFromEnv.split(File.pathSeparator))
          .map(Paths::get)
          .collect(ImmutableList.toImmutableList());
    }
    return getBuckClasspathForIntellij();
  }

  /** Returns Buck's classpath. */
  public static ImmutableList<Path> getClasspath() throws IOException {
    String classpathFromEnv = getBuckClasspathFromEnvVarOrNull();
    if (classpathFromEnv != null) {
      return Streams.concat(
              Arrays.stream(classpathFromEnv.split(File.pathSeparator)).map(Paths::get),
              getBuckTestClasspath().stream())
          .collect(ImmutableList.toImmutableList());
    }
    return getBuckClasspathForIntellij();
  }

  /**
   * Return Buck's classpath when running under Intellij. Use getClasspath() or
   * getBootstrapClasspath() instead.
   */
  public static ImmutableList<Path> getBuckClasspathForIntellij() throws IOException {
    ImmutableList.Builder<Path> classPathEntries = ImmutableList.builder();
    Path productionPath = getClassLocation(BuckClasspath.class);
    classPathEntries.add(productionPath.toAbsolutePath());
    Path testPath = productionPath.resolve("../../test/buck").normalize();
    classPathEntries.add(testPath.toAbsolutePath());
    classPathEntries.addAll(
        filterAntClasspaths(readClasspaths(Paths.get("programs", "classpaths"))));
    classPathEntries.addAll(
        filterAntClasspaths(readClasspaths(Paths.get("programs", "test_classpaths"))));
    return classPathEntries.build();
  }

  private static Path getClassLocation(Class<?> clazz) {
    // Move along, no ridiculous hacks here...
    // Should be something like file:/Users/you/buck/build-ij/classes/production/buck/
    URL myLocation = clazz.getProtectionDomain().getCodeSource().getLocation();
    Preconditions.checkState(
        myLocation.getProtocol().equals("file"),
        "Don't know how to launch the worker process from %s.",
        myLocation);

    // CLASSPATH entries need to be absolute paths, mostly because the JVM gets exec'd with
    // IntelliJ's CWD when running tests from there.
    try {
      return new File(myLocation.toURI()).toPath();
    } catch (URISyntaxException e) {
      throw new RuntimeException(e);
    }
  }

  private static ImmutableList<Path> filterAntClasspaths(ImmutableList<Path> classpaths) {
    return classpaths
        .stream()
        .filter(
            path ->
                !path.startsWith("src") && !path.startsWith("src-gen") && !path.startsWith("build"))
        .collect(ImmutableList.toImmutableList());
  }

  private static ImmutableList<Path> readClasspaths(Path classpathsFile) throws IOException {
    return Files.readAllLines(classpathsFile)
        .stream()
        .filter(line -> !line.startsWith("#"))
        .map(Paths::get)
        .map(Path::toAbsolutePath)
        .collect(ImmutableList.toImmutableList());
  }
}

@@ -986,16 +986,75 @@
 Arrays;%0A
+import java.util.Optional;%0Aimport java.util.stream.Stream;%0A
 import j
@@ -1797,34 +1797,31 @@
  static 
-ImmutableList%3CPath
+Optional%3CString
 %3E getBuc
@@ -1836,35 +1836,16 @@
 sspath()
- throws IOException
  %7B%0A    S
@@ -1906,84 +1906,34 @@
     
-P
 re
-conditions.checkNotNull(envVarValue);%0A    return readClasspaths(Paths.get
+turn Optional.ofNullable
 (env
@@ -1941,17 +1941,16 @@
 arValue)
-)
 ;%0A  %7D%0A%0A 
@@ -2892,35 +2892,106 @@
     
-return Streams.concat(%0A    
+Optional%3CString%3E buckTestClasspath = getBuckTestClasspath();%0A      Stream%3CPath%3E classpathStream =%0A
     
@@ -3073,28 +3073,166 @@
 get)
-,
+;
 %0A      
-        getB
+if (buckTestClasspath.isPresent()) %7B%0A        classpathStream =%0A            Streams.concat(%0A                classpathStream, readClasspaths(Paths.get(b
 uckT
@@ -3243,17 +3243,23 @@
 lasspath
-(
+.get())
 ).stream
@@ -3261,27 +3261,54 @@
 tream())
+;
 %0A      
+%7D%0A
     
+  return classpathStream
 .collect

Make BuckClasspath handle missing test classpath env var