// file      : libbuild2/bin/target.cxx -*- C++ -*-
// license   : MIT; see accompanying LICENSE file

#include <libbuild2/bin/target.hxx>

#include <libbuild2/context.hxx>

using namespace std;

namespace build2
{
  namespace bin
  {
    const target_type objx::static_type
    {
      "objx",
      &file::static_type,
      nullptr,
      nullptr,
      nullptr,
      nullptr,
      nullptr,
      &target_search,
      target_type::flag::none
    };

    const target_type bmix::static_type
    {
      "bmix",
      &file::static_type,
      nullptr,
      nullptr,
      nullptr,
      nullptr,
      nullptr,
      &target_search,
      target_type::flag::none
    };

    const target_type hbmix::static_type
    {
      "hbmix",
      &bmix::static_type,
      nullptr,
      nullptr,
      nullptr,
      nullptr,
      nullptr,
      &target_search,
      target_type::flag::none
    };

    const target_type libx::static_type
    {
      "libx",
      &mtime_target::static_type,
      nullptr,
      nullptr,
      nullptr,
      nullptr,
      nullptr,
      &target_search,
      target_type::flag::member_hint // Use untyped hint for group members.
    };

    const target_type libux::static_type
    {
      "libux",
      &file::static_type,
      nullptr,
      nullptr,
      nullptr,
      nullptr,
      nullptr,
      &target_search,
      target_type::flag::none
    };

    // Note that we link groups during the load phase since this is often
    // relied upon when setting target-specific variables (e.g., we may set a
    // common value for lib{} and then append liba/libs-specific values to
    // it). While sure inelegant, this is MT-safe since during load we are
    // running serial. For the members it is also safe to set the group during
    // creation.

    // obj*{}, lib*{}, and [h]bmi*{} member factory.
    //
    template <typename M, typename G>
    static target*
    m_factory (context& ctx,
               const target_type&, dir_path dir, dir_path out, string n)
    {
      const G* g (ctx.targets.find<G> (dir, out, n));

      M* m (new M (ctx, move (dir), move (out), move (n)));
      m->group = g;

      return m;
    }

    const target_type obje::static_type
    {
      "obje",
      &objx::static_type,
      &m_factory<obje, obj>,
      nullptr, /* fixed_extension */
      &target_extension_var<nullptr>,
      &target_pattern_var<nullptr>,
      nullptr,
      &target_search, // Note: not _file(); don't look for an existing file.
      target_type::flag::none
    };

    const target_type bmie::static_type
    {
      "bmie",
      &bmix::static_type,
      &m_factory<bmie, bmi>,
      nullptr, /* fixed_extension */
      &target_extension_var<nullptr>,
      &target_pattern_var<nullptr>,
      nullptr,
      &target_search, // Note: not _file(); don't look for an existing file.
      target_type::flag::none
    };

    const target_type hbmie::static_type
    {
      "hbmie",
      &hbmix::static_type,
      &m_factory<hbmie, hbmi>,
      nullptr, /* fixed_extension */
      &target_extension_var<nullptr>,
      &target_pattern_var<nullptr>,
      nullptr,
      &target_search, // Note: not _file(); don't look for an existing file.
      target_type::flag::none
    };

    const target_type obja::static_type
    {
      "obja",
      &objx::static_type,
      &m_factory<obja, obj>,
      nullptr, /* fixed_extension */
      &target_extension_var<nullptr>,
      &target_pattern_var<nullptr>,
      nullptr,
      &target_search, // Note: not _file(); don't look for an existing file.
      target_type::flag::none
    };

    const target_type bmia::static_type
    {
      "bmia",
      &bmix::static_type,
      &m_factory<bmia, bmi>,
      nullptr, /* fixed_extension */
      &target_extension_var<nullptr>,
      &target_pattern_var<nullptr>,
      nullptr,
      &target_search, // Note: not _file(); don't look for an existing file.
      target_type::flag::none
    };

    const target_type hbmia::static_type
    {
      "hbmia",
      &hbmix::static_type,
      &m_factory<hbmia, hbmi>,
      nullptr, /* fixed_extension */
      &target_extension_var<nullptr>,
      &target_pattern_var<nullptr>,
      nullptr,
      &target_search, // Note: not _file(); don't look for an existing file.
      target_type::flag::none
    };

    const target_type objs::static_type
    {
      "objs",
      &objx::static_type,
      &m_factory<objs, obj>,
      nullptr, /* fixed_extension */
      &target_extension_var<nullptr>,
      &target_pattern_var<nullptr>,
      nullptr,
      &target_search, // Note: not _file(); don't look for an existing file.
      target_type::flag::none
    };

    const target_type bmis::static_type
    {
      "bmis",
      &bmix::static_type,
      &m_factory<bmis, bmi>,
      nullptr, /* fixed_extension */
      &target_extension_var<nullptr>,
      &target_pattern_var<nullptr>,
      nullptr,
      &target_search, // Note: not _file(); don't look for an existing file.
      target_type::flag::none
    };

    const target_type hbmis::static_type
    {
      "hbmis",
      &hbmix::static_type,
      &m_factory<hbmis, hbmi>,
      nullptr, /* fixed_extension */
      &target_extension_var<nullptr>,
      &target_pattern_var<nullptr>,
      nullptr,
      &target_search, // Note: not _file(); don't look for an existing file.
      target_type::flag::none
    };

    const target_type libue::static_type
    {
      "libue",
      &libux::static_type,
      &target_factory<libue>,
      nullptr, /* fixed_extension */
      &target_extension_var<nullptr>,
      &target_pattern_var<nullptr>,
      nullptr,
      &target_search, // Note: not _file(); don't look for an existing file.
      target_type::flag::none
    };

    const target_type libua::static_type
    {
      "libua",
      &libux::static_type,
      &m_factory<libua, libul>,
      nullptr, /* fixed_extension */
      &target_extension_var<nullptr>,
      &target_pattern_var<nullptr>,
      nullptr,
      &target_search, // Note: not _file(); don't look for an existing file.
      target_type::flag::none
    };

    const target_type libus::static_type
    {
      "libus",
      &libux::static_type,
      &m_factory<libus, libul>,
      nullptr, /* fixed_extension */
      &target_extension_var<nullptr>,
      &target_pattern_var<nullptr>,
      nullptr,
      &target_search, // Note: not _file(); don't look for an existing file.
      target_type::flag::none
    };

    // obj{}, [h]bmi{}, and libu{} group factory.
    //
    template <typename G, typename E, typename A, typename S>
    static target*
    g_factory (context& ctx,
               const target_type&, dir_path dir, dir_path out, string n)
    {
      // Casts are MT-aware (during serial load).
      //
      E* e (ctx.phase == run_phase::load
            ? const_cast<E*> (ctx.targets.find<E> (dir, out, n))
            : nullptr);
      A* a (ctx.phase == run_phase::load
            ? const_cast<A*> (ctx.targets.find<A> (dir, out, n))
            : nullptr);
      S* s (ctx.phase == run_phase::load
            ? const_cast<S*> (ctx.targets.find<S> (dir, out, n))
            : nullptr);

      G* g (new G (ctx, move (dir), move (out), move (n)));

      if (e != nullptr) e->group = g;
      if (a != nullptr) a->group = g;
      if (s != nullptr) s->group = g;

      return g;
    }

    const target_type obj::static_type
    {
      "obj",
      &target::static_type,
      &g_factory<obj, obje, obja, objs>,
      nullptr,
      nullptr,
      nullptr,
      nullptr,
      &target_search,
      target_type::flag::member_hint // Use untyped hint for group members.
    };

    const target_type bmi::static_type
    {
      "bmi",
      &target::static_type,
      &g_factory<bmi, bmie, bmia, bmis>,
      nullptr,
      nullptr,
      nullptr,
      nullptr,
      &target_search,
      target_type::flag::member_hint // Use untyped hint for group members.
    };

    const target_type hbmi::static_type
    {
      "hbmi",
      &target::static_type,
      &g_factory<hbmi, hbmie, hbmia, hbmis>,
      nullptr,
      nullptr,
      nullptr,
      nullptr,
      &target_search,
      target_type::flag::member_hint // Use untyped hint for group members.
    };

    // The same as g_factory() but without E.
    //
    static target*
    libul_factory (context& ctx,
                   const target_type&, dir_path dir, dir_path out, string n)
    {
      libua* a (ctx.phase == run_phase::load
                ? const_cast<libua*> (ctx.targets.find<libua> (dir, out, n))
                : nullptr);
      libus* s (ctx.phase == run_phase::load
                ? const_cast<libus*> (ctx.targets.find<libus> (dir, out, n))
                : nullptr);

      libul* g (new libul (ctx, move (dir), move (out), move (n)));

      if (a != nullptr) a->group = g;
      if (s != nullptr) s->group = g;

      return g;
    }

    const target_type libul::static_type
    {
      "libul",
      &libx::static_type,
      &libul_factory,
      nullptr,
      nullptr,
      nullptr,
      nullptr,
      &target_search,
      target_type::flag::member_hint // Use untyped hint for group members.
    };

    // What extensions should we use? At the outset, this is platform-
    // dependent. And if we consider cross-compilation, is it build or
    // host-dependent? Feels like it should be host-dependent so that
    // we can copy things between cross and native environments. So
    // these will have to be determined based on what we are building.
    // As if this is not complicated enough, the bin module doesn't
    // know anything about building. So perhaps the extension should
    // come from a variable that is set not by bin but by the module
    // whose rule matched the target (e.g., cxx::link).
    //
    const target_type liba::static_type
    {
      "liba",
      &file::static_type,
      &m_factory<liba, lib>,
      nullptr, /* fixed_extension */
      &target_extension_var<nullptr>,
      &target_pattern_var<nullptr>,
      nullptr,
      &file_search,
      target_type::flag::none
    };

    const target_type libs::static_type
    {
      "libs",
      &file::static_type,
      &m_factory<libs, lib>,
      nullptr, /* fixed_extension */
      &target_extension_var<nullptr>,
      &target_pattern_var<nullptr>,
      nullptr,
      &file_search,
      target_type::flag::none
    };

    // lib
    //
    group_view lib::
    group_members (action) const
    {
      static_assert (sizeof (lib_members) == sizeof (const target*) * 2,
                     "member layout incompatible with array");

      return a != nullptr || s != nullptr
        ? group_view {reinterpret_cast<const target* const*> (&a), 2}
        : group_view {nullptr, 0};
    }

    static target*
    lib_factory (context& ctx,
                 const target_type&, dir_path dir, dir_path out, string n)
    {
      // Casts are MT-aware (during serial load).
      //
      liba* a (ctx.phase == run_phase::load
               ? const_cast<liba*> (ctx.targets.find<liba> (dir, out, n))
               : nullptr);
      libs* s (ctx.phase == run_phase::load
               ? const_cast<libs*> (ctx.targets.find<libs> (dir, out, n))
               : nullptr);

      lib* l (new lib (ctx, move (dir), move (out), move (n)));

      if (a != nullptr) a->group = l;
      if (s != nullptr) s->group = l;

      return l;
    }

    const target_type lib::static_type
    {
      "lib",
      &libx::static_type,
      &lib_factory,
      nullptr,
      nullptr,
      nullptr,
      nullptr,
      &target_search,

      // Note: not see-through ("alternatives" group).
      //
      target_type::flag::member_hint // Use untyped hint for group members.
    };

    // libi
    //
    const target_type libi::static_type
    {
      "libi",
      &file::static_type,
      &target_factory<libi>,
      nullptr, /* fixed_extension */
      &target_extension_var<nullptr>,
      &target_pattern_var<nullptr>,
      nullptr,
      &file_search,
      target_type::flag::none
    };

    // def
    //
    extern const char def_ext[] = "def"; // VC14 rejects constexpr.

    const target_type def::static_type
    {
      "def",
      &file::static_type,
      &target_factory<def>,
      &target_extension_fix<def_ext>,
      nullptr,                          /* default_extension */
      &target_pattern_fix<def_ext>,
      nullptr,
      &file_search,
      target_type::flag::none
    };
  }
}