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

#ifndef BUILD_VARIABLE
#define BUILD_VARIABLE

#include <map>
#include <vector>
#include <cstddef>       // nullptr_t
#include <utility>       // pair, make_pair()
#include <iterator>
#include <functional>    // hash
#include <type_traits>   // conditional, is_reference, remove_reference, etc.
#include <unordered_set>

#include <butl/prefix-map>

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

#include <build/target-type>

namespace build
{
  struct variable;

  // If assign is NULL, then the value is assigned as is. If append
  // is NULL, then the names are appended to the end of the value
  // and assign is called, if not NULL. Variable is provided primarily
  // for diagnostics. Return true if the resulting value is not empty.
  //
  struct value_type
  {
    const char* name;
    bool (*const assign) (names&, const variable&);
    bool (*const append) (names&, names, const variable&);
  };

  enum class variable_visibility
  {
    scope,    // This scope (no outer scopes).
    project,  // This project (no outer projects).
    normal    // All outer scopes.
  };

  // variable
  //
  // The two variables are considered the same if they have the same name.
  //
  struct variable
  {
    std::string name;
    const value_type* type;         // If NULL, then not (yet) typed.
    variable_visibility visibility;
    char pairs;                     // Pair symbold or '\0' if not used.
  };

  inline bool
  operator== (const variable& x, const variable& y) {return x.name == y.name;}

  typedef std::reference_wrapper<const variable> variable_cref;

  // value
  //
  class value
  {
  public:
    // By default we create NULL value.
    //
    explicit value (const value_type* t = nullptr)
        : type (t), state_ (state_type::null) {}

    value (value&&) = default;

    value&
    operator= (std::nullptr_t)
    {
      data_.clear ();
      state_ = state_type::null;
      return *this;
    }

    value&
    operator= (value&&);

    value&
    operator= (const value& v)
    {
      if (&v != this)
        *this = value (v);
      return *this;
    }

    value&
    operator= (reference_wrapper<value> v) {return *this = v.get ();}

    value&
    operator= (reference_wrapper<const value> v) {return *this = v.get ();}

    value&
    append (value, const variable&); // Aka operator+=().

    // Forwarded to the representation type's assign()/append() (see below).
    //
    template <typename T> value& operator= (T);
    value& operator= (const char* v) {return *this = std::string (v);}

    template <typename T> value& operator+= (T);
    value& operator+= (const char* v) {return *this += std::string (v);}

  private:
    explicit value (const value&) = default;

  public:
    const value_type* type; // NULL means this value is not (yet) typed.

    bool null () const {return state_ == state_type::null;}
    bool empty () const {return state_ == state_type::empty;}

    explicit operator bool () const {return !null ();}
    bool operator== (std::nullptr_t) const {return null ();}
    bool operator!= (std::nullptr_t) const {return !null ();}

    // Raw data read interface.
    //
    using const_iterator = names::const_iterator;

    const_iterator begin () const {return data_.begin ();}
    const_iterator end () const {return data_.end ();}

    // Raw data write interface. Note that it triggers callbacks for
    // typed values. Variable is passed for diagnostics.
    //
    void
    assign (names, const variable&);

    void
    append (names, const variable&);

  public:
    // Don't use directly except in the implementation of representation
    // types, in which case take care to update state.
    //
    enum class state_type {null, empty, filled} state_;
    names data_;
  };

  //@@ Right now we assume that for each value type each value has a
  //   unique representation. This is currently not the case for map.
  //
  inline bool
  operator== (const value& x, const value& y)
  {
    return x.state_ == y.state_ && x.data_ == y.data_;
  }

  inline bool
  operator!= (const value& x, const value& y) {return !(x == y);}

  // lookup
  //
  // A variable can be undefined, NULL, or contain a (potentially
  // empty) value.
  //
  struct variable_map;

  template <typename V>
  struct lookup
  {
    V* value;
    const variable_map* vars;

    bool
    defined () const {return value != nullptr;}

    // Note: returns true if defined and not NULL.
    //
    explicit operator bool () const {return defined () && !value->null ();}

    V& operator*  () const {return *value;}
    V* operator-> () const {return value;}

    // Return true if this value belongs to the specified scope or target.
    // Note that it can also be a target type/pattern-specific value.
    //
    template <typename T>
    bool
    belongs (const T& x) const {return vars == &x.vars;}

    lookup (): value (nullptr), vars (nullptr) {}
    lookup (V* v, const variable_map* vs)
        : value (v), vars (v != nullptr ? vs : nullptr) {}

    template <typename T>
    lookup (V& v, const T& x): lookup (&v, &x.vars) {}
  };

  // Representation types.
  //
  template <typename T> struct value_traits;

  // Assign value type to the value.
  //
  template <typename T>
  void assign (value&, const variable&);
  void assign (value&, const value_type*, const variable&);

  template <typename T> typename value_traits<T>::type as (value&);
  template <typename T> typename value_traits<T>::const_type as (const value&);

  // "Assign" simple value type to the value stored in name. Return false
  // if the value is not valid for this type.
  //
  template <typename T> bool assign (name&);

  template <typename T> typename value_traits<T>::type as (name&);
  template <typename T> typename value_traits<T>::const_type as (const name&);

  // bool
  //
  template <typename D>
  struct bool_value
  {
    explicit
    operator bool () const {return d->value[0] == 't';}

    bool_value&
    operator= (bool v)
    {
      d->value = v ? "true" : "false";
      return *this;
    }

    bool_value&
    operator+= (bool v)
    {
      if (!*this && v)
        d->value = "true";
      return *this;
    }

    // Implementation details.
    //
  public:
    explicit bool_value (D& d): d (&d) {}

    bool_value (const bool_value&) = delete;
    bool_value& operator= (const bool_value&) = delete; // Rebind or deep?

    bool_value (bool_value&&) = default;
    bool_value& operator= (bool_value&&) = delete;

    D* d; // name
  };

  template <>
  struct value_traits<bool>
  {
    using type = bool_value<name>;
    using const_type = bool_value<const name>;

    static type       as (name& n) {return type (n);}
    static const_type as (const name& n) {return const_type (n);}

    static type       as (value&);
    static const_type as (const value&);

    static bool assign (name&);
    static void assign (value&, bool);
    static void append (value&, bool);

    static const build::value_type value_type;
  };

  extern const value_type* bool_type;

  // string
  //
  template <>
  struct value_traits<std::string>
  {
    using type = std::string&;
    using const_type = const std::string&;

    static type       as (name& n) {return n.value;}
    static const_type as (const name& n) {return n.value;}

    static type       as (value&);
    static const_type as (const value&);

    static bool assign (name&);
    static void assign (value&, std::string);
    static void append (value&, std::string);

    static const build::value_type value_type;
  };

  extern const value_type* string_type;

  // dir_path
  //
  template <>
  struct value_traits<dir_path>
  {
    using type = dir_path&;
    using const_type = const dir_path&;

    static type       as (name& n) {return n.dir;}
    static const_type as (const name& n) {return n.dir;}

    static type       as (value&);
    static const_type as (const value&);

    static bool assign (name&);
    static void assign (value&, dir_path);
    static void append (value&, dir_path);

    static const build::value_type value_type;
  };

  extern const value_type* dir_path_type;

  // name
  //
  template <>
  struct value_traits<name>
  {
    using type = name&;
    using const_type = const name&;

    static type       as (name& n) {return n;}
    static const_type as (const name& n) {return n;}

    static type       as (value&);
    static const_type as (const value&);

    static bool assign (name&) {return true;}
    static void assign (value&, name);
    static void append (value&, name) = delete;

    static const build::value_type value_type;
  };

  extern const value_type* name_type;

  // vector<T>
  //
  template <typename T, typename D>
  struct vector_value
  {
    using size_type = typename D::size_type;

    using value_type = typename value_traits<T>::type;
    using const_value_type = typename value_traits<T>::const_type;

    template <typename I, typename V, typename R>
    struct iterator_impl: I
    {
      using value_type = V;
      using pointer = value_type*;
      using reference = R;
      using difference_type = typename I::difference_type;

      iterator_impl () = default;
      iterator_impl (const I& i): I (i) {}

      // Note: operator->() is unavailable if R is a value.
      //
      reference operator* () const {return as<T> (I::operator* ());}
      pointer operator-> () const {return &as<T> (I::operator* ());}
      reference operator[] (difference_type n) const
      {
        return as<T> (I::operator[] (n));
      }
    };

    // Make iterator the same as const_iterator if our data type is const.
    //
    using const_iterator =
      iterator_impl<names::const_iterator, const T, const_value_type>;
    using iterator = typename std::conditional<
      std::is_const<D>::value,
      const_iterator,
      iterator_impl<names::iterator, T, value_type>>::type;

  public:
    vector_value&
    operator= (std::vector<T> v) {assign (std::move (v)); return *this;}

    vector_value&
    assign (std::vector<T>);

    template <typename D1>
    vector_value&
    assign (const vector_value<T, D1>&);

    template <typename D1>
    vector_value&
    append (const vector_value<T, D1>&);

  public:
    bool empty () const {return d->empty ();}
    size_type size () const {return d->size ();}

    value_type operator[] (size_type i) {return as<T> ((*d)[i]);}
    const_value_type operator[] (size_type i) const {return as<T> ((*d)[i]);}

    iterator begin () {return iterator (d->begin ());}
    iterator end () {return iterator (d->end ());}

    const_iterator begin () const {return const_iterator (d->begin ());}
    const_iterator end () const {return const_iterator (d->end ());}

    const_iterator cbegin () const {return const_iterator (d->begin ());}
    const_iterator cend () const {return const_iterator (d->end ());}

    // Implementation details.
    //
  public:
    explicit vector_value (D& d): d (&d) {}

    vector_value (const vector_value&) = delete;
    vector_value& operator= (const vector_value&) = delete; // Rebind or deep?

    vector_value (vector_value&&) = default;
    vector_value& operator= (vector_value&&) = default; //@@ TMP

    explicit vector_value (std::nullptr_t): d (nullptr) {} //@@ TMP

    D* d; // names
  };

  template <typename T>
  struct value_traits<std::vector<T>>
  {
    using type = vector_value<T, names>;
    using const_type = vector_value<T, const names>;

    static type       as (value&);
    static const_type as (const value&);

    template <typename V> static void assign (value&, V);
    template <typename V> static void append (value&, V);

    static const std::string type_name;
    static const build::value_type value_type;
  };

  template <typename T, typename D>
  struct value_traits<vector_value<T, D>>: value_traits<std::vector<T>> {};

  using strings_value = vector_value<std::string, names>;
  using const_strings_value = vector_value<std::string, const names>;

  extern const value_type* strings_type;   // vector<string> aka strings
  extern const value_type* dir_paths_type; // vector<dir_path> aka dir_paths
  extern const value_type* names_type;     // vector<name> aka names

  // map<K, V>
  //
  template <typename K, typename V, typename D>
  struct map_value
  {
    template <typename F, typename S>
    struct pair
    {
      using first_type = typename std::conditional<
        std::is_reference<F>::value,
        std::reference_wrapper<typename std::remove_reference<F>::type>,
        F>::type;

      using second_type = typename std::conditional<
        std::is_reference<S>::value,
        std::reference_wrapper<typename std::remove_reference<S>::type>,
        S>::type;

      first_type first;
      second_type second;
    };

    template <typename I, typename T>
    struct iterator_impl
    {
      using value_type = T;
      using pointer = value_type*;
      using reference = value_type; // Note: value.
      using difference_type = typename I::difference_type;
      using iterator_category = std::bidirectional_iterator_tag;

      iterator_impl () = default;
      iterator_impl (const I& i): i_ (i) {}

      pointer operator-> () const = delete;
      reference operator* () const
      {
        return value_type {as<K> (*i_), as<V> (*(i_ + 1))};
      }

      iterator_impl& operator++ () {i_ += 2; return *this;}
      iterator_impl operator++ (int) {auto r (*this); operator++ (); return r;}

      iterator_impl& operator-- () {i_ -= 2; return *this;}
      iterator_impl operator-- (int) {auto r (*this); operator-- (); return r;}

      bool operator== (const iterator_impl& y) const {return i_ == y.i_;}
      bool operator!= (const iterator_impl& y) const {return i_ != y.i_;}

    private:
      I i_;
    };

    using size_type = typename D::size_type;

    using value_type = pair<typename value_traits<K>::const_type,
                            typename value_traits<V>::type>;

    using const_value_type = pair<typename value_traits<K>::const_type,
                                  typename value_traits<V>::const_type>;

    // Make iterator the same as const_iterator if our data type is const.
    //
    using const_iterator =
      iterator_impl<names::const_iterator, const_value_type>;
    using iterator = typename std::conditional<
      std::is_const<D>::value,
      const_iterator,
      iterator_impl<names::iterator, value_type>>::type;


  public:
    map_value&
    operator= (std::map<K, V> m) {assign (std::move (m)); return *this;}

    map_value&
    assign (std::map<K, V>);

    bool empty () const {return d->empty ();}
    size_type size () const {return d->size ();}

    iterator find (const K&);
    const_iterator find (const K&) const;

    iterator begin () {return iterator (d->begin ());}
    iterator end () {return iterator (d->end ());}

    const_iterator begin () const {return const_iterator (d->begin ());}
    const_iterator end () const {return const_iterator (d->end ());}

    // Implementation details.
    //
  public:
    explicit map_value (D& d): d (&d) {}

    map_value (const map_value&) = delete;
    map_value& operator= (const map_value&) = delete; // Rebind or deep?

    map_value (map_value&&) = default;
    map_value& operator= (map_value&&) = delete;

    D* d; // names
  };

  template <typename K, typename V>
  struct value_traits<std::map<K, V>>
  {
    using type = map_value<K, V, names>;
    using const_type = map_value<K, V, const names>;

    static type       as (value&);
    static const_type as (const value&);

    template <typename M> static void assign (value&, M);
    template <typename M> static void append (value&, M);

    static const std::string type_name;
    static const build::value_type value_type;
  };

  template <typename K, typename V, typename D>
  struct value_traits<map_value<K, V, D>>: value_traits<std::map<K, V>> {};
}

namespace std
{
  template <>
  struct hash<build::variable>: hash<string>
  {
    size_t
    operator() (const build::variable& v) const noexcept
    {
      return hash<string>::operator() (v.name);
    }
  };
}

namespace butl
{
  template <>
  struct compare_prefix<build::variable_cref>: compare_prefix<std::string>
  {
    typedef compare_prefix<std::string> base;

    explicit
    compare_prefix (char d): base (d) {}

    bool
    operator() (const build::variable& x, const build::variable& y) const
    {
      return base::operator() (x.name, y.name);
    }

    bool
    prefix (const build::variable& p, const build::variable& k) const
    {
      return base::prefix (p.name, k.name);
    }
  };
}

namespace build
{
  // variable_pool
  //
  using variable_pool_base = std::unordered_set<variable>;
  struct variable_pool: private variable_pool_base
  {
    const variable&
    find (string name, const build::value_type* t = nullptr, char p = '\0')
    {
      return find (name, nullptr, t, p);
    }

    const variable&
    find (string name,
          variable_visibility v,
          const build::value_type* t = nullptr,
          char p = '\0')
    {
      return find (name, &v, t, p);
    }

    using variable_pool_base::clear;

  private:
    const variable&
    find (string name,
          const variable_visibility* vv,
          const build::value_type* t,
          char p)
    {
      auto r (
        insert (
          variable {
            std::move (name),
            t,
            vv != nullptr ? *vv : variable_visibility::normal,
            p}));
      const variable& v (*r.first);

      // Update type?
      //
      if (!r.second && t != nullptr && v.type != t)
      {
        assert (v.type == nullptr);
        const_cast<variable&> (v).type = t; // Not changing the key.
      }

      // Change visibility? While this might at first seem like a bad idea,
      // it can happen that the variable lookup happens before any values
      // were set, in which case the variable will be entered with the
      // default visibility.
      //
      if (!r.second && vv != nullptr && v.visibility != *vv)
      {
        assert (v.visibility == variable_visibility::normal); // Default.
        const_cast<variable&> (v).visibility = *vv; // Not changing the key.
      }

      return v;
    }
  };

  extern variable_pool var_pool;

  // variable_map
  //
  struct variable_map
  {
    using map_type = butl::prefix_map<variable_cref, value, '.'>;
    using size_type = map_type::size_type;

    template <typename I>
    struct iterator_adapter: I
    {
      iterator_adapter () = default;
      iterator_adapter (const I& i): I (i) {}
      typename I::reference operator* () const;
      typename I::pointer operator-> () const;
    };

    using const_iterator = iterator_adapter<map_type::const_iterator>;

    const value*
    find (const variable& var) const
    {
      auto i (m_.find (var));
      const value* r (i != m_.end () ? &i->second : nullptr);

      // First access after being assigned a type?
      //
      if (r != nullptr && var.type != nullptr && r->type != var.type)
        build::assign (const_cast<value&> (*r), var.type, var);

      return  r;
    }

    value*
    find (const variable& var)
    {
      auto i (m_.find (var));
      value* r (i != m_.end () ? &i->second : nullptr);

      // First access after being assigned a type?
      //
      if (r != nullptr && var.type != nullptr && r->type != var.type)
        build::assign (*r, var.type, var);

      return  r;
    }

    lookup<const value>
    operator[] (const variable& var) const
    {
      return lookup<const value> (find (var), this);
    }

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

    // Non-const lookup. Only exposed on the map directly.
    //
    lookup<value>
    operator[] (const variable& var)
    {
      return lookup<value> (find (var), this);
    }

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

    // The second member in the pair indicates whether the new
    // value (which will be NULL) was assigned.
    //
    std::pair<std::reference_wrapper<value>, bool>
    assign (const variable& var)
    {
      auto r (m_.emplace (var, value (var.type)));
      value& v (r.first->second);

      // First access after being assigned a type?
      //
      if (!r.second && var.type != nullptr && v.type != var.type)
        build::assign (v, var.type, var);

      return std::make_pair (std::reference_wrapper<value> (v), r.second);
    }

    std::pair<std::reference_wrapper<value>, bool>
    assign (const std::string& name)
    {
      return assign (var_pool.find (name));
    }

    std::pair<const_iterator, const_iterator>
    find_namespace (const std::string& ns) const
    {
      auto r (m_.find_prefix (var_pool.find (ns)));
      return std::make_pair (const_iterator (r.first),
                             const_iterator (r.second));
    }

    const_iterator
    begin () const {return m_.begin ();}

    const_iterator
    end () const {return m_.end ();}

    bool
    empty () const {return m_.empty ();}

    size_type
    size () const {return m_.size ();}

  private:
    map_type m_;
  };

  // Target type/pattern-specific variables.
  //
  // @@ In quite a few places we assume that we can store a reference
  //    to the returned value (e.g., install::lookup_install()). If
  //    we "instantiate" the value on the fly, then we will need to
  //    consider its lifetime.
  //
  using variable_pattern_map = std::map<std::string, variable_map>;

  struct variable_type_map: std::map<std::reference_wrapper<const target_type>,
                                     variable_pattern_map>
  {
    build::lookup<const value>
    lookup (const target_type&, const string& name, const variable&) const;
  };
}

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

#endif // BUILD_VARIABLE