// Copyright (C) 2020-2025 Free Software Foundation, Inc.

// This file is part of GCC.

// GCC is free software; you can redistribute it and/or modify it under
// the terms of the GNU General Public License as published by the Free
// Software Foundation; either version 3, or (at your option) any later
// version.

// GCC is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
// for more details.

// You should have received a copy of the GNU General Public License
// along with GCC; see the file COPYING3.  If not see
// <http://www.gnu.org/licenses/>.

#ifndef RUST_CANONICAL_PATH
#define RUST_CANONICAL_PATH

#include "rust-system.h"
#include "rust-mapping-common.h"

namespace Rust {
namespace Resolver {

// https://doc.rust-lang.org/reference/paths.html#canonical-paths
//
// struct X - path X
// impl X { fn test - path X::test }
//
// struct X<T> - path X
//
// impl X<T>   { fn test - path X::test}
// impl X<i32> { fn test - path X<i32>::test }
// impl X<f32> { fn test - path X<f32>::test }
//
// pub trait Trait { // ::a::Trait
//   fn f(&self); // ::a::Trait::f
// }
//
// impl Trait for Struct {
//    fn f(&self) {} // <::a::Struct as ::a::Trait>::f
// }
class CanonicalPath
{
public:
  CanonicalPath (const CanonicalPath &other)
    : segs (other.segs), crate_num (other.crate_num)
  {}

  CanonicalPath &operator= (const CanonicalPath &other)
  {
    segs = other.segs;
    crate_num = other.crate_num;
    return *this;
  }

  static CanonicalPath new_seg (NodeId id, const std::string &path)
  {
    rust_assert (!path.empty ());
    return CanonicalPath ({std::pair<NodeId, std::string> (id, path)},
			  UNKNOWN_CRATENUM);
  }

  static CanonicalPath
  trait_impl_projection_seg (NodeId id, const CanonicalPath &trait_seg,
			     const CanonicalPath &impl_type_seg)
  {
    return CanonicalPath::new_seg (id, "<" + impl_type_seg.get () + " as "
					 + trait_seg.get () + ">");
  }

  static CanonicalPath inherent_impl_seg (NodeId id,
					  const CanonicalPath &impl_type_seg)
  {
    return CanonicalPath::new_seg (id, "<" + impl_type_seg.get () + ">");
  }

  std::string get () const
  {
    std::string buf;
    for (size_t i = 0; i < segs.size (); i++)
      {
	bool have_more = (i + 1) < segs.size ();
	const std::string &seg = segs.at (i).second;
	buf += seg + (have_more ? "::" : "");
      }
    return buf;
  }

  static CanonicalPath get_big_self (NodeId id)
  {
    return CanonicalPath::new_seg (id, "Self");
  }

  static CanonicalPath create_empty ()
  {
    return CanonicalPath ({}, UNKNOWN_CRATENUM);
  }

  bool is_empty () const { return segs.size () == 0; }

  CanonicalPath append (const CanonicalPath &other) const
  {
    rust_assert (!other.is_empty ());
    if (is_empty ())
      return CanonicalPath (other.segs, crate_num);

    std::vector<std::pair<NodeId, std::string>> copy (segs);
    for (auto &s : other.segs)
      copy.push_back (s);

    return CanonicalPath (copy, crate_num);
  }

  // if we have the path A::B::C this will give a callback for each segment
  // including the prefix, example:
  //
  // path:
  //   A::B::C
  //
  // iterate:
  //   A
  //   A::B
  //   A::B::C
  void iterate (std::function<bool (const CanonicalPath &)> cb) const
  {
    std::vector<std::pair<NodeId, std::string>> buf;
    for (auto &seg : segs)
      {
	buf.push_back (seg);
	if (!cb (CanonicalPath (buf, crate_num)))
	  return;
      }
  }

  // if we have the path A::B::C this will give a callback for each segment
  // example:
  //
  // path:
  //   A::B::C
  //
  // iterate:
  //   A
  //      B
  //         C
  void iterate_segs (std::function<bool (const CanonicalPath &)> cb) const
  {
    for (auto &seg : segs)
      {
	std::vector<std::pair<NodeId, std::string>> buf;
	buf.push_back ({seg.first, seg.second});
	if (!cb (CanonicalPath (buf, crate_num)))
	  return;
      }
  }

  size_t size () const { return segs.size (); }

  NodeId get_node_id () const
  {
    rust_assert (!segs.empty ());
    return segs.back ().first;
  }

  const std::pair<NodeId, std::string> &get_seg_at (size_t index) const
  {
    rust_assert (index < size ());
    return segs.at (index);
  }

  bool is_equal (const CanonicalPath &b) const
  {
    return get ().compare (b.get ()) == 0;
  }

  void set_crate_num (CrateNum n) { crate_num = n; }

  CrateNum get_crate_num () const
  {
    rust_assert (crate_num != UNKNOWN_CRATENUM);
    return crate_num;
  }

  bool operator== (const CanonicalPath &b) const { return is_equal (b); }

  bool operator< (const CanonicalPath &b) const { return get () < b.get (); }

private:
  explicit CanonicalPath (std::vector<std::pair<NodeId, std::string>> path,
			  CrateNum crate_num)
    : segs (path), crate_num (crate_num)
  {}

  std::vector<std::pair<NodeId, std::string>> segs;
  CrateNum crate_num;
};

} // namespace Resolver
} // namespace Rust

#endif // RUST_CANONICAL_PATH