// file      : build/algorithm.ixx -*- C++ -*-
// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
// license   : MIT; see accompanying LICENSE file

#include <utility> // pair

#include <build/rule>
#include <build/prerequisite>
#include <build/context>

namespace build
{
  inline target&
  search (prerequisite& p)
  {
    if (p.target == nullptr)
      p.target = &search (p.key ());

    return *p.target;
  }

  inline target&
  search (const target_type& t, const prerequisite_key& k)
  {
    return search (
      prerequisite_key
        {k.proj, {&t, k.tk.dir, k.tk.name, k.tk.ext}, k.scope});
  }

  inline target&
  search (const target_type& type,
          const dir_path& dir,
          const std::string& name,
          const std::string* ext,
          scope* scope)
  {
    const std::string* proj (nullptr);
    return search (
      prerequisite_key
        {&proj, {&type, &dir, &name, &ext}, scope});
  }

  template <typename T>
  inline T&
  search (const dir_path& dir,
          const std::string& name,
          const std::string* ext,
          scope* scope)
  {
    return static_cast<T&> (search (T::static_type, dir, name, ext, scope));
  }

  std::pair<const rule*, match_result>
  match_impl (action, target&, bool apply);

  inline void
  match (action a, target& t)
  {
    if (!t.recipe (a))
      match_impl (a, t, true);

    t.dependents++;
    dependency_count++;

    // text << "M " << t << ": " << t.dependents << " " << dependency_count;
  }

  inline void
  unmatch (action, target& t)
  {
    // text << "U " << t << ": " << t.dependents << " " << dependency_count;

    assert (t.dependents != 0 && dependency_count != 0);
    t.dependents--;
    dependency_count--;
  }

  inline void
  match_only (action a, target& t)
  {
    if (!t.recipe (a))
      match_impl (a, t, false);
  }

  inline std::pair<recipe, action>
  match_delegate (action a, target& t)
  {
    auto rp (match_impl (a, t, false));
    const match_result& mr (rp.second);
    return std::make_pair (rp.first->apply (mr.recipe_action, t, mr),
                           mr.recipe_action);
  }

  group_view
  resolve_group_members_impl (action, target&);

  inline group_view
  resolve_group_members (action a, target& g)
  {
    group_view r (g.group_members (a));
    return r.members != nullptr ? r : resolve_group_members_impl (a, g);
  }

  inline void
  search_and_match_prerequisites (action a, target& t)
  {
    search_and_match_prerequisites (
      a,
      t,
      a.operation () != clean_id
      ? dir_path ()
      : t.strong_scope ().path ());
  }

  inline void
  search_and_match_prerequisite_members (action a, target& t)
  {
    if (a.operation () != clean_id)
      search_and_match_prerequisite_members (a, t, dir_path ());
    else
      // Note that here we don't iterate over members even for see-
      // through groups since the group target should clean eveything
      // up. A bit of an optimization.
      //
      search_and_match_prerequisites (a, t, t.strong_scope ().path ());
  }

  target_state
  execute_impl (action, target&);

  inline target_state
  execute (action a, target& t)
  {
    // text << "E " << t << ": " << t.dependents << " " << dependency_count;

    if (dependency_count != 0) // Re-examination of a postponed target?
    {
      assert (t.dependents != 0);
      t.dependents--;
      dependency_count--;
    }

    switch (target_state ts = t.state ())
    {
    case target_state::unchanged:
    case target_state::changed:
      return ts;
    default:
      {
        // Handle the "last" execution mode.
        //
        // This gets interesting when we consider interaction with
        // groups. It seem to make sense to treat group members as
        // dependents of the group, so, for example, if we try to
        // clean the group via three of its members, only the last
        // attempt will actually execute the clean. This means that
        // when we match a group member, inside we should also match
        // the group in order to increment the dependents count. This
        // seems to be a natural requirement: if we are delegating to
        // the group, we need to find a recipe for it, just like we
        // would for a prerequisite.
        //
        // Note that below we are going to change the group state
        // to postponed. This is not a mistake: until we execute
        // the recipe, we want to keep returning postponed. And
        // once the recipe is executed, it will reset the state
        // to group (see group_action()). To put it another way,
        // the execution of this member is postponed, not of the
        // group.
        //
        // One important invariant to keep in mind: the return
        // value from execute() should always be the same as what
        // would get returned by a subsequent call to state().
        //
        if (current_mode == execution_mode::last && t.dependents != 0)
          return (t.raw_state = target_state::postponed);

        return execute_impl (a, t);
      }
    }
  }

  inline target_state
  execute_delegate (const recipe& r, action a, target& t)
  {
    return r (a, t);
  }

  inline target_state
  execute_direct (action a, target& t)
  {
    switch (target_state ts = t.state ())
    {
    case target_state::unchanged:
    case target_state::changed: return ts;
    default: return execute_impl (a, t);
    }
  }
}