// file      : bbot/bootstrap-manifest.cxx -*- C++ -*-
// license   : MIT; see accompanying LICENSE file

#include <bbot/bootstrap-manifest.hxx>

#include <libbutl/manifest-parser.hxx>
#include <libbutl/manifest-serializer.hxx>

using namespace butl;

namespace bbot
{
  using parser = manifest_parser;
  using parsing = manifest_parsing;
  using serializer = manifest_serializer;
  using serialization = manifest_serialization;
  using name_value = manifest_name_value;

  // bootstrap_manifest
  //
  bootstrap_manifest::
  bootstrap_manifest (parser& p, bool iu)
      : bootstrap_manifest (p, p.next (), iu)
  {
    // Make sure this is the end.
    //
    name_value nv (p.next ());
    if (!nv.empty ())
      throw parsing (p.name (), nv.name_line, nv.name_column,
                     "single bootstrap manifest expected");
  }

  bootstrap_manifest::
  bootstrap_manifest (parser& p, name_value nv, bool iu)
  {
    auto bad_name = [&p, &nv] (const string& d)
    {
      throw parsing (p.name (), nv.name_line, nv.name_column, d);
    };

    auto bad_value = [&p, &nv] (const string& d)
    {
      throw parsing (p.name (), nv.value_line, nv.value_column, d);
    };

    // Make sure this is the start and we support the version.
    //
    if (!nv.name.empty ())
      bad_name ("start of bootstrap manifest expected");

    if (nv.value != "1")
      bad_value ("unsupported format version");

    // Parse the bootstrap manifest.
    //
    for (nv = p.next (); !nv.empty (); nv = p.next ())
    {
      string& n (nv.name);
      string& v (nv.value);

      size_t nn (n.size ()); // Name length.

      // Note: returns false if nothing preceeds a suffix.
      //
      auto suffix = [&n, nn] (const char* s, size_t sn) -> bool
      {
        return nn > sn && n.compare (nn - sn, sn, s) == 0;
      };

      size_t sn;
      if (suffix ("-version", sn = 8))
      {
        string pn (n, 0, nn - sn); // Package name.

        // Package version.
        //
        try
        {
          // Make sure the package version is not redefined.
          //
          if (!versions.emplace (move (pn), standard_version (v)).second)
            bad_name (n + " redefinition");
        }
        catch (const invalid_argument& e)
        {
          bad_value (string ("invalid package version: ") + e.what ());
        }
      }
      else if (!iu)
        bad_name ("unknown name '" + n + "' in bootstrap manifest");
    }

    // Verify all non-optional values were specified.
    //
    if (versions.empty ())
      bad_value ("no package versions specified");
  }

  void bootstrap_manifest::
  serialize (serializer& s) const
  {
    // @@ Should we check that all non-optional values are specified?
    //
    s.next ("", "1"); // Start of manifest.

    // Serialize *-version values.
    //
    for (const auto& v: versions)
      s.next (v.first + "-version", v.second.string_project ());

    s.next ("", ""); // End of manifest.
  }
}
