// Copyright 2017 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.rules.cpp;

import static com.google.common.collect.ImmutableList.toImmutableList;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.Streams;
import com.google.devtools.build.lib.actions.ActionExecutionContext;
import com.google.devtools.build.lib.actions.ActionKeyContext;
import com.google.devtools.build.lib.actions.ActionOwner;
import com.google.devtools.build.lib.actions.Artifact;
import com.google.devtools.build.lib.actions.Artifact.ArtifactExpander;
import com.google.devtools.build.lib.analysis.actions.AbstractFileWriteAction;
import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
import com.google.devtools.build.lib.util.Fingerprint;
import com.google.devtools.build.lib.vfs.PathFragment;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;

/**
 * Action for generating an umbrella header. All the headers are #included in the umbrella header.
 */
@Immutable
public final class UmbrellaHeaderAction extends AbstractFileWriteAction {

  private static final String GUID = "62ea2952-bf28-92c3-efb1-e34621646910";

  // NOTE: If you add a field here, you'll likely need to add it to the cache key in computeKey().
  private final Artifact umbrellaHeader;
  private final ImmutableList<Artifact> publicHeaders;
  private final ImmutableList<PathFragment> additionalExportedHeaders;

  public UmbrellaHeaderAction(
      ActionOwner owner,
      Artifact umbrellaHeader,
      Iterable<Artifact> publicHeaders,
      Iterable<PathFragment> additionalExportedHeaders) {
    super(
        owner,
        Streams.stream(publicHeaders).filter(Artifact::isTreeArtifact).collect(toImmutableList()),
        umbrellaHeader,
        /*makeExecutable=*/ false);
    this.umbrellaHeader = umbrellaHeader;
    this.publicHeaders = ImmutableList.copyOf(publicHeaders);
    this.additionalExportedHeaders = ImmutableList.copyOf(additionalExportedHeaders);
  }

  @Override
  public DeterministicWriter newDeterministicWriter(ActionExecutionContext ctx)  {
    final ArtifactExpander artifactExpander = ctx.getArtifactExpander();
    return new DeterministicWriter() {
      @Override
      public void writeOutputFile(OutputStream out) throws IOException {
        StringBuilder content = new StringBuilder();
        HashSet<PathFragment> deduper = new HashSet<>();
        for (Artifact artifact : expandedHeaders(artifactExpander, publicHeaders)) {
          appendHeader(content, artifact.getExecPath(), deduper);
        }
        for (PathFragment additionalExportedHeader : additionalExportedHeaders) {
          appendHeader(content, additionalExportedHeader, deduper);
        }
        out.write(content.toString().getBytes(StandardCharsets.ISO_8859_1));
      }
    };
  }

  private static Iterable<Artifact> expandedHeaders(ArtifactExpander artifactExpander,
      Iterable<Artifact> unexpandedHeaders) {
    List<Artifact> expandedHeaders = new ArrayList<>();
    for (Artifact unexpandedHeader : unexpandedHeaders) {
      if (unexpandedHeader.isTreeArtifact()) {
        artifactExpander.expand(unexpandedHeader, expandedHeaders);
      } else {
        expandedHeaders.add(unexpandedHeader);
      }
    }
    return ImmutableList.copyOf(expandedHeaders);
  }

  private void appendHeader(StringBuilder content, PathFragment path, 
      HashSet<PathFragment> deduper) {
    if (deduper.contains(path)) {
      return;
    }
    deduper.add(path);
    // #include headers. The #import directive is incompatible with J2ObjC segmented headers.
    content.append("#include \"").append(path).append("\"");
    content.append("\n");
  }
  
  @Override
  public String getMnemonic() {
    return "UmbrellaHeader";
  }

  @Override
  protected String computeKey(ActionKeyContext actionKeyContext) {
    Fingerprint f = new Fingerprint();
    f.addString(GUID);
    f.addPath(umbrellaHeader.getExecPath());
    f.addInt(publicHeaders.size());
    for (Artifact artifact : publicHeaders) {
      f.addPath(artifact.getExecPath());
    }
    f.addInt(additionalExportedHeaders.size());
    for (PathFragment path : additionalExportedHeaders) {
      f.addPath(path);
    }
    return f.hexDigestAndReset();
  }
}
