/*
 * Copyright 2018-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.parser.implicit;

import com.facebook.buck.core.exceptions.HumanReadableException;
import com.facebook.buck.core.util.immutables.BuckStyleImmutable;
import com.facebook.buck.io.file.MorePaths;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.devtools.build.lib.syntax.SkylarkImport;
import com.google.devtools.build.lib.syntax.SkylarkImports;
import com.google.devtools.build.lib.syntax.SkylarkImports.SkylarkImportSyntaxException;
import java.nio.file.InvalidPathException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import org.immutables.value.Value;

/** Represents a load path and symbols that should be implicitly included in a build file */
@Value.Immutable(builder = false, copy = false)
@BuckStyleImmutable
public abstract class AbstractImplicitInclude {
  @JsonIgnore
  @Value.Parameter
  abstract Path getPath();

  @JsonProperty("load_symbols")
  @Value.Parameter
  abstract ImmutableMap<String, String> getSymbols();

  @JsonProperty("load_path")
  @Value.Derived
  public String getImportString() {
    return getLoadPath().getImportString();
  }

  /**
   * Returns the load path for the given path. SkylarkImport is used to eagerly compute fewer
   * objects up front and centralize error handling
   */
  @JsonIgnore
  @Value.Derived
  public SkylarkImport getLoadPath() {
    Path path = getPath();
    final Path packagePath = MorePaths.getParentOrEmpty(path);
    String loadPath = MorePaths.pathWithUnixSeparators(packagePath);
    String label = String.format("//%s:%s", loadPath, path.getFileName());
    try {
      return SkylarkImports.create(label);
    } catch (SkylarkImportSyntaxException e) {
      throw new IllegalStateException(
          String.format("Invalid implicit label provided: %s", label), e);
    }
  }

  /**
   * Constructs a {@link AbstractImplicitInclude} from a configuration string in the form of
   *
   * <p>path/to/bzl_file.bzl::symbol_to_import::second_symbol_to_import
   *
   * @param configurationString The string used in configuration
   * @return A parsed {@link AbstractImplicitInclude} object
   * @throws {@link HumanReadableException} if the configuration string is invalid
   */
  public static ImplicitInclude fromConfigurationString(String configurationString) {
    // Double colons are used so that if someone uses an absolute windows path, their error
    // messages will not be confusing. e.g. C:\foo.bzl:bar would lead to a file named
    // 'C', and symbols '\foo.bzl' and 'bar'. This just makes things explicit.
    ImmutableList<String> parts =
        Arrays.stream(configurationString.split("::"))
            .map(String::trim)
            .collect(ImmutableList.toImmutableList());
    if (parts.size() < 2) {
      throw new HumanReadableException(
          String.format(
              "Configuration setting '%s' did not list any symbols to load. Setting should be of "
                  + "the format <path>::<symbol1>::<symbol2>...",
              configurationString));
    }
    int i = 0;
    ImmutableMap.Builder<String, String> symbolBuilder = ImmutableMap.builder();
    for (String part : parts) {
      if (part.isEmpty()) {
        throw new HumanReadableException(
            String.format(
                "Provided configuration %s specifies an empty path/symbols", configurationString));
      }
      // Path is the first component, symbols are each subsequent one
      if (i > 0) {
        parseSymbolsFromConfiguration(part, symbolBuilder, configurationString);
      }
      i++;
    }

    Path loadPath;
    try {
      loadPath = Paths.get(parts.get(0));
      if (loadPath.isAbsolute()) {
        throw new HumanReadableException(String.format("Path %s may not be absolute", loadPath));
      }
    } catch (InvalidPathException e) {
      throw new HumanReadableException(
          String.format("Provided path %s is not a valid path", parts.get(0)), e);
    }

    return ImplicitInclude.of(loadPath, symbolBuilder.build());
  }

  static void parseSymbolsFromConfiguration(
      String part, ImmutableMap.Builder<String, String> symbolBuilder, String configurationString) {
    String[] symbolParts = part.split("=", 2);
    String alias;
    String symbol;
    if (symbolParts.length == 1) {
      alias = symbolParts[0];
      symbol = symbolParts[0];
    } else {
      alias = symbolParts[0];
      symbol = symbolParts[1];
    }
    if (symbol.isEmpty()) {
      throw new HumanReadableException(
          String.format(
              "Provided configuration %s specifies an empty symbol", configurationString));
    }
    if (alias.isEmpty()) {
      throw new HumanReadableException(
          String.format(
              "Provided configuration %s specifies an empty symbol alias", configurationString));
    }
    symbolBuilder.put(alias, symbol);
  }
}

@@ -3566,37 +3566,8 @@
 on(%0A
-          String.format(%0A    
     
@@ -3671,20 +3671,16 @@
         
-    
 + %22the f
@@ -3729,20 +3729,16 @@
         
-    
 configur
@@ -3741,33 +3741,32 @@
 igurationString)
-)
 ;%0A    %7D%0A    int 
@@ -3959,39 +3959,8 @@
 on(%0A
-            String.format(%0A    
     
@@ -4044,25 +4044,24 @@
 ationString)
-)
 ;%0A      %7D%0A  
@@ -4401,30 +4401,16 @@
 ception(
-String.format(
 %22Path %25s
@@ -4441,17 +4441,16 @@
 oadPath)
-)
 ;%0A      
@@ -4533,33 +4533,11 @@
 ion(
-%0A          String.format(
+e, 
 %22Pro
@@ -4585,20 +4585,16 @@
 .get(0))
-, e)
 ;%0A    %7D%0A
@@ -5141,37 +5141,8 @@
 on(%0A
-          String.format(%0A    
     
@@ -5214,33 +5214,32 @@
 igurationString)
-)
 ;%0A    %7D%0A    if (
@@ -5301,37 +5301,8 @@
 on(%0A
-          String.format(%0A    
     
@@ -5388,17 +5388,16 @@
 nString)
-)
 ;%0A    %7D%0A

Remove string.format from HumanReadableExceptions