/*
 * Copyright 2017-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.features.ocaml;

import com.facebook.buck.core.model.Flavor;
import com.facebook.buck.core.model.FlavorDomain;
import com.facebook.buck.core.toolchain.ToolchainCreationContext;
import com.facebook.buck.core.toolchain.ToolchainFactory;
import com.facebook.buck.core.toolchain.ToolchainProvider;
import com.facebook.buck.core.toolchain.tool.impl.HashedFileTool;
import com.facebook.buck.core.toolchain.toolprovider.ToolProvider;
import com.facebook.buck.core.toolchain.toolprovider.impl.ConstantToolProvider;
import com.facebook.buck.cxx.toolchain.CxxPlatform;
import com.facebook.buck.cxx.toolchain.CxxPlatformsProvider;
import com.facebook.buck.cxx.toolchain.DefaultCxxPlatforms;
import com.facebook.buck.rules.tool.config.ToolConfig;
import com.facebook.buck.util.RichStream;
import com.google.common.collect.ImmutableList;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Optional;
import java.util.function.BiFunction;

public class OcamlToolchainFactory implements ToolchainFactory<OcamlToolchain> {

  private static final String SECTION = "ocaml";

  private static final Path DEFAULT_OCAML_COMPILER = Paths.get("ocamlopt.opt");
  private static final Path DEFAULT_OCAML_BYTECODE_COMPILER = Paths.get("ocamlc.opt");
  private static final Path DEFAULT_OCAML_DEP_TOOL = Paths.get("ocamldep.opt");
  private static final Path DEFAULT_OCAML_YACC_COMPILER = Paths.get("ocamlyacc");
  private static final Path DEFAULT_OCAML_DEBUG = Paths.get("ocamldebug");
  private static final Path DEFAULT_OCAML_LEX_COMPILER = Paths.get("ocamllex.opt");

  private OcamlPlatform getPlatform(
      ToolchainCreationContext context, CxxPlatform cxxPlatform, String section) {
    BiFunction<String, Path, ToolProvider> getTool =
        (field, defaultValue) ->
            context
                .getBuckConfig()
                .getView(ToolConfig.class)
                .getToolProvider(section, field)
                .orElseGet(
                    () ->
                        new ConstantToolProvider(
                            new HashedFileTool(
                                () ->
                                    context
                                        .getBuckConfig()
                                        .getPathSourcePath(
                                            context
                                                .getExecutableFinder()
                                                .getExecutable(
                                                    context
                                                        .getBuckConfig()
                                                        .getPath(section, field)
                                                        .orElse(defaultValue),
                                                    context.getEnvironment())))));
    return OcamlPlatform.builder()
        .setOcamlCompiler(getTool.apply("ocaml.compiler", DEFAULT_OCAML_COMPILER))
        .setOcamlDepTool(getTool.apply("dep.tool", DEFAULT_OCAML_DEP_TOOL))
        .setYaccCompiler(getTool.apply("yacc.compiler", DEFAULT_OCAML_YACC_COMPILER))
        .setLexCompiler(getTool.apply("lex.compiler", DEFAULT_OCAML_LEX_COMPILER))
        .setOcamlInteropIncludesDir(context.getBuckConfig().getValue(section, "interop.includes"))
        .setWarningsFlags(context.getBuckConfig().getValue(section, "warnings_flags"))
        .setOcamlBytecodeCompiler(
            getTool.apply("ocaml.bytecode.compiler", DEFAULT_OCAML_BYTECODE_COMPILER))
        .setOcamlDebug(getTool.apply("debug", DEFAULT_OCAML_DEBUG))
        .setCCompiler(cxxPlatform.getCc())
        .setCxxCompiler(cxxPlatform.getCxx())
        .setCPreprocessor(cxxPlatform.getCpp())
        .setCFlags(
            ImmutableList.<String>builder()
                .addAll(cxxPlatform.getCppflags())
                .addAll(cxxPlatform.getCflags())
                .addAll(cxxPlatform.getAsflags())
                .build())
        .setLdFlags(cxxPlatform.getLdflags())
        .setCxxPlatform(cxxPlatform)
        .build();
  }

  private String getSection(Flavor flavor) {
    String section = SECTION;

    // We special case the "default" C/C++ platform to just use the "ocaml" section.
    if (!flavor.equals(DefaultCxxPlatforms.FLAVOR)) {
      section += "#" + flavor;
    }

    return section;
  }

  private ImmutableList<OcamlPlatform> getPlatforms(
      ToolchainCreationContext context, Iterable<CxxPlatform> cxxPlatforms) {
    return RichStream.from(cxxPlatforms)
        .map(cxxPlatform -> getPlatform(context, cxxPlatform, getSection(cxxPlatform.getFlavor())))
        .toImmutableList();
  }

  @Override
  public Optional<OcamlToolchain> createToolchain(
      ToolchainProvider toolchainProvider, ToolchainCreationContext context) {

    CxxPlatformsProvider cxxPlatformsProviderFactory =
        toolchainProvider.getByName(CxxPlatformsProvider.DEFAULT_NAME, CxxPlatformsProvider.class);
    FlavorDomain<CxxPlatform> cxxPlatforms = cxxPlatformsProviderFactory.getCxxPlatforms();
    CxxPlatform defaultCxxPlatform = cxxPlatformsProviderFactory.getDefaultCxxPlatform();

    FlavorDomain<OcamlPlatform> ocamlPlatforms =
        FlavorDomain.from("OCaml platform", getPlatforms(context, cxxPlatforms.getValues()));
    OcamlPlatform defaultOcamlPlatform = ocamlPlatforms.getValue(defaultCxxPlatform.getFlavor());

    return Optional.of(OcamlToolchain.of(defaultOcamlPlatform, ocamlPlatforms));
  }
}

@@ -1365,50 +1365,8 @@
 ig;%0A
-import com.facebook.buck.util.RichStream;%0A
 impo
@@ -4920,313 +4920,8 @@
  %7D%0A%0A
-  private ImmutableList%3COcamlPlatform%3E getPlatforms(%0A      ToolchainCreationContext context, Iterable%3CCxxPlatform%3E cxxPlatforms) %7B%0A    return RichStream.from(cxxPlatforms)%0A        .map(cxxPlatform -%3E getPlatform(context, cxxPlatform, getSection(cxxPlatform.getFlavor())))%0A        .toImmutableList();%0A  %7D%0A%0A
   @O
@@ -5458,43 +5458,86 @@
     
-FlavorDomain.from(%22OCaml platform%22,
+cxxPlatforms.convert(%0A            %22OCaml platform%22,%0A            cxxPlatform -%3E
  get
@@ -5544,17 +5544,16 @@
 Platform
-s
 (context
@@ -5569,20 +5569,44 @@
 form
-s.getValues(
+, getSection(cxxPlatform.getFlavor()
 )));

Use FlavorDomain.convert() in OcamlToolchainFactory