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

#ifndef BUILD_TARGET
#define BUILD_TARGET

#include <map>
#include <string>
#include <vector>
#include <memory>     // unique_ptr
#include <functional> // function, reference_wrapper
#include <typeindex>
#include <ostream>
#include <cassert>
#include <utility>    // move()

#include <build/path>
#include <build/key-set>
#include <build/timestamp>
#include <build/prerequisite>
#include <build/utility>       // compare_*, extension_pool

namespace build
{
  class target;

  enum class target_state {unknown, uptodate, updated, failed};

  // Note: should throw rather than returning target_state::failed.
  //
  typedef std::function<target_state (target&)> recipe;

  struct target_type
  {
    std::type_index id;
    const char* name;
    const target_type* base;
    target* (*const factory) (path, std::string, const std::string*);
    target* (*const search) (prerequisite&);
  };

  inline std::ostream&
  operator<< (std::ostream& os, const target_type& tt)
  {
    return os << tt.name;
  }

  class target
  {
  public:
    virtual
    ~target () = default;

    target (path d, std::string n, const std::string* e)
        : dir (std::move (d)), name (std::move (n)), ext (e) {}

    const path dir;         // Absolute and normalized.
    const std::string name;
    const std::string* ext; // Extension, NULL means unspecified.

  public:
    typedef
    std::vector<std::reference_wrapper<prerequisite>>
    prerequisites_type;

    prerequisites_type prerequisites;

  public:
    typedef build::recipe recipe_type;

    const recipe_type&
    recipe () const {return recipe_;}

    void
    recipe (recipe_type r) {assert (!recipe_); recipe_ = r;}

  public:
    target_state
    state () const {return state_;}

    void
    state (target_state s) {state_ = s;}

  private:
    target (const target&) = delete;
    target& operator= (const target&) = delete;

  public:
    virtual const target_type& type () const = 0;
    static const target_type static_type;

  private:
    recipe_type recipe_;
    target_state state_ {target_state::unknown};
  };

  std::ostream&
  operator<< (std::ostream&, const target&);

  struct target_set
  {
    struct key
    {
      mutable const std::type_index* type;
      mutable const path* dir;
      mutable const std::string* name;
      mutable const std::string** ext;

      friend bool
      operator< (const key& x, const key& y)
      {
        //@@ TODO: use compare() to compare once.

        // Unspecified and specified extension are assumed equal. The
        // extension strings are from the pool, so we can just compare
        // pointers.
        //
        return
          (*x.type < *y.type) ||
          (*x.type == *y.type && *x.name < *y.name) ||
          (*x.type == *y.type && *x.name == *y.name && *x.dir < *y.dir) ||
          (*x.type == *y.type && *x.name == *y.name && *x.dir == *y.dir &&
           *x.ext != nullptr && *y.ext != nullptr && **x.ext < **y.ext);
      }
    };

    typedef std::map<key, std::unique_ptr<target>> map;
    typedef map_iterator_adapter<map::const_iterator> iterator;

    iterator
    find (const key& k, tracer& trace) const;

    iterator
    find (const std::type_index& type,
          const path& dir,
          const std::string& name,
          const std::string*& ext,
          tracer& trace) const
    {
      return find (key {&type, &dir, &name, &ext}, trace);
    }

    iterator begin () const {return map_.begin ();}
    iterator end () const {return map_.end ();}

    std::pair<target&, bool>
    insert (const target_type&,
            path dir,
            std::string name,
            const std::string* ext,
            tracer&);

  private:
    map map_;
  };

  extern target_set targets;
  extern target* default_target;

  class target_type_map: public std::map<
    const char*,
    std::reference_wrapper<const target_type>,
    compare_c_string>
  {
  public:
    void
    insert (const target_type& tt) {emplace (tt.name, tt);}
  };

  extern target_type_map target_types;

  template <typename T>
  target*
  target_factory (path d, std::string n, const std::string* e)
  {
    return new T (std::move (d), std::move (n), e);
  }

  // Modification time-based target.
  //
  class mtime_target: public target
  {
  public:
    using target::target;

    timestamp
    mtime () const
    {
      if (mtime_ == timestamp_unknown)
        mtime_ = load_mtime ();

      return mtime_;
    }

    void
    mtime (timestamp mt) {mtime_ = mt;}

  protected:
    virtual timestamp
    load_mtime () const = 0;

  public:
    static const target_type static_type;

  private:
    mutable timestamp mtime_ {timestamp_unknown};
  };

  // Filesystem path-based target.
  //
  class path_target: public mtime_target
  {
  public:
    using mtime_target::mtime_target;

    typedef build::path path_type;

    const path_type&
    path () const {return path_;}

    void
    path (path_type p) {assert (path_.empty ()); path_ = std::move (p);}

  protected:
    virtual timestamp
    load_mtime () const;

  public:
    static const target_type static_type;

  private:
    path_type path_;
  };

  // File target.
  //
  class file: public path_target
  {
  public:
    using path_target::path_target;

  public:
    virtual const target_type& type () const {return static_type;}
    static const target_type static_type;
  };

  // Directory alias/action target. Note that it is not mtime-based.
  // Rather it is meant to represent a group of targets. For actual
  // filesystem directory (creation), see fsdir.
  //
  class dir: public target
  {
  public:
    using target::target;

  public:
    virtual const target_type& type () const {return static_type;}
    static const target_type static_type;
  };

  // While a filesystem directory is mtime-based, the semantics is
  // not very useful in our case. In particular, if another target
  // depends on fsdir{}, then all that's desired is the creation of
  // the directory if it doesn't already exist. In particular, we
  // don't want to update the target just because some unrelated
  // entry was created in that directory.
  //
  class fsdir: public target
  {
  public:
    using target::target;

  public:
    virtual const target_type& type () const {return static_type;}
    static const target_type static_type;
  };
}

#endif // BUILD_TARGET