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

#ifndef BUILD_TARGET
#define BUILD_TARGET

#include <map>
#include <string>
#include <vector>
#include <memory>      // unique_ptr
#include <cstddef>     // size_t
#include <cstdint>     // uint8_t
#include <functional>  // reference_wrapper
#include <ostream>
#include <cassert>
#include <utility>     // move(), forward(), declval()
#include <iterator>
#include <type_traits>

#include <butl/utility>     // reverse_iterate()
#include <butl/multi-index> // map_iterator_adapter

#include <build/types>
#include <build/utility>

#include <build/scope>
#include <build/variable>
#include <build/operation>
#include <build/target-type>
#include <build/target-key>
#include <build/prerequisite>

namespace build
{
  class scope;
  class target;

  target&
  search (prerequisite&); // From <build/algorithm>.

  // Target state.
  //
  enum class target_state: std::uint8_t
  {
    // The order of the enumerators is arranged so that their integral
    // values indicate whether one "overrides" the other in the merge
    // operator (see below).
    //
    unknown,
    unchanged,
    postponed,
    changed,
    failed,
    group       // Target's state is the group's state.
  };

  std::ostream&
  operator<< (std::ostream&, target_state);

  inline target_state&
  operator |= (target_state& l, target_state r)
  {
    if (static_cast<std::uint8_t> (r) > static_cast<std::uint8_t> (l))
      l = r;

    return l;
  }

  // Recipe.
  //
  // The returned target state should be changed, unchanged, or
  // postponed, though you shouldn't be returning postponed
  // directly. If there is an error, then the recipe should
  // throw rather than returning failed.
  //
  // The return value of the recipe is used to update the target
  // state except if the state was manually set by the recipe to
  // target_state::group. Note that in this case the returned by
  // the recipe value is still used as the resulting target state
  // so it should match the group's state.
  //
  using recipe_function = target_state (action, target&);
  using recipe = std::function<recipe_function>;

  // Commonly-used recipes. The default recipe executes the action on
  // all the prerequisites in a loop, skipping ignored. Specifically,
  // for actions with the "first" execution mode, it calls
  // execute_prerequisites() while for those with the "last" mode --
  // reverse_execute_prerequisites(); see <build/operation>,
  // <build/algorithm> for details. The group recipe call's the group's
  // recipe.
  //
  extern const recipe empty_recipe;
  extern const recipe noop_recipe;
  extern const recipe default_recipe;
  extern const recipe group_recipe;

  target_state
  noop_action (action, target&); // Defined in <build/algorithm>.

  target_state
  group_action (action, target&); // Defined in <build/algorithm>.

  // Prerequisite references as used in the target::prerequisites list
  // below.
  //
  struct prerequisite_ref: std::reference_wrapper<prerequisite>
  {
    typedef std::reference_wrapper<prerequisite> base;

    using base::base;

    // Return true if this reference belongs to the target's prerequisite
    // list. Note that this test only works if you use references to
    // the container elements and the container hasn't been resized
    // since such a reference was obtained. Normally this function is
    // used when iterating over a combined prerequisites range (see
    // group_prerequisites below).
    //
    bool
    belongs (const target&) const;
  };

  // A view of target group members.
  //
  struct group_view
  {
    target* const* members; // NULL means not yet known.
    std::size_t count;
  };

  // Target.
  //
  class target
  {
  public:
    typedef build::action action_type;

    virtual
    ~target () = default;

    target (const target&) = delete;
    target& operator= (const target&) = delete;

    target (dir_path d, std::string n, const std::string* e)
        : dir (std::move (d)), name (std::move (n)), ext (e) {}

    // Reset the target before matching a rule for it. The
    // default implementation clears prerequisite_targets.
    //
    virtual void
    reset (action_type);

    const dir_path dir;      // Absolute and normalized.
    const std::string name;
    const std::string* ext;  // Extension, NULL means unspecified,
                             // empty means no extension.

    // Target group to which this target belongs, if any. Note that
    // we assume that the group and all its members are in the same
    // scope (for example, in variable lookup). We also don't support
    // nested groups.
    //
    // The semantics of the interaction between the group and its
    // members and what it means to, say, update the group, is
    // unspecified and determined by the group's type. In particular,
    // a group can be created out of member types that have no idea
    // they are part of this group (e.g., cli.cxx{}).
    //
    // Normally, however, there are two kinds of groups: "alternatives"
    // and "combination". In an alternatives group, normally one of the
    // members is selected when the group is mentioned as a prerequisite
    // with, perhaps, an exception for special rules, like aliases, where
    // it makes more sense to treat the group as a whole. In this case we
    // say that the rule "semantically recognizes" the group and picks
    // some of its members.
    //
    // Updating an alternatives group as a whole can mean updating some
    // subset of its members (e.g., lib{}). Or the group may not support
    // this at all (e.g., obj{}).
    //
    // In a combination group, when a group is updated, normally all
    // members are updates (and usually with a single command), though
    // there could be some members that are omitted, depending on the
    // configuration (e.g., an inline file not/being generated). When
    // a combination group is mentioned as a prerequisite, the rule
    // is usually interested in the individual members rather than
    // the whole group. For example, a C++ compile rule would like to
    // "see" the ?xx{} members when it gets a cli.cxx{} group.
    //
    // Which brings us to the group iteration mode. The target type
    // contains a member called see_through that indicates whether the
    // default iteration mode for the group should be "see through";
    // that is, whether we see the members or the group itself. For
    // the iteration support itself, see the *_prerequisite_members()
    // machinery below.
    //
    target* group {nullptr};

    // You should not call this function directly; rather use
    // resolve_group_members() from <build/algorithm>.
    //
    virtual group_view
    group_members (action_type) const;

    target_key
    key () const {return target_key {&type (), &dir, &name, &ext};}

    // Scoping.
    //
   public:
    // Most qualified scope that contains this target.
    //
    scope&
    base_scope () const;

    // Root scope of a project that contains this target. Note that
    // a target can be out of any (known) project root in which case
    // this function asserts. If you need to detect this situation,
    // then use base_scope().root_scope() expression instead.
    //
    scope&
    root_scope () const;

    // Root scope of a strong amalgamation that contains this target.
    // The same notes as to root_scope() apply.
    //
    scope&
    strong_scope () const {return *root_scope ().strong_scope ();}


    bool
    in (const scope& s) const
    {
      return
        (s.out_path_ != nullptr && dir.sub (*s.out_path_)) ||
        (s.src_path_ != nullptr && dir.sub (*s.src_path_));
    }

    // Prerequisites.
    //
  public:
    typedef std::vector<prerequisite_ref> prerequisites_type;
    prerequisites_type prerequisites;

    // Targets to which prerequisites resolve for this recipe. Note
    // that unlike prerequisite::target, these can be resolved to
    // group members. NULL means the target should be skipped (or
    // the rule may simply not add such a target to the list).
    //
    // Note also that it is possible the target can vary from
    // action to action, just like recipes. We don't need to keep
    // track of the action here since the targets will be updated
    // if the recipe is updated, normally as part of rule::apply().
    //
    typedef std::vector<target*> prerequisite_targets_type;
    prerequisite_targets_type prerequisite_targets;

    // Check if there are any prerequisites, taking into account
    // group prerequisites.
    //
    bool
    has_prerequisites () const
    {
      return !prerequisites.empty () ||
        (group != nullptr && !group->prerequisites.empty ());
    }

    // Target-specific variables.
    //
  public:
    variable_map vars;

    // Lookup, including in groups to which this target belongs and
    // then in outer scopes (including target type/pattern-specific
    // variables). If you only want to lookup in this target, do it
    // on the variable map directly.
    //
    lookup<const value>
    operator[] (const variable&) const;

    lookup<const value>
    operator[] (const std::string& name) const
    {
      return operator[] (variable_pool.find (name));
    }

    // Return a value suitable for assignment. See class scope for
    // details.
    //
    value&
    assign (const variable& var) {return vars.assign (var).first;}

    value&
    assign (const std::string& name) {return vars.assign (name).first;}

    // Return a value suitable for appending. See class scope for
    // details.
    //
    value&
    append (const variable&);

    value&
    append (const std::string& name)
    {
      return append (variable_pool.find (name));
    }

  public:
    target_state raw_state;

    target_state
    state () const
    {
      return raw_state != target_state::group ? raw_state : group->raw_state;
    }

    // Number of direct targets that depend on this target in the current
    // action. It is incremented during the match phase and then decremented
    // during execution, before running the recipe. As a result, the recipe
    // can detect the last chance (i.e., last dependent) to execute the
    // command (see also the first/last execution modes in <operation>).
    //
    // Note that setting a new recipe (which happens when we match the rule
    // and which in turn is triggered by the first dependent) clears this
    // counter. However, if the previous action was the same as the current,
    // then the existing recipe is reused. In this case, however, the counter
    // should have been decremented to 0 naturally, as part of the previous
    // action execution.
    //
    std::size_t dependents;

  public:
    action_type action; // Action this recipe is for.

  public:
    typedef build::recipe recipe_type;

    const recipe_type&
    recipe (action_type a) const {return a > action ? empty_recipe : recipe_;}

    void
    recipe (action_type, recipe_type);

    // Target type info.
    //
  public:
    template <typename T>
    T*
    is_a () {return dynamic_cast<T*> (this);}

    template <typename T>
    const T*
    is_a () const {return dynamic_cast<const T*> (this);}

    // Dynamic derivation to support define.
    //
    const target_type* derived_type = nullptr;

    const target_type&
    type () const
    {
      return derived_type != nullptr ? *derived_type : dynamic_type ();
    }

    virtual const target_type& dynamic_type () const = 0;
    static const target_type static_type;

  private:
    recipe_type recipe_;
  };

  std::ostream&
  operator<< (std::ostream&, const target&);

  // A "range" that presents the prerequisites of a group and one of
  // its members as one continuous sequence, or, in other words, as
  // if they were in a single container. The group's prerequisites
  // come first followed by the member's. If you need to see them
  // in the other direction, iterate in reverse, for example:
  //
  // for (prerequisite_ref& pr: group_prerequisites (t))
  //
  // for (prerequisite_ref& pr: reverse_iterate (group_prerequisites (t))
  //
  // Note that in this case the individual elements of each list will
  // also be traversed in reverse, but that's what you usually want,
  // anyway.
  //
  class group_prerequisites
  {
  public:
    typedef target::prerequisites_type prerequisites_type;

    explicit
    group_prerequisites (target& t): t_ (t) {}

    struct iterator
    {
      typedef prerequisites_type::iterator base_iterator;

      typedef base_iterator::value_type value_type;
      typedef base_iterator::pointer pointer;
      typedef base_iterator::reference reference;
      typedef base_iterator::difference_type difference_type;
      typedef std::bidirectional_iterator_tag iterator_category;

      iterator () {}
      iterator (target* t, prerequisites_type* c, base_iterator i)
          : t_ (t), c_ (c), i_ (i) {}

      iterator&
      operator++ ()
      {
        if (++i_ == c_->end () && c_ != &t_->prerequisites)
        {
          c_ = &t_->prerequisites;
          i_ = c_->begin ();
        }
        return *this;
      }

      iterator
      operator++ (int) {iterator r (*this); operator++ (); return r;}

      iterator&
      operator-- ()
      {
        if (i_ == c_->begin () && c_ == &t_->prerequisites)
        {
          c_ = &t_->group->prerequisites;
          i_ = c_->end ();
        }

        --i_;
        return *this;
      }

      iterator
      operator-- (int) {iterator r (*this); operator-- (); return r;}

      reference operator* () const {return *i_;}
      pointer operator-> () const {return i_.operator -> ();}

      friend bool
      operator== (const iterator& x, const iterator& y)
      {
        return x.t_ == y.t_ && x.c_ == y.c_ && x.i_ == y.i_;
      }

      friend bool
      operator!= (const iterator& x, const iterator& y) {return !(x == y);}

    private:
      target* t_ {nullptr};
      prerequisites_type* c_ {nullptr};
      base_iterator i_;
    };

    typedef std::reverse_iterator<iterator> reverse_iterator;

    iterator
    begin () const
    {
      auto& c ((t_.group != nullptr && !t_.group->prerequisites.empty ()
                ? *t_.group : t_).prerequisites);
      return iterator (&t_, &c, c.begin ());
    }

    iterator
    end () const
    {
      auto& c (t_.prerequisites);
      return iterator (&t_, &c, c.end ());
    }

    reverse_iterator
    rbegin () const {return reverse_iterator (end ());}

    reverse_iterator
    rend () const {return reverse_iterator (begin ());}

    std::size_t
    size () const
    {
      return t_.prerequisites.size () +
        (t_.group != nullptr ? t_.group->prerequisites.size () : 0);
    }

  private:
    target& t_;
  };

  // A member of a prerequisite. If 'target' is NULL, then this is the
  // prerequisite itself. Otherwise, it is its member. In this case
  // 'prerequisite' still refers to the prerequisite.
  //
  struct prerequisite_member
  {
    typedef build::target target_type;
    typedef build::prerequisite prerequisite_type;

    prerequisite_ref& prerequisite;
    target_type* target;

    template <typename T>
    bool
    is_a () const
    {
      return target != nullptr
        ? target->is_a<T> () != nullptr
        : prerequisite.get ().is_a<T> ();
    }

    prerequisite_key
    key () const
    {
      return target != nullptr
        ? prerequisite_key {&prerequisite.get ().proj, target->key (), nullptr}
        : prerequisite.get ().key ();
    }

    const build::target_type&
    type () const
    {
      return target != nullptr ? target->type () : prerequisite.get ().type;
    }

    const std::string&
    name () const
    {
      return target != nullptr ? target->name : prerequisite.get ().name;
    }

    const std::string*
    proj () const
    {
      // Target cannot be project-qualified.
      //
      return target != nullptr ? nullptr : prerequisite.get ().proj;
    }

    target_type&
    search () const
    {
      return target != nullptr ? *target : build::search (prerequisite);
    }

    prerequisite_type&
    as_prerequisite (tracer&) const;
  };

  inline std::ostream&
  operator<< (std::ostream& os, const prerequisite_member& pm)
  {
    return os << pm.key ();
  }

  // A "range" that presents a sequence of prerequisites (e.g., from
  // group_prerequisites()) as a sequence of prerequisite_member's. For
  // each group prerequisite you will "see" either the prerequisite
  // itself or all its members, depending on the default iteration
  // mode of the target group type. You can skip the rest of the
  // group members with leave_group() and you can force iteration
  // over the members with enter_group(). Usage:
  //
  // for (prerequisite_member pm: prerequisite_members (a, ...))
  //
  // Where ... can be:
  //
  //   t.prerequisites
  //   reverse_iterate(t.prerequisites)
  //   group_prerequisites (t)
  //   reverse_iterate (group_prerequisites (t))
  //
  // But use shortcuts instead:
  //
  // prerequisite_members (a, t)
  // reverse_prerequisite_members (a, t)
  // group_prerequisite_members (a, t)
  // reverse_group_prerequisite_members (a, t)
  //
  template <typename T>
  class prerequisite_members_range;

  template <typename T>
  inline prerequisite_members_range<T>
  prerequisite_members (action a, T&& x, bool members = true)
  {
    return prerequisite_members_range<T> (a, std::forward<T> (x), members);
  }

  template <typename T>
  class prerequisite_members_range
  {
  public:
    prerequisite_members_range (action a, T&& r, bool m)
        : a_ (a), members_ (m), r_ (std::forward<T> (r)), e_ (r_.end ()) {}

    using base_iterator = decltype (std::declval<T> ().begin ());

    struct iterator
    {
      typedef prerequisite_member value_type;
      typedef const value_type* pointer;
      typedef const value_type& reference;
      typedef typename base_iterator::difference_type difference_type;
      typedef std::forward_iterator_tag iterator_category;

      iterator (): r_ (nullptr) {}
      iterator (const prerequisite_members_range* r, const base_iterator& i)
          : r_ (r), i_ (i), g_ {nullptr, 0}
      {
        if (r_->members_ && i_ != r_->e_ && i_->get ().type.see_through)
        {
          bool r (switch_members ());
          assert (r); // Group could not be resolved.
        }
      }

      iterator& operator++ ();
      iterator operator++ (int) {iterator r (*this); operator++ (); return r;}

      // Skip iterating over the rest of this group's members, if any.
      // Note that the only valid operation after this call is to
      // increment the iterator.
      //
      void
      leave_group ()
      {
        // Pretend we are on the last member of some group.
        //
        j_ = 0;
        g_.count = 1;
      }

      // Iterate over this group's members. Return false if the member
      // information is not available. Similar to leave_group(), you
      // should increment the iterator after calling this function
      // (provided it returned true).
      //
      bool
      enter_group ()
      {
        bool r (switch_members ());
        if (r)
          --j_; // Compensate for the increment that will follow.
        return r;
      }

      value_type operator* () const
      {
        return value_type {*i_, g_.count != 0 ? g_.members[j_ - 1] : nullptr};
      }

      pointer operator-> () const
      {
        static_assert (
          std::is_trivially_destructible<prerequisite_member>::value,
          "prerequisite_member is not trivially destructible");

        return new (&m_)
          value_type {*i_, g_.count != 0 ? g_.members[j_ - 1] : nullptr};
      }

      friend bool
      operator== (const iterator& x, const iterator& y)
      {
        return x.i_ == y.i_ &&
          x.g_.count == y.g_.count &&
          (x.g_.count == 0 || x.j_ == y.j_);
      }

      friend bool
      operator!= (const iterator& x, const iterator& y) {return !(x == y);}

    private:
      bool
      switch_members ();

    private:
      const prerequisite_members_range* r_;
      base_iterator i_;
      group_view g_;
      std::size_t j_; // 1-based index, to support enter_group().
      mutable std::aligned_storage<sizeof (prerequisite_member),
                                   alignof (prerequisite_member)>::type m_;
    };

    iterator
    begin () const {return iterator (this, r_.begin ());}

    iterator
    end () const {return iterator (this, e_);}

  private:
    action a_;
    bool members_; // Go into group members by default?
    T r_;
    base_iterator e_;
  };

  // prerequisite_members(t.prerequisites)
  //
  inline auto
  prerequisite_members (action a, target& t, bool members = true)
  {
    return prerequisite_members (a, t.prerequisites, members);
  }

  // prerequisite_members(reverse_iterate(t.prerequisites))
  //
  inline auto
  reverse_prerequisite_members (action a, target& t, bool members = true)
  {
    return prerequisite_members (
      a, butl::reverse_iterate (t.prerequisites), members);
  }

  // prerequisite_members(group_prerequisites (t))
  //
  inline auto
  group_prerequisite_members (action a, target& t, bool members = true)
  {
    return prerequisite_members (a, group_prerequisites (t), members);
  }

  // prerequisite_members(reverse_iterate (group_prerequisites (t)))
  //
  inline auto
  reverse_group_prerequisite_members (action a, target& t, bool members = true)
  {
    return prerequisite_members (
      a, butl::reverse_iterate (group_prerequisites (t)), members);
  }

  //
  //
  struct target_set
  {
    typedef std::map<target_key, std::unique_ptr<target>> map;
    typedef butl::map_iterator_adapter<map::const_iterator> iterator;

    iterator
    find (const target_key& k, tracer& trace) const;

    iterator
    find (const target_type& type,
          const dir_path& dir,
          const std::string& name,
          const std::string* ext,
          tracer& trace) const
    {
      return find (target_key {&type, &dir, &name, &ext}, trace);
    }

    // As above but ignore the extension and return the target or
    // nullptr instead of the iterator.
    //
    template <typename T>
    T*
    find (const dir_path& dir, const std::string& name) const
    {
      const std::string* e (nullptr);
      auto i (map_.find (target_key {&T::static_type, &dir, &name, &e}));
      return i != map_.end () ? static_cast<T*> (i->second.get ()) : nullptr;
    }

    iterator begin () const {return map_.begin ();}
    iterator end () const {return map_.end ();}

    std::pair<target&, bool>
    insert (const target_type&,
            dir_path dir,
            std::string name,
            const std::string* ext,
            tracer&);

    template <typename T>
    T&
    insert (const dir_path& dir,
            const std::string& name,
            const std::string* ext,
            tracer& t)
    {
      return static_cast<T&> (
        insert (T::static_type, dir, name, ext, t).first);
    }

    template <typename T>
    T&
    insert (const dir_path& dir, const std::string& name, tracer& t)
    {
      return static_cast<T&> (
        insert (T::static_type, dir, name, nullptr, t).first);
    }

    void
    clear () {map_.clear ();}

  private:
    map map_;
  };

  extern target_set targets;

  // Modification time-based target.
  //
  class mtime_target: public target
  {
  public:
    using target::target;

    // Generally, modification time for a target can only be queried
    // after a rule has been matched since that's where the path is
    // normally gets assigned. Normally, however, it would make sense
    // to first execute the rule to get the "updated" timestamp.
    //
    // The rule for groups that utilize the group state is as follows:
    // if it has any members that are mtime_targets, then the group
    // should be mtime_target and the members get the mtime from it.
    //
    timestamp
    mtime () const
    {
      const mtime_target* t (raw_state == target_state::group
                             ? static_cast<const mtime_target*> (group)
                             : this);

      if (t->mtime_ == timestamp_unknown)
        t->mtime_ = t->load_mtime ();

      return t->mtime_;
    }

    void
    mtime (timestamp mt)
    {
      // While we can cache the mtime at any time, it may be ignored
      // if the target state is group (see the mtime() accessor).
      //
      mtime_ = mt;
    }

  protected:
    virtual timestamp
    load_mtime () const = 0;

  public:
    static const target_type static_type;

  private:
    mutable timestamp mtime_ {timestamp_unknown};
  };

  // Filesystem path-based target.
  //
  class path_target: public mtime_target
  {
  public:
    using mtime_target::mtime_target;

    typedef build::path path_type;

    const path_type&
    path () const {return path_;}

    void
    path (path_type p) {assert (path_.empty ()); path_ = std::move (p);}

    // Derive a path from target's dir, name, and, if specified, ext.
    // If ext is not specified, then use default_ext and also update
    // the target's extension (this becomes important if later we need
    // to reliably determine whether this file has an extension; think
    // hxx{foo.bar.} and hxx.ext is empty).
    //
    // If name_prefix is not NULL, add it before the name part and after
    // the directory. Similarly, if name_suffix is not NULL, add it after
    // the name part and before the extension.
    //
    // Finally, if the path was already assigned to this target, then
    // this function verifies that the two are the same.
    //
    void
    derive_path (const char* default_ext = nullptr,
                 const char* name_prefix = nullptr,
                 const char* name_suffix = nullptr);

  public:
    static const target_type static_type;

  private:
    path_type path_;
  };

  // File target.
  //
  class file: public path_target
  {
  public:
    using path_target::path_target;

  protected:
    // Note that it is final in order to be consistent with file_rule,
    // search_existing_file().
    //
    virtual timestamp
    load_mtime () const final;

  public:
    static const target_type static_type;
    virtual const target_type& dynamic_type () const {return static_type;}
  };

  // Alias target. It represents a list of targets (its prerequisites)
  // as a single "name".
  //
  class alias: public target
  {
  public:
    using target::target;

  public:
    static const target_type static_type;
    virtual const target_type& dynamic_type () const {return static_type;}
  };

  // Directory target. Note that this is not a filesystem directory
  // but rather an alias target with the directory name. For actual
  // filesystem directory (creation), see fsdir.
  //
  class dir: public alias
  {
  public:
    using alias::alias;

  public:
    static const target_type static_type;
    virtual const target_type& dynamic_type () const {return static_type;}
  };

  // While a filesystem directory is mtime-based, the semantics is
  // not very useful in our case. In particular, if another target
  // depends on fsdir{}, then all that's desired is the creation of
  // the directory if it doesn't already exist. In particular, we
  // don't want to update the target just because some unrelated
  // entry was created in that directory.
  //
  class fsdir: public target
  {
  public:
    using target::target;

  public:
    static const target_type static_type;
    virtual const target_type& dynamic_type () const {return static_type;}
  };

  class buildfile: public file
  {
  public:
    using file::file;

  public:
    static const target_type static_type;
    virtual const target_type& dynamic_type () const {return static_type;}
  };

  // Common documentation file targets.
  //
  // @@ Maybe these should be in the built-in doc module?
  //
  class doc: public file
  {
  public:
    using file::file;

  public:
    static const target_type static_type;
    virtual const target_type& dynamic_type () const {return static_type;}
  };

  // The problem with man pages is this: different platforms have
  // different sets of sections. What seems to be the "sane" set
  // is 1-9 (Linux and BSDs). SysV (e.g., Solaris) instead maps
  // 8 to 1M (system administration). The section determines two
  // things: the directory where the page is installed (e.g.,
  // /usr/share/man/man1) as well as the extension of the file
  // (e.g., test.1). Note also that there could be sub-sections,
  // e.g., 1p (for POSIX). Such a page would still go into man1
  // but will have the .1p extension (at least that's what happens
  // on Linux). The challenge is to somehow handle this in a
  // portable manner. So here is the plan:
  //
  // First of all, we have the man{} target type which can be used
  // for a custom man page. That is, you can have any extension and
  // install it anywhere you please:
  //
  // man{foo.X}: install = man/manX
  //
  // Then we have man1..9{} target types which model the "sane"
  // section set and that would be automatically installed into
  // correct locations on other platforms. In other words, the
  // idea is that you should be able to have the foo.8 file,
  // write man8{foo} and have it installed as man1m/foo.1m on
  // some SysV host.
  //
  // Re-mapping the installation directory is easy: to help with
  // that we have assigned install.man1..9 directory names. The
  // messy part is to change the extension. It seems the only
  // way to do that would be to have special logic for man pages
  // in the generic install rule. @@ This is still a TODO.
  //
  // Note that handling subsections with man1..9{} is easy, we
  // simply specify the extension explicitly, e.g., man{foo.1p}.
  //
  class man: public doc
  {
  public:
    using doc::doc;

  public:
    static const target_type static_type;
    virtual const target_type& dynamic_type () const {return static_type;}
  };

  class man1: public man
  {
  public:
    using man::man;

  public:
    static const target_type static_type;
    virtual const target_type& dynamic_type () const {return static_type;}
  };

  // Common implementation of the target factory, extension, and
  // search functions.
  //
  template <typename T>
  target*
  target_factory (const target_type&, dir_path d, string n, const string* e)
  {
    return new T (move (d), move (n), e);
  }

  // Return fixed target extension.
  //
  template <const char* ext>
  const std::string&
  target_extension_fix (const target_key&, scope&);

  // Get the extension from the variable.
  //
  template <const char* var>
  const std::string&
  target_extension_var (const target_key&, scope&);

  // The default behavior, that is, look for an existing target in the
  // prerequisite's directory scope.
  //
  target*
  search_target (const prerequisite_key&);

  // First look for an existing target as above. If not found, then look
  // for an existing file in the target-type-specific list of paths.
  //
  target*
  search_file (const prerequisite_key&);

}

#include <build/target.ixx>
#include <build/target.txx>

#endif // BUILD_TARGET