// 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.xcode.xcodegen;

import com.google.common.base.Function;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.devtools.build.xcode.xcodegen.proto.XcodeGenProtos.Control;
import com.google.devtools.build.xcode.xcodegen.proto.XcodeGenProtos.TargetControl;
import com.google.devtools.common.options.Option;
import com.google.devtools.common.options.Options;
import com.google.devtools.common.options.OptionsBase;
import com.google.devtools.common.options.OptionsParser;
import com.google.devtools.common.options.OptionsParsingException;

import com.facebook.buck.apple.xcode.xcodeproj.PBXProject;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Iterator;
import java.util.List;

/**
 * Entry-point for the command-line Xcode project generator.
 */
public class XcodeGen {
  /**
   * Options for {@link XcodeGen}.
   */
  public static class XcodeGenOptions extends OptionsBase {
    @Option(
        name = "control",
        help = "Path to a control file, which contains only a binary serialized instance of "
            + "the Control protocol buffer. Required.",
        defaultValue = "null")
    public String control;
  }

  public static void main(String[] args) throws IOException, OptionsParsingException {
    OptionsParser parser = OptionsParser.newOptionsParser(XcodeGenOptions.class);
    parser.parse(args);
    XcodeGenOptions options = parser.getOptions(XcodeGenOptions.class);
    if (options.control == null) {
      throw new IllegalArgumentException("--control must be specified\n"
          + Options.getUsage(XcodeGenOptions.class));
    }
    FileSystem fileSystem = FileSystems.getDefault();

    Control controlPb;
    try (InputStream in = Files.newInputStream(fileSystem.getPath(options.control))) {
      controlPb = Control.parseFrom(in);
    }
    Path pbxprojPath = fileSystem.getPath(controlPb.getPbxproj());

    Iterator<String> srcList = allSourceFilePaths(controlPb).iterator();
    Path workspaceRoot;

    // TODO(bazel-team): Remove this if-else clause once Bazel passes in the workspace root.
    if (controlPb.hasWorkspaceRoot()) {
      workspaceRoot = fileSystem.getPath(controlPb.getWorkspaceRoot());
    } else if (!srcList.hasNext()) {
      workspaceRoot = XcodeprojGeneration.relativeWorkspaceRoot(pbxprojPath);
    } else {
      // Get the absolute path to the workspace root.

      // TODO(bazel-team): Remove this hack, possibly by converting Xcodegen to be run with
      // "bazel run" and using RUNFILES to get the workspace root. For now, this is needed to work
      // around Xcode's handling of symlinks not playing nicely with how Bazel stores output
      // artifacts in /private/var/tmp. This means a relative path from .xcodeproj in bazel-out to
      // the workspace root in .xcodeproj will not work properly at certain times during
      // Xcode/xcodebuild execution. Walking up the path of a known source file prevents having
      // to reason about a file that might only be accessible through a symlink, like a tools jar.
      Path relSourceFilePath = fileSystem.getPath(srcList.next());
      Path absSourceFilePath = relSourceFilePath.toAbsolutePath();
      workspaceRoot = absSourceFilePath;
      for (int i = 0; i < relSourceFilePath.getNameCount(); i++) {
        workspaceRoot = workspaceRoot.getParent();
      }
    }

    try (OutputStream out = Files.newOutputStream(pbxprojPath)) {
      // This workspace root here is relative to the PWD, so that the .xccurrentversion
      // files can actually be read. The other workspaceRoot is relative to the .xcodeproj
      // root or is absolute.
      Path relativeWorkspaceRoot = fileSystem.getPath(".");
      PBXProject project = XcodeprojGeneration.xcodeproj(
          workspaceRoot, controlPb,
          ImmutableList.of(
              new CurrentVersionSetter(relativeWorkspaceRoot),
              new PbxReferencesGrouper(fileSystem)));
      XcodeprojGeneration.write(out, project);
    }
  }

  private static Iterable<String> allSourceFilePaths(Control control) {
    return Iterables.concat(
        Iterables.transform(control.getTargetList(),
                            new Function<TargetControl, List<String>>() {
                              public List<String> apply(TargetControl tc) {
                                return tc.getSourceFileList();
                              }
                            }));
  }
}
