// file      : libbuild2/install/rule.hxx -*- C++ -*-
// license   : MIT; see accompanying LICENSE file

#ifndef LIBBUILD2_INSTALL_RULE_HXX
#define LIBBUILD2_INSTALL_RULE_HXX

#include <libbuild2/types.hxx>
#include <libbuild2/utility.hxx>

#include <libbuild2/rule.hxx>
#include <libbuild2/action.hxx>
#include <libbuild2/target.hxx>
#include <libbuild2/filesystem.hxx>

#include <libbuild2/export.hxx>

namespace build2
{
  namespace install
  {
    class LIBBUILD2_SYMEXPORT alias_rule: public rule
    {
    public:
      virtual bool
      match (action, target&, const string&) const override;

      // Return NULL if this prerequisite should be ignored and pointer to its
      // target otherwise. The default implementation accepts all prerequsites
      // from the target's (weak) amalgamation.
      //
      // The prerequisite is passed as an iterator allowing the filter to
      // "see" inside groups.
      //
      using prerequisite_iterator =
        prerequisite_members_range<group_prerequisites>::iterator;

      virtual const target*
      filter (action, const target&, prerequisite_iterator&) const;

      virtual const target*
      filter (action, const target&, const prerequisite&) const;

      virtual recipe
      apply (action, target&) const override;

      alias_rule () {}
      static const alias_rule instance;
    };

    class fsdir_rule: public rule
    {
    public:
      virtual bool
      match (action, target&, const string&) const override;

      virtual recipe
      apply (action, target&) const override;

      fsdir_rule () {}
      static const fsdir_rule instance;
    };

    // In addition to the alias rule's semantics, this rule sees through to
    // the group's members.
    //
    // The default group_rule::instance matches any target for which it was
    // registered. It is to be used for non-see-through groups that should
    // exhibit the see-through behavior for install (see lib{} in the bin
    // module for an example).
    //
    // We also register (for all targets) another instance of this rule that
    // only matches see-through groups.
    //
    class LIBBUILD2_SYMEXPORT group_rule: public alias_rule
    {
    public:
      virtual bool
      match (action, target&, const string&) const override;

      // Return NULL if this group member should be ignored and pointer to its
      // target otherwise. The default implementation accepts all members.
      //
      virtual const target*
      filter (action, const target&, const target& group_member) const;

      using alias_rule::filter; // "Unhide" to make Clang happy.

      virtual recipe
      apply (action, target&) const override;

      group_rule (bool see_through_only): see_through (see_through_only) {}
      static const group_rule instance;

      bool see_through;
    };

    struct install_dir;

    class LIBBUILD2_SYMEXPORT file_rule: public rule
    {
    public:
      virtual bool
      match (action, target&, const string&) const override;

      // Return NULL if this prerequisite should be ignored and pointer to its
      // target otherwise. The default implementation ignores prerequsites
      // that are outside of this target's project.
      //
      // @@ I wonder why we do weak amalgamation for alias but project for
      //    file? And then override this for prerequisite libraries/modules
      //    in cc::install_rule and bash::install_rule...
      //
      // The prerequisite is passed as an iterator allowing the filter to
      // "see" inside groups.
      //
      using prerequisite_iterator =
        prerequisite_members_range<group_prerequisites>::iterator;

      virtual const target*
      filter (action, const target&, prerequisite_iterator&) const;

      virtual const target*
      filter (action, const target&, const prerequisite&) const;

      virtual recipe
      apply (action, target&) const override;

      static target_state
      perform_update (action, const target&);

      // Extra un/installation hooks. Return true if anything was actually
      // un/installed.
      //
      using install_dir = install::install_dir; // For derived rules.

      virtual bool
      install_extra (const file&, const install_dir&) const;

      virtual bool
      uninstall_extra (const file&, const install_dir&) const;

      // Lower-level pre/post installation hooks that can be used to override
      // the source file path being installed (for example, to implement
      // post-processing, etc).
      //
      // Note that one cannot generally perform post-processing in-place
      // because of permissions.
      //
      virtual auto_rmfile
      install_pre (const file&, const install_dir&) const;

      virtual bool
      install_post (const file&, const install_dir&, auto_rmfile&&) const;

      // Installation/uninstallation "commands".
      //
      // The verbosity argument specified the level to start printing the
      // command at. Note that these functions respect the dry_run flag.

      // Install (create) a directory:
      //
      // install -d <dir>
      //
      static void
      install_d (const scope& rs,
                 const install_dir& base,
                 const dir_path& dir,
                 uint16_t verbosity = 1);

      // Install a file:
      //
      // install <file> <base>/        # if <name> is empty
      // install <file> <base>/<name>  # if <name> is not empty
      //
      static void
      install_f (const scope& rs,
                 const install_dir& base,
                 const path& name,
                 const file& target,
                 const path& file,
                 uint16_t verbosity = 1);

      // Install (make) a symlink:
      //
      // ln -s <target> <base>/<link>
      //
      static void
      install_l (const scope& rs,
                 const install_dir& base,
                 const path& target,
                 const path& link,
                 uint16_t verbosity = 1);

      // Uninstall (remove) a file or symlink:
      //
      // uninstall <target> <base>/  # rm <base>/<target>.leaf (); name empty
      // uninstall <target> <name>   # rm <base>/<name>; target can be NULL
      //
      // Return false if nothing has been removed (i.e., the file does not
      // exist).
      //
      static bool
      uninstall_f (const scope& rs,
                   const install_dir& base,
                   const file* target,
                   const path& name,
                   uint16_t verbosity = 1);

      // Uninstall (remove) an empty directory.
      //
      // uninstall -d <dir>
      //
      // We try to remove all the directories between base and dir but not base
      // itself unless base == dir. Return false if nothing has been removed
      // (i.e., the directories do not exist or are not empty).
      //
      static bool
      uninstall_d (const scope& rs,
                   const install_dir& base,
                   const dir_path& dir,
                   uint16_t verbosity = 1);

      target_state
      perform_install (action, const target&) const;

      target_state
      perform_uninstall (action, const target&) const;

      static const file_rule instance;
      file_rule () {}
    };
  }
}

#endif // LIBBUILD2_INSTALL_RULE_HXX