// file      : libbuild2/scope.ixx -*- C++ -*-
// license   : MIT; see accompanying LICENSE file

namespace build2
{
  // scope
  //
  inline bool scope::
  root () const
  {
    return root_ == this;
  }

  inline bool scope::
  amalgamatable () const
  {
    return (root_extra == nullptr ||
            !root_extra->amalgamation ||
            *root_extra->amalgamation != nullptr);
  }

  inline scope* scope::
  parent_scope ()
  {
    // If this is a root scope and amalgamation is disabled, "jump" straight
    // to the global scope.
    //
    return root () && !amalgamatable () ? &global_scope () : parent_;
  }

  inline const scope* scope::
  parent_scope () const
  {
    return root () && !amalgamatable () ? &global_scope () : parent_;
  }

  inline scope* scope::
  root_scope ()
  {
    return root_;
  }

  inline const scope* scope::
  root_scope () const
  {
    return root_;
  }

  inline scope* scope::
  strong_scope ()
  {
    // We naturally assume strong_ is not set for non-amalgamatable projects.
    //
    return root_ != nullptr
      ? root_->strong_ != nullptr ? root_->strong_ : root_
      : nullptr;
  }

  inline const scope* scope::
  strong_scope () const
  {
    return root_ != nullptr
      ? root_->strong_ != nullptr ? root_->strong_ : root_
      : nullptr;
  }

  inline scope* scope::
  weak_scope ()
  {
    scope* r (root_);
    if (r != nullptr)
      for (;
           r->amalgamatable () && r->parent_->root_ != nullptr;
           r = r->parent_->root_) ;
    return r;
  }

  inline const scope* scope::
  weak_scope () const
  {
    const scope* r (root_);
    if (r != nullptr)
      for (;
           r->amalgamatable () && r->parent_->root_ != nullptr;
           r = r->parent_->root_) ;
    return r;
  }

  inline bool scope::
  sub_root (const scope& r) const
  {
    // Scan the parent root scope chain looking for this scope.
    //
    for (const scope* pr (&r);
         pr->amalgamatable () && (pr = pr->parent_->root_) != nullptr; )
    {
      if (pr == this)
        return true;
    }

    return false;
  }

  inline target_key scope::
  find_target_key (name& n, name& o, const location& loc) const
  {
    auto p (find_target_type (n, o, loc));
    return target_key {
      &p.first,
      &n.dir,
      o.dir.empty () ? &empty_dir_path : &o.dir,
      &n.value,
      move (p.second)};
  }

  inline prerequisite_key scope::
  find_prerequisite_key (name& n, name& o, const location& loc) const
  {
    auto p (find_prerequisite_type (n, o, loc));
    return prerequisite_key {
      n.proj,
      {
        &p.first,
        &n.dir,
        o.dir.empty () ? &empty_dir_path : &o.dir,
        &n.value,
        move (p.second)
      },
      this};
  }

  inline dir_path
  src_out (const dir_path& out, const scope& r)
  {
    assert (r.root ());
    return src_out (out, r.out_path (), r.src_path ());
  }

  inline dir_path
  out_src (const dir_path& src, const scope& r)
  {
    assert (r.root ());
    return out_src (src, r.out_path (), r.src_path ());
  }

  inline dir_path
  src_out (const dir_path& o,
           const dir_path& out_root, const dir_path& src_root)
  {
    assert (o.sub (out_root));
    return src_root / o.leaf (out_root);
  }

  inline dir_path
  out_src (const dir_path& s,
           const dir_path& out_root, const dir_path& src_root)
  {
    assert (s.sub (src_root));
    return out_root / s.leaf (src_root);
  }

  inline const project_name&
  project (const scope& rs)
  {
    auto l (rs[rs.ctx.var_project]);
    return l ? cast<project_name> (l) : empty_project_name;
  }

  inline const project_name&
  named_project (const scope& rs)
  {
    for (auto r (&rs), a (rs.strong_scope ());
         ;
         r = r->parent_scope ()->root_scope ())
    {
      const project_name& n (project (*r));
      if (!n.empty ())
        return n;

      if (r == a)
        break;
    }

    return empty_project_name;
  }
}