// file      : mod/mod-build-configs.cxx -*- C++ -*-
// license   : MIT; see accompanying LICENSE file

#include <mod/mod-build-configs.hxx>

#include <algorithm> // replace()

#include <libstudxml/serializer.hxx>

#include <web/server/module.hxx>

#include <web/xhtml/serialization.hxx>

#include <mod/page.hxx>
#include <mod/module-options.hxx>

using namespace std;
using namespace bbot;
using namespace brep::cli;

// While currently the user-defined copy constructor is not required (we don't
// need to deep copy nullptr's), it is a good idea to keep the placeholder
// ready for less trivial cases.
//
brep::build_configs::
build_configs (const build_configs& r)
    : handler (r),
      build_config_module (r),
      options_ (r.initialized_ ? r.options_ : nullptr)
{
}

void brep::build_configs::
init (scanner& s)
{
  HANDLER_DIAG;

  options_ = make_shared<options::build_configs> (
    s, unknown_mode::fail, unknown_mode::fail);

  if (options_->build_config_specified ())
    build_config_module::init (*options_);
}

bool brep::build_configs::
handle (request& rq, response& rs)
{
  using namespace web::xhtml;

  HANDLER_DIAG;

  if (build_conf_ == nullptr)
    throw invalid_request (501, "not implemented");

  const size_t page_configs (options_->build_config_page_entries ());
  const dir_path& root (options_->root ());

  params::build_configs params;

  try
  {
    name_value_scanner s (rq.parameters (1024));
    params = params::build_configs (s, unknown_mode::fail, unknown_mode::fail);

    // We accept the non-url-encoded class name. Note that the parameter is
    // already url-decoded by the web server, so we just restore the space
    // character (that is otherwise forbidden in a class name) to the plus
    // character.
    //
    string& cn (params.class_name ());
    replace (cn.begin (), cn.end (), ' ', '+');
  }
  catch (const cli::exception& e)
  {
    throw invalid_request (400, e.what ());
  }

  size_t page (params.page ());

  const char* title ("Build Configurations");
  xml::serializer s (rs.content (), title);

  s << HTML
    <<   HEAD
    <<     TITLE << title << ~TITLE
    <<     CSS_LINKS (path ("build-configs.css"), root)
    <<   ~HEAD
    <<   BODY
    <<     DIV_HEADER (options_->logo (), options_->menu (), root, tenant)
    <<     DIV(ID="content");

  auto url = [&root] (const string& cls)
  {
    string r (root.string () + "?build-configs");

    if (cls != "all")
    {
      r += '=';

      // Note that '+' is the only class name character that potentially needs
      // to be url-encoded, and only in the query part of the URL. However, we
      // embed the class name into the URL query part, where it is not encoded
      // by design (see above).
      //
      r += cls;
    }

    return r;
  };

  auto print_class_name = [&s, &url] (const string& c, bool sel = false)
  {
    if (sel)
      s << SPAN(ID="selected-class", CLASS="class-name") << c << ~SPAN;
    else
      s << A(CLASS="class-name") << HREF << url (c) << ~HREF << c << ~A;
  };

  // Print the configuration filter on the first page only.
  //
  if (params.page () == 0)
  {
    const strings& cls (build_conf_->classes);
    const map<string, string>& im (build_conf_->class_inheritance_map);

    s << DIV(ID="filter-heading") << "Build Configuration Classes" << ~DIV
      << P(ID="filter");

    for (auto b (cls.begin ()), i (b), e (cls.end ()); i != e; ++i)
    {
      if (i != b)
        s << ' ';

      const string& c (*i);
      print_class_name (c, c == params.class_name ());

      // Append the base class, if present.
      //
      auto j (im.find (c));
      if (j != im.end ())
      {
        s << ':';
        print_class_name (j->second);
      }
    }

    s << ~P;
  }

  // Print build configurations that belong to the selected class.
  //
  // We will calculate the total configuration count and cache configurations
  // for printing (skipping an appropriate number of them for page number
  // greater than one) on the same pass. Note that we need to print the count
  // before printing the configurations.
  //
  size_t count (0);
  vector<const build_config*> configs;
  configs.reserve (page_configs);

  size_t skip (page * page_configs);
  size_t print (page_configs);
  for (const build_config& c: *build_conf_)
  {
    if (belongs (c, params.class_name ()))
    {
      if (skip != 0)
        --skip;
      else if (print != 0)
      {
        configs.emplace_back (&c);
        --print;
      }

      ++count;
    }
  }

  // Print configuration count.
  //
  s << DIV_COUNTER (count, "Build Configuration", title);

  // Finally, print the cached build configurations.
  //
  // Enclose the subsequent tables to be able to use nth-child CSS selector.
  //
  s << DIV;
  for (const build_config* c: configs)
  {
    s << TABLE(CLASS="proplist config")
      <<   TBODY
      <<     TR_VALUE ("name", c->name)
      <<     TR_VALUE ("target", c->target.string ())
      <<     TR(CLASS="classes")
      <<       TH << "classes" << ~TH
      <<       TD
      <<         SPAN(CLASS="value");

    const strings& cls (c->classes);
    for (auto b (cls.begin ()), i (b), e (cls.end ()); i != e; ++i)
    {
      if (i != b)
        s << ' ';

      print_class_name (*i);
    }

    s <<         ~SPAN
      <<       ~TD
      <<     ~TR
      <<   ~TBODY
      << ~TABLE;
  }
  s << ~DIV;

  s <<       DIV_PAGER (page,
                        count,
                        page_configs,
                        options_->build_config_pages (),
                        url (params.class_name ()))
    <<     ~DIV
    <<   ~BODY
    << ~HTML;

  return true;
}