// Copyright 2014 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.objc;

import com.google.common.base.Optional;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.devtools.build.lib.actions.Artifact;
import com.google.devtools.build.lib.util.Preconditions;

/**
 * Artifacts related to compilation. Any rule containing compilable sources will create an instance
 * of this class.
 */
final class CompilationArtifacts {
  static class Builder {
    // TODO(bazel-team): Should these be sets instead of just iterables?
    private Iterable<Artifact> srcs = ImmutableList.of();
    private Iterable<Artifact> nonArcSrcs = ImmutableList.of();
    private Iterable<Artifact> additionalHdrs = ImmutableList.of();
    private Iterable<Artifact> privateHdrs = ImmutableList.of();
    private Iterable<Artifact> precompiledSrcs = ImmutableList.of();
    private IntermediateArtifacts intermediateArtifacts;

    Builder addSrcs(Iterable<Artifact> srcs) {
      this.srcs = Iterables.concat(this.srcs, srcs);
      return this;
    }

    Builder addNonArcSrcs(Iterable<Artifact> nonArcSrcs) {
      this.nonArcSrcs = Iterables.concat(this.nonArcSrcs, nonArcSrcs);
      return this;
    }

    /**
     * Adds header artifacts that should be directly accessible to dependers, but aren't specified
     * in the hdrs attribute. {@code additionalHdrs} should not be a {@link NestedSet}, as it will
     * be flattened when added.
     */
    Builder addAdditionalHdrs(Iterable<Artifact> additionalHdrs) {
      this.additionalHdrs = Iterables.concat(this.additionalHdrs, additionalHdrs);
      return this;
    }

    /**
     * Adds header artifacts that should not be directly accessible to dependers.
     * {@code privateHdrs} should not be a {@link NestedSet}, as it will be flattened when added.
     */
    Builder addPrivateHdrs(Iterable<Artifact> privateHdrs) {
      this.privateHdrs = Iterables.concat(this.privateHdrs, privateHdrs);
      return this;
    }

    /**
     * Adds precompiled sources (.o files).
     */
    Builder addPrecompiledSrcs(Iterable<Artifact> precompiledSrcs) {
      this.precompiledSrcs = Iterables.concat(this.precompiledSrcs, precompiledSrcs);
      return this;
    }

    Builder setIntermediateArtifacts(IntermediateArtifacts intermediateArtifacts) {
      Preconditions.checkState(this.intermediateArtifacts == null,
          "intermediateArtifacts is already set to: %s", this.intermediateArtifacts);
      this.intermediateArtifacts = intermediateArtifacts;
      return this;
    }

    Builder addAllSources(CompilationArtifacts otherArtifacts) {
      return this.addNonArcSrcs(otherArtifacts.getNonArcSrcs())
          .addSrcs(otherArtifacts.getSrcs())
          .addPrecompiledSrcs(otherArtifacts.getPrecompiledSrcs())
          .addPrivateHdrs(otherArtifacts.getPrivateHdrs())
          .addAdditionalHdrs(otherArtifacts.getAdditionalHdrs());
    }

    CompilationArtifacts build() {
      Optional<Artifact> archive = Optional.absent();
      if (!Iterables.isEmpty(srcs)
          || !Iterables.isEmpty(nonArcSrcs)
          || !Iterables.isEmpty(precompiledSrcs)) {
        archive = Optional.of(intermediateArtifacts.archive());
      }
      return new CompilationArtifacts(
          srcs, nonArcSrcs, additionalHdrs, privateHdrs, precompiledSrcs, archive);
    }
  }

  private final Iterable<Artifact> srcs;
  private final Iterable<Artifact> nonArcSrcs;
  private final Optional<Artifact> archive;
  private final Iterable<Artifact> additionalHdrs;
  private final Iterable<Artifact> privateHdrs;
  private final Iterable<Artifact> precompiledSrcs;

  private CompilationArtifacts(
      Iterable<Artifact> srcs,
      Iterable<Artifact> nonArcSrcs,
      Iterable<Artifact> additionalHdrs,
      Iterable<Artifact> privateHdrs,
      Iterable<Artifact> precompiledSrcs,
      Optional<Artifact> archive) {
    this.srcs = Preconditions.checkNotNull(srcs);
    this.nonArcSrcs = Preconditions.checkNotNull(nonArcSrcs);
    this.additionalHdrs = Preconditions.checkNotNull(additionalHdrs);
    this.privateHdrs = Preconditions.checkNotNull(privateHdrs);
    this.precompiledSrcs = Preconditions.checkNotNull(precompiledSrcs);
    this.archive = Preconditions.checkNotNull(archive);
  }

  public Iterable<Artifact> getSrcs() {
    return srcs;
  }

  public Iterable<Artifact> getNonArcSrcs() {
    return nonArcSrcs;
  }

  /**
   * Returns the public headers that aren't included in the hdrs attribute.
   */
  public Iterable<Artifact> getAdditionalHdrs() {
    return additionalHdrs;
  }

  /**
   * Returns the private headers from the srcs attribute, which may by imported by any source or
   * header in this target, but not by sources or headers of dependers.
   */
  public Iterable<Artifact> getPrivateHdrs() {
    return privateHdrs;
  }

  /**
   * Returns .o files provided to the build directly as srcs.
   */
  public Iterable<Artifact> getPrecompiledSrcs() {
    return precompiledSrcs;
  }

  /**
   * Returns the output archive library (.a) file created by combining object files of the srcs,
   * non arc srcs, and precompiled srcs of this artifact collection. Returns absent if there
   * are no such source files for which to create an archive library.
   */
  public Optional<Artifact> getArchive() {
    return archive;
  }

}
