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

#ifndef BUILD_OPERATION
#define BUILD_OPERATION

#include <string>
#include <iosfwd>
#include <vector>
#include <cstdint>
#include <functional> // reference_wrapper

#include <butl/string-table>

#include <build/types>

namespace build
{
  class location;
  class scope;
  class target_key;

  // While we are using uint8_t for the meta/operation ids, we assume
  // that each is limited to 4 bits (max 128 entries) so that we can
  // store the combined action id in uint8_t as well. This makes our
  // life easier when it comes to defining switch labels for action
  // ids (no need to mess with endian-ness).
  //
  // Note that 0 is not a valid meta/operation/action id.
  //
  using meta_operation_id = std::uint8_t;
  using operation_id = std::uint8_t;
  using action_id = std::uint8_t;

  struct action
  {
    action (meta_operation_id m, operation_id o): id ((m << 4) | o) {}

    meta_operation_id
    meta_operation () const {return id >> 4;}

    operation_id
    operation () const {return id & 0xF;}

    // Implicit conversion operator to action_id for the switch()
    // statement, etc.
    //
    operator action_id () const {return id;}

    action_id id;
  };

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

  // Id constants for build-in operations.
  //
  const meta_operation_id perform_id   = 1;

  // The default operation is a special marker that can be used to
  // indicate that no operation was explicitly specified by the user.
  //
  const operation_id default_id = 1;
  const operation_id update_id = 2;
  const operation_id clean_id  = 3;

  const action_id perform_update_id = (perform_id << 4) | update_id;
  const action_id perform_clean_id  = (perform_id << 4) | clean_id;

  // Recipe execution mode.
  //
  // When a target is a prerequisite of another target, its recipe can be
  // executed before the dependent's recipe (the normal case) or after.
  // We will call these "front" and "back" execution modes, respectively
  // (think "the prerequisite is 'front-running' the dependent").
  //
  // There could also be several dependent targets and the prerequisite's
  // recipe can be execute as part of the first dependent (the normal
  // case) or last (or for all/some of them; see the recipe execution
  // protocol in <target>). We will call these "first" and "last"
  // execution modes, respectively.
  //
  // Now you may be having a hard time imagining where a mode other than
  // the normal one (first/front) could be useful. An the answer is,
  // compensating or inverse operations such as clean, uninstall, etc.
  // If we use the last/back mode for, say, clean, then we will remove
  // targets in the order inverse to the way they were updated. While
  // this sounds like an elegant idea, are there any practical benefits
  // of doing it this way. As it turns out there is (at least) one: when
  // we are removing a directory (see fsdir{}), we want to do it after
  // all the targets that depend on it (such as files, sub-directories)
  // were removed. If we do it before, then the directory won't be empty
  // yet.
  //
  // It appears that this execution mode is dictated by the essence of
  // the operation. Constructive operations (those that "do") seem to
  // naturally use the first/front mode. That is, we need to "do" the
  // prerequisite first before we can "do" the dependent. While the
  // destructive ones (those that "undo") seem to need last/back. That
  // is, we need to "undo" all the dependents before we can "undo" the
  // prerequisite (say, we need to remove all the files before we can
  // remove their directory).
  //
  // If you noticed the parallel with the way C++ construction and
  // destruction works for base/derived object then you earned a gold
  // star!
  //
  // Note that the front/back mode is realized in the dependen's recipe
  // (which is another indication that it is a property of the operation).
  //
  enum class execution_mode {first, last};

  // Meta-operation info.
  //

  // Normally a list of resolved and matched targets to execute. But
  // can be something else, depending on the meta-operation.
  //
  typedef std::vector<void*> action_targets;

  struct meta_operation_info
  {
    const std::string name;

    // Name derivatives for diagnostics. If empty, then the meta-
    // operation need not be mentioned.
    //
    const std::string name_do;           // E.g., [to] 'configure'.
    const std::string name_doing;        // E.g., [while] 'configuring'.
    const std::string name_already_done; // E.g., [already] 'configured'.

    void (*meta_operation_pre) (); // Start of meta-operation batch.
    operation_id (*operation_pre) (operation_id); // Start of operation batch.

    // Meta-operation-specific logic to load the buildfile, resolve and match
    // the targets, and execute the action on the targets.
    //
    void (*load) (const path& buildfile,
                  scope& root,
                  const dir_path& out_base,
                  const dir_path& src_base,
                  const location&);

    void (*match) (action,
                   scope& root,
                   const target_key&,
                   const location&,
                   action_targets&);

    void (*execute) (action, const action_targets&);

    void (*operation_post) (operation_id); // End of operation batch.
    void (*meta_operation_post) (); // End of meta-operation batch.
  };

  // Built-in meta-operations.
  //

  // perform
  //

  // Load the buildfile. This is the default implementation that first
  // calls root_pre(), then creates the scope for out_base, and, finally,
  // loads the buildfile unless it has already been loaded for the root
  // scope.
  //
  void
  load (const path& buildfile,
        scope& root,
        const dir_path& out_base,
        const dir_path& src_base,
        const location&);

  // Resolve and match the target. This is the default implementation
  // that does just that and adds a pointer to the target to the list.
  //
  void
  match (action, scope&, const target_key&, const location&, action_targets&);

  // Execute the action on the list of targets. This is the default
  // implementation that does just that while issuing appropriate
  // diagnostics.
  //
  void
  execute (action, const action_targets&);

  extern meta_operation_info perform;

  // Operation info.
  //
  struct operation_info
  {
    const std::string name;

    // Name derivatives for diagnostics. Note that unlike meta-operations,
    // these can only be empty for the default operation (id 1), And
    // meta-operations that make use of the default operation shall not
    // have empty derivatives (failed which only target name will be
    // printed).
    //
    const std::string name_do;           // E.g., [to] 'update'.
    const std::string name_doing;        // E.g., [while] 'updating'.
    const std::string name_already_done; // E.g., [already] 'up to date'.

    const execution_mode mode;
  };

  // Build-in operations.
  //
  extern operation_info default_;
  extern operation_info update;
  extern operation_info clean;

  // Meta/operation tables.
  //
  using meta_operation_table = butl::string_table<
    meta_operation_id,
    std::reference_wrapper<const meta_operation_info>>;

  using operation_table = butl::string_table<
    operation_id,
    std::reference_wrapper<const operation_info>>;
}

namespace butl
{
  template <>
  struct string_table_traits<
    std::reference_wrapper<const build::meta_operation_info>>
  {
    static const std::string&
    key (const build::meta_operation_info& x) {return x.name;}
  };

  template <>
  struct string_table_traits<
    std::reference_wrapper<const build::operation_info>>
  {
    static const std::string&
    key (const build::operation_info& x) {return x.name;}
  };
}

#endif // BUILD_OPERATION