// Protocol Buffers - Google's data interchange format
// Copyright 2023 Google LLC.  All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd

#include "google/protobuf/compiler/rust/relative_path.h"

#include <string>
#include <vector>

#include "absl/algorithm/container.h"
#include "absl/strings/match.h"
#include "absl/strings/str_join.h"
#include "absl/strings/str_split.h"
#include "absl/strings/string_view.h"

namespace google {
namespace protobuf {
namespace compiler {
namespace rust {

std::vector<absl::string_view> RelativePath::Segments() const {
  return absl::StrSplit(this->path_, '/', absl::SkipWhitespace());
}

bool RelativePath::IsDirectory() const {
  return absl::EndsWith(this->path_, "/");
}

std::string RelativePath::Relative(const RelativePath& dest) const {
  ABSL_CHECK(!dest.IsDirectory())
      << "`dest` has to be a file path, but is a directory.";
  std::vector<absl::string_view> current_segments = this->Segments();

  if (!current_segments.empty() && !this->IsDirectory()) {
    // `this` represents a file path, skip the last segment to get its
    // directory.
    current_segments.pop_back();
  }

  std::vector<absl::string_view> dest_segments = dest.Segments();

  // Find the lowest common ancestor.
  absl::c_reverse(current_segments);
  absl::c_reverse(dest_segments);
  while (true) {
    if (current_segments.empty()) break;
    if (dest_segments.empty()) break;
    if (current_segments.back() != dest_segments.back()) break;

    current_segments.pop_back();
    dest_segments.pop_back();
  }

  // Construct the relative path in reverse order.
  std::vector<absl::string_view> result;
  result.reserve(current_segments.size() + dest_segments.size());
  // Push the segments from the `dest` to the common ancestor.
  for (const auto& segment : dest_segments) {
    result.push_back(segment);
  }
  // Push `..` from the common ancestor to the current path.
  for (int i = 0; i < current_segments.size(); ++i) {
    result.push_back("..");
  }
  absl::c_reverse(result);
  if (dest.IsDirectory()) {
    // Convince the `StrJoin` below to add a trailing `/`.
    result.push_back("");
  }
  return absl::StrJoin(result, "/");
}

}  // namespace rust
}  // namespace compiler
}  // namespace protobuf
}  // namespace google
