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

#ifndef BUILD_VARIABLE
#define BUILD_VARIABLE

#include <string>
#include <memory>        // unique_ptr
#include <cstddef>       // nullptr_t
#include <utility>       // move()
#include <cassert>
#include <functional>    // hash
#include <typeindex>
#include <unordered_set>

#include <build/path>
#include <build/name>
#include <build/prefix-map>

namespace build
{
  struct value;

  struct value_type
  {
    std::type_index id;
    value* (*const factory) ();
  };

  // variable
  //
  // The two variables are considered the same if they have the same name.
  //
  struct variable
  {
    explicit
    variable (std::string n): name (std::move (n)), type (nullptr) {}

    std::string name;
    const value_type* type; // If NULL, then this variable has no fixed type.
  };

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

  typedef std::reference_wrapper<const variable> variable_cref;

  // value
  //
  struct value;
  typedef std::unique_ptr<value> value_ptr;

  struct value
  {
  public:
    virtual value_ptr
    clone () const = 0;

    virtual bool
    compare (const value&) const = 0;

    virtual
    ~value () = default;
  };

  struct list_value: value, names
  {
  public:
    list_value (names d): names (std::move (d)) {}
    list_value (std::string d) {emplace_back (std::move (d));}
    list_value (dir_path d) {emplace_back (std::move (d));}

    virtual value_ptr
    clone () const {return value_ptr (new list_value (*this));}

    virtual bool
    compare (const value& v) const
    {
      const list_value* lv (dynamic_cast<const list_value*> (&v));
      return lv != nullptr && static_cast<const names&> (*this) == *lv;
    }
  };
  typedef std::unique_ptr<list_value> list_value_ptr;

  // value_proxy
  //
  // A variable can be undefined, null, or contain some actual value.
  //
  struct variable_map;

  struct value_proxy
  {
    bool
    defined () const {return p != nullptr;}

    bool
    null () const {return *p == nullptr;}

    explicit operator bool () const {return defined () && !null ();}
    explicit operator value_ptr& () const {return *p;}

    // Get interface. See available specializations below.
    //
    template <typename T>
    T
    as () const = delete;

    // Set interface.
    //
    const value_proxy&
    operator= (value_ptr) const;

    const value_proxy&
    operator= (const value_proxy&) const;

    const value_proxy&
    operator= (std::string) const;

    // Append enother simple name to list_value.
    //
    const value_proxy&
    operator+= (std::string) const;

    const value_proxy&
    operator= (dir_path) const;

    const value_proxy&
    operator= (nullptr_t) const;

    // Return true if this value belongs to the specified scope or target.
    //
    template <typename T>
    bool
    belongs (const T& x) const {return map == &x.vars;}

    // Implementation details.
    //
    const variable_map* map; // Variable map to which this value belongs.

    value_proxy (value_ptr* p, const variable_map* m): map (m), p (p) {}

  private:
    value_ptr* p;
  };

  template <>
  inline value& value_proxy::
  as<value&> () const {return **p;}

  template <>
  inline const value& value_proxy::
  as<const value&> () const {return **p;}

  template <>
  list_value& value_proxy::
  as<list_value&> () const;

  template <>
  inline const list_value& value_proxy::
  as<const list_value&> () const {return as<list_value&> ();}

  template <>
  const std::string& value_proxy::
  as<const std::string&> () const;

  template <>
  const dir_path& value_proxy::
  as<const dir_path&> () const;
}

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 build
{
  // variable_pool
  //
  struct variable_set: std::unordered_set<variable>
  {
    // @@ Need to check/set type?
    //
    const variable&
    find (std::string name) {return *emplace (std::move (name)).first;}
  };

  extern variable_set variable_pool;

  // variable_map
  //
  template <>
  struct compare_prefix<variable_cref>: compare_prefix<std::string>
  {
    typedef compare_prefix<std::string> base;

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

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

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

  struct variable_map: prefix_map<variable_cref, value_ptr, '.'>
  {
    typedef prefix_map<variable_cref, value_ptr, '.'> base;

    value_proxy
    operator[] (const variable& var) const
    {
      auto i (find (var));
      return i != end ()
        // @@ To do this properly we seem to need ro_value_proxy.
        //
        ? value_proxy (&const_cast<value_ptr&> (i->second), this)
        : value_proxy (nullptr, nullptr);
    }

    value_proxy
    operator[] (const std::string& name) const
    {
      return operator[] (variable_pool.find (name));
    }

    value_proxy
    assign (const variable& var)
    {
      return value_proxy (&base::operator[] (var), this);
    }

    value_proxy
    assign (const std::string& name)
    {
      return assign (variable_pool.find (name));
    }

    std::pair<iterator, iterator>
    find_namespace (const std::string& ns)
    {
      return find_prefix (variable_pool.find (ns));
    }

    std::pair<const_iterator, const_iterator>
    find_namespace (const std::string& ns) const
    {
      return find_prefix (variable_pool.find (ns));
    }
  };
}

#include <build/variable.ixx>

#endif // BUILD_VARIABLE