// Licensed to the Software Freedom Conservancy (SFC) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The SFC licenses this file
// to you 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.openqa.selenium;

import static com.google.common.base.StandardSystemProperty.LINE_SEPARATOR;
import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
import static org.openqa.selenium.Platform.WINDOWS;
import static org.openqa.selenium.testing.DevMode.isInDevMode;

import com.google.common.base.Preconditions;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableList;
import com.google.common.hash.HashCode;
import com.google.common.hash.Hashing;

import org.openqa.selenium.os.CommandLine;
import org.openqa.selenium.testing.InProject;

import java.io.File;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.logging.Logger;

public class BuckBuild {
  private static Logger log = Logger.getLogger(BuckBuild.class.getName());

  private String target;

  public BuckBuild of(String target) {
    this.target = target;
    return this;
  }

  public Path go() throws IOException {
    Path projectRoot = InProject.locate("Rakefile").toPath().getParent();

    if (!isInDevMode()) {
      // we should only need to do this when we're in dev mode
      // when running in a test suite, our dependencies should already
      // be listed.
      log.info("Not in dev mode. Ignoring attempt to build: " + target);
      return findOutput(projectRoot);
    }

    if (target == null || "".equals(target)) {
      throw new IllegalStateException("No targets specified");
    }
    System.out.println("\nBuilding " + target + " ...");

    ImmutableList.Builder<String> builder = ImmutableList.builder();
    findBuck(projectRoot, builder);
    builder.add("build");
    builder.add(target);

    ImmutableList<String> command = builder.build();
    CommandLine commandLine = new CommandLine(command.toArray(new String[command.size()]));
    commandLine.copyOutputTo(System.err);
    commandLine.execute();

    if (!commandLine.isSuccessful()) {
      throw new WebDriverException("Build failed! " + target);
    }

    return findOutput(projectRoot);
  }

  private Path findOutput(Path projectRoot) throws IOException {
    ImmutableList.Builder<String> builder = ImmutableList.builder();
    findBuck(projectRoot, builder);
    builder.add("targets", "--show-output", target);

    ImmutableList<String> command = builder.build();
    CommandLine commandLine = new CommandLine(command.toArray(new String[command.size()]));
    commandLine.copyOutputTo(System.err);
    commandLine.execute();

    if (!commandLine.isSuccessful()) {
      throw new WebDriverException("Unable to find output! " + target);
    }

    String[] allLines = commandLine.getStdOut().split(LINE_SEPARATOR.value());
    String lastLine = allLines[allLines.length -1 ];

    List<String> outputs = Splitter.on(' ').limit(2).splitToList(lastLine);
    if (outputs.size() != 2) {
      throw new WebDriverException(
        String.format("Unable to find output! %s, %s", target, lastLine));
    }

    Path output = projectRoot.resolve(outputs.get(1));

    if (!Files.exists(output)) {
      throw new WebDriverException(
        String.format("Found output, but it does not exist: %s, %s", target, output));
    }

    return output;
  }

  private void findBuck(Path projectRoot, ImmutableList.Builder<String> builder) throws IOException {
    Path noBuckCheck = projectRoot.resolve(".nobuckcheck");

    // If there's a .nobuckcheck in the root of the file, and we can execute "buck", then assume
    // that the developer knows what they're doing. Ha! Ahaha! Ahahahaha!
    if (Files.exists(noBuckCheck)) {
      String buckCommand = CommandLine.find("buck");
      if (buckCommand != null) {
        builder.add(buckCommand);
        return;
      }
    }

    downloadBuckPexIfNecessary(builder);
  }

  private void downloadBuckPexIfNecessary(ImmutableList.Builder<String> builder)
    throws IOException {
    Path projectRoot = InProject.locate("Rakefile").getParentFile().toPath();
    String buckVersion = new String(Files.readAllBytes(projectRoot.resolve(".buckversion"))).trim();

    File rootOfRepo = InProject.locate("Rakefile").getParentFile();
    Path pex = rootOfRepo.toPath().resolve("buck-out/crazy-fun/" + buckVersion + "/buck.pex");

    String expectedHash = new String(Files.readAllBytes(projectRoot.resolve(".buckhash"))).trim();
    HashCode md5 = Files.exists(pex) ?
                   Hashing.md5().hashBytes(Files.readAllBytes(pex)) :
                   HashCode.fromString("aa");  // So we have a non-null value

    if (!Files.exists(pex) || !expectedHash.equals(md5.toString())) {
      log.warning("Downloading PEX");

      if (!Files.exists(pex.getParent())) {
        Files.createDirectories(pex.getParent());
      }

      URL url = new URL(String.format(
          "https://github.com/SeleniumHQ/buck/releases/download/buck-release-%s/buck.pex",
          buckVersion));
      HttpURLConnection connection = (HttpURLConnection) url.openConnection();
      connection.setInstanceFollowRedirects(true);
      Files.copy(connection.getInputStream(), pex, REPLACE_EXISTING);
      // Do our best to make this executable
      pex.toFile().setExecutable(true);
    }

    md5 = Hashing.md5().hashBytes(Files.readAllBytes(pex));
    if (!expectedHash.equals(md5.toString())) {
      throw new WebDriverException("Unable to confirm that download is valid");
    }

    if (Platform.getCurrent().is(WINDOWS)) {
      String python = CommandLine.find("python2");
      if (python == null) {
        python = CommandLine.find("python");
      }
      Preconditions.checkNotNull(python, "Unable to find python executable");
      builder.add(python);
    }

    builder.add(pex.toAbsolutePath().toString());
  }

  public static void main(String[] args) throws IOException {
    new BuckBuild().of("se3-server").go();
  }
}

@@ -3589,37 +3589,180 @@
 e = 
-allLines%5BallLines.length -1 %5D
+null;%0A    for (String line : allLines) %7B%0A      if (line.startsWith(target)) %7B%0A        lastLine = line;%0A        break;%0A      %7D%0A    %7D%0A    Preconditions.checkNotNull(lastLine)
 ;%0A%0A 
@@ -5086,76 +5086,8 @@
 );%0A%0A
-    File rootOfRepo = InProject.locate(%22Rakefile%22).getParentFile();%0A
     
@@ -5101,27 +5101,19 @@
 x = 
+p
 ro
-otOfRepo.toPath()
+jectRoot
 .res

Work around issue on Windows with BuckBuild in IDE tests