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

#ifndef LIBBUILD2_CONFIG_MODULE_HXX
#define LIBBUILD2_CONFIG_MODULE_HXX

#include <cstring> // strncmp()

#include <libbutl/prefix-map.hxx>

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

#include <libbuild2/scope.hxx>
#include <libbuild2/module.hxx>
#include <libbuild2/variable.hxx>

#include <libbuild2/config/utility.hxx>

namespace build2
{
  namespace config
  {
    // An ordered list of build system modules each with an ordered list of
    // config.* variables and their save flags/function (see save_variable())
    // that are used (as opposed to just being specified) in this
    // configuration. Populated by the config utility functions (required(),
    // optional()) and saved in the order populated. If flags are absent, then
    // this variable was marked as "unsaved" (always transient).
    //
    struct saved_variable
    {
      reference_wrapper<const variable> var;
      optional<uint64_t> flags;
      save_variable_function* save;
    };

    struct saved_variables: vector<saved_variable>
    {
      // Normally each module only have a handful of config variables and we
      // only do this during configuration so for now we do linear search
      // instead of adding a map.
      //
      const_iterator
      find (const variable& var) const
      {
        return find_if (
          begin (),
          end (),
          [&var] (const saved_variable& v) {return var == v.var;});
      }
    };

    struct saved_modules: butl::prefix_map<string, saved_variables, '.'>
    {
      // Priority order with INT32_MIN being the highest. Modules with the
      // same priority are saved in the order inserted.
      //
      multimap<std::int32_t, const_iterator> order;

      pair<iterator, bool>
      insert (string name, int prio = 0)
      {
        auto p (emplace (move (name), saved_variables ()));

        if (p.second)
          order.emplace (prio, p.first);

        return p;
      }
    };

    // List of environment variable names that effect this project.
    //
    // Note that on Windows environment variable names are case-insensitive.
    //
    struct saved_environment: vector<string>
    {
      // Compare environment variable names.
      //
      static inline bool
      compare (const string& x,
               const string& y,
               size_t xn = string::npos,
               size_t yn = string::npos)
      {
        if (xn == string::npos) xn = x.size ();
        if (yn == string::npos) yn = y.size ();

        return xn == yn &&
#ifdef _WIN32
          icasecmp (x.c_str (), y.c_str (), xn) == 0
#else
          strncmp (x.c_str (), y.c_str (), xn) == 0
#endif
          ;
      }

      iterator
      find (const string& v)
      {
        return find_if (
          begin (),
          end (),
          [&v] (const string& v1) {return compare (v, v1);});
      }

      const_iterator
      find (const string& v) const
      {
        return find_if (
          begin (),
          end (),
          [&v] (const string& v1) {return compare (v, v1);});
      }

      void
      insert (string v)
      {
        if (find (v) == end ())
          push_back (move (v));
      }

      void
      erase (const string& v)
      {
        auto i (find (v));
        if (i != end ())
          vector<string>::erase (i);
      }
    };

    class module: public build2::module
    {
    public:
      config::saved_modules saved_modules;

      // Return true if variable/module were newly inserted.
      //
      bool
      save_variable (const variable&,
                     optional<uint64_t> flags,
                     save_variable_function* = nullptr);

      static void
      save_variable (scope&,
                     const variable&,
                     optional<uint64_t>,
                     save_variable_function*);

      bool
      save_module (const char* name, int prio = 0);

      static void
      save_module (scope&, const char*, int);

      const saved_variable*
      find_variable (const variable& var) const
      {
        auto i (saved_modules.find_sup (var.name));
        if (i != saved_modules.end ())
        {
          auto j (i->second.find (var));
          if (j != i->second.end ())
            return &*j;
        }

        return nullptr;
      }

      void
      save_environment (const char* var)
      {
        saved_environment.insert (var);
      }

      static void
      save_environment (scope&, const char*);

      config::saved_environment saved_environment;
      strings old_environment;

      // Configure/disfigure hooks.
      //
      static bool
      configure_post (scope&, configure_post_hook*);

      static bool
      disfigure_pre (scope&, disfigure_pre_hook*);

      small_vector<configure_post_hook*, 1> configure_post_;
      small_vector<disfigure_pre_hook*,  1> disfigure_pre_;

      // Cached (during init) config.config.persist value, if defined.
      //
      const vector<pair<string, string>>* persist = nullptr;

      static const string name;
      static const uint64_t version;
    };

    // Implementation-specific utilities.
    //

    inline path
    config_file (const scope& rs)
    {
      return (rs.out_path () /
              rs.root_extra->build_dir /
              "config." + rs.root_extra->build_ext);
    }
  }
}

#endif // LIBBUILD2_CONFIG_MODULE_HXX
