/*
 * This file is part of libmodulemd
 * Copyright (C) 2020 Red Hat, Inc.
 *
 * Fedora-License-Identifier: MIT
 * SPDX-2.0-License-Identifier: MIT
 * SPDX-3.0-License-Identifier: MIT
 *
 * This program is free software.
 * For more information on the license, see COPYING.
 * For more information on free software, see <https://www.gnu.org/philosophy/free-sw.en.html>.
 */

#include "private/modulemd-build-config-private.h"
#include "private/modulemd-component-private.h"
#include "private/modulemd-component-module-private.h"
#include "private/modulemd-component-rpm-private.h"
#include "private/modulemd-defaults-v1-private.h"
#include "private/modulemd-module-stream-private.h"
#include "private/modulemd-packager-v3-private.h"
#include "private/modulemd-profile-private.h"
#include "private/modulemd-subdocument-info-private.h"
#include "private/modulemd-util.h"
#include "private/modulemd-yaml.h"

struct _ModulemdPackagerV3
{
  GObject parent_instance;

  gchar *module_name;
  gchar *stream_name;
  gchar *summary;
  gchar *description;
  GHashTable *module_licenses; /* string set */
  GVariant *xmd;
  GHashTable *build_configs; /* <string, Modulemd.BuildConfig> */
  gchar *community;
  gchar *documentation;
  gchar *tracker;
  GHashTable *profiles; /* <string, Modulemd.Profile> */
  GHashTable *rpm_api; /* string set */
  GHashTable *rpm_filters; /* string set */
  GHashTable *demodularized_rpms; /* string set */
  GHashTable *rpm_components; /* <string, Modulemd.ComponentRpm> */
  GHashTable *module_components; /* <string, Modulemd.ComponentModule */
};

G_DEFINE_TYPE (ModulemdPackagerV3, modulemd_packager_v3, G_TYPE_OBJECT)

ModulemdPackagerV3 *
modulemd_packager_v3_new (void)
{
  return g_object_new (MODULEMD_TYPE_PACKAGER_V3, NULL);
}

static void
modulemd_packager_v3_finalize (GObject *object)
{
  ModulemdPackagerV3 *self = (ModulemdPackagerV3 *)object;

  g_clear_pointer (&self->module_name, g_free);
  g_clear_pointer (&self->stream_name, g_free);
  g_clear_pointer (&self->summary, g_free);
  g_clear_pointer (&self->description, g_free);
  g_clear_pointer (&self->module_licenses, g_hash_table_unref);
  g_clear_pointer (&self->xmd, g_variant_unref);
  g_clear_pointer (&self->build_configs, g_hash_table_unref);
  g_clear_pointer (&self->community, g_free);
  g_clear_pointer (&self->documentation, g_free);
  g_clear_pointer (&self->tracker, g_free);
  g_clear_pointer (&self->profiles, g_hash_table_unref);
  g_clear_pointer (&self->rpm_api, g_hash_table_unref);
  g_clear_pointer (&self->rpm_filters, g_hash_table_unref);
  g_clear_pointer (&self->demodularized_rpms, g_hash_table_unref);
  g_clear_pointer (&self->rpm_components, g_hash_table_unref);
  g_clear_pointer (&self->module_components, g_hash_table_unref);

  G_OBJECT_CLASS (modulemd_packager_v3_parent_class)->finalize (object);
}

static void
modulemd_packager_v3_class_init (ModulemdPackagerV3Class *klass)
{
  GObjectClass *object_class = G_OBJECT_CLASS (klass);

  object_class->finalize = modulemd_packager_v3_finalize;
}

static void
modulemd_packager_v3_init (ModulemdPackagerV3 *self)
{
  self->module_licenses =
    g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);

  self->build_configs =
    g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);

  self->profiles =
    g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);

  self->rpm_api =
    g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);

  self->rpm_filters =
    g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);

  self->demodularized_rpms =
    g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);

  self->rpm_components =
    g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);

  self->module_components =
    g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
}


ModulemdPackagerV3 *
modulemd_packager_v3_copy (ModulemdPackagerV3 *self)
{
  g_autoptr (ModulemdPackagerV3) copy = modulemd_packager_v3_new ();

  modulemd_packager_v3_set_module_name (
    copy, modulemd_packager_v3_get_module_name (self));

  modulemd_packager_v3_set_stream_name (
    copy, modulemd_packager_v3_get_stream_name (self));

  modulemd_packager_v3_set_summary (copy,
                                    modulemd_packager_v3_get_summary (self));

  modulemd_packager_v3_set_description (
    copy, modulemd_packager_v3_get_description (self));

  MODULEMD_REPLACE_SET (copy->module_licenses, self->module_licenses);

  modulemd_packager_v3_set_xmd (copy, modulemd_packager_v3_get_xmd (self));

  COPY_HASHTABLE_BY_VALUE_ADDER (
    copy, self, build_configs, modulemd_packager_v3_add_build_config);

  modulemd_packager_v3_set_community (
    copy, modulemd_packager_v3_get_community (self));

  modulemd_packager_v3_set_documentation (
    copy, modulemd_packager_v3_get_documentation (self));

  modulemd_packager_v3_set_tracker (copy,
                                    modulemd_packager_v3_get_tracker (self));

  COPY_HASHTABLE_BY_VALUE_ADDER (
    copy, self, profiles, modulemd_packager_v3_add_profile);

  modulemd_packager_v3_replace_rpm_api (copy, self->rpm_api);

  modulemd_packager_v3_replace_rpm_filters (copy, self->rpm_filters);

  modulemd_packager_v3_replace_demodularized_rpms (copy,
                                                   self->demodularized_rpms);

  COPY_HASHTABLE_BY_VALUE_ADDER (
    copy, self, rpm_components, modulemd_packager_v3_add_component);

  COPY_HASHTABLE_BY_VALUE_ADDER (
    copy, self, module_components, modulemd_packager_v3_add_component);

  return g_steal_pointer (&copy);
}


void
modulemd_packager_v3_set_module_name (ModulemdPackagerV3 *self,
                                      const gchar *module_name)
{
  g_return_if_fail (MODULEMD_IS_PACKAGER_V3 (self));

  g_clear_pointer (&self->module_name, g_free);

  if (module_name)
    {
      self->module_name = g_strdup (module_name);
    }
}


const gchar *
modulemd_packager_v3_get_module_name (ModulemdPackagerV3 *self)
{
  g_return_val_if_fail (MODULEMD_IS_PACKAGER_V3 (self), NULL);

  return self->module_name;
}


void
modulemd_packager_v3_set_stream_name (ModulemdPackagerV3 *self,
                                      const gchar *stream_name)
{
  g_return_if_fail (MODULEMD_IS_PACKAGER_V3 (self));

  g_clear_pointer (&self->stream_name, g_free);

  if (stream_name)
    {
      self->stream_name = g_strdup (stream_name);
    }
}


const gchar *
modulemd_packager_v3_get_stream_name (ModulemdPackagerV3 *self)
{
  g_return_val_if_fail (MODULEMD_IS_PACKAGER_V3 (self), NULL);

  return self->stream_name;
}


void
modulemd_packager_v3_set_summary (ModulemdPackagerV3 *self,
                                  const gchar *summary)
{
  g_return_if_fail (MODULEMD_IS_PACKAGER_V3 (self));

  g_clear_pointer (&self->summary, g_free);

  if (summary)
    {
      self->summary = g_strdup (summary);
    }
}


const gchar *
modulemd_packager_v3_get_summary (ModulemdPackagerV3 *self)
{
  g_return_val_if_fail (MODULEMD_IS_PACKAGER_V3 (self), NULL);

  return self->summary;
}


void
modulemd_packager_v3_set_description (ModulemdPackagerV3 *self,
                                      const gchar *description)
{
  g_return_if_fail (MODULEMD_IS_PACKAGER_V3 (self));

  g_clear_pointer (&self->description, g_free);

  if (description)
    {
      self->description = g_strdup (description);
    }
}


const gchar *
modulemd_packager_v3_get_description (ModulemdPackagerV3 *self)
{
  g_return_val_if_fail (MODULEMD_IS_PACKAGER_V3 (self), NULL);

  return self->description;
}


void
modulemd_packager_v3_add_module_license (ModulemdPackagerV3 *self,
                                         const gchar *license)
{
  g_return_if_fail (MODULEMD_IS_PACKAGER_V3 (self));

  if (!license)
    {
      return;
    }

  g_hash_table_add (self->module_licenses, g_strdup (license));
}


void
modulemd_packager_v3_remove_module_license (ModulemdPackagerV3 *self,
                                            const gchar *license)
{
  g_return_if_fail (MODULEMD_IS_PACKAGER_V3 (self));

  if (!license)
    {
      return;
    }

  g_hash_table_remove (self->module_licenses, license);
}


void
modulemd_packager_v3_clear_module_licenses (ModulemdPackagerV3 *self)
{
  g_return_if_fail (MODULEMD_IS_PACKAGER_V3 (self));

  g_hash_table_remove_all (self->module_licenses);
}


GStrv
modulemd_packager_v3_get_module_licenses_as_strv (ModulemdPackagerV3 *self)
{
  g_return_val_if_fail (MODULEMD_IS_PACKAGER_V3 (self), NULL);

  return modulemd_ordered_str_keys_as_strv (self->module_licenses);
}


void
modulemd_packager_v3_set_xmd (ModulemdPackagerV3 *self, GVariant *xmd)
{
  g_return_if_fail (MODULEMD_IS_PACKAGER_V3 (self));

  /* Do nothing if we were passed the same pointer */
  if (self->xmd == xmd)
    {
      return;
    }

  g_clear_pointer (&self->xmd, g_variant_unref);
  self->xmd = modulemd_variant_deep_copy (xmd);
}

GVariant *
modulemd_packager_v3_get_xmd (ModulemdPackagerV3 *self)
{
  g_return_val_if_fail (MODULEMD_IS_PACKAGER_V3 (self), NULL);
  return self->xmd;
}


guint64
modulemd_packager_v3_get_mdversion (ModulemdPackagerV3 *UNUSED (self))
{
  return MD_PACKAGER_VERSION_THREE;
}


void
modulemd_packager_v3_add_build_config (ModulemdPackagerV3 *self,
                                       ModulemdBuildConfig *buildconfig)
{
  if (!buildconfig)
    return;

  g_return_if_fail (MODULEMD_IS_PACKAGER_V3 (self));
  g_return_if_fail (MODULEMD_IS_BUILD_CONFIG (buildconfig));

  g_hash_table_replace (
    self->build_configs,
    g_strdup (modulemd_build_config_get_context (buildconfig)),
    modulemd_build_config_copy (buildconfig));
}


void
modulemd_packager_v3_clear_build_configs (ModulemdPackagerV3 *self)
{
  g_return_if_fail (MODULEMD_IS_PACKAGER_V3 (self));
  g_hash_table_remove_all (self->build_configs);
}


GStrv
modulemd_packager_v3_get_build_config_contexts_as_strv (
  ModulemdPackagerV3 *self)
{
  g_return_val_if_fail (MODULEMD_IS_PACKAGER_V3 (self), NULL);

  return modulemd_ordered_str_keys_as_strv (self->build_configs);
}


ModulemdBuildConfig *
modulemd_packager_v3_get_build_config (ModulemdPackagerV3 *self,
                                       const gchar *context)
{
  g_return_val_if_fail (MODULEMD_IS_PACKAGER_V3 (self), NULL);

  return g_hash_table_lookup (self->build_configs, context);
}

void
modulemd_packager_v3_set_community (ModulemdPackagerV3 *self,
                                    const gchar *community)
{
  g_return_if_fail (MODULEMD_IS_PACKAGER_V3 (self));

  g_clear_pointer (&self->community, g_free);

  if (community)
    {
      self->community = g_strdup (community);
    }
}


const gchar *
modulemd_packager_v3_get_community (ModulemdPackagerV3 *self)
{
  g_return_val_if_fail (MODULEMD_IS_PACKAGER_V3 (self), NULL);

  return self->community;
}


void
modulemd_packager_v3_set_documentation (ModulemdPackagerV3 *self,
                                        const gchar *documentation)
{
  g_return_if_fail (MODULEMD_IS_PACKAGER_V3 (self));

  g_clear_pointer (&self->documentation, g_free);

  if (documentation)
    {
      self->documentation = g_strdup (documentation);
    }
}


const gchar *
modulemd_packager_v3_get_documentation (ModulemdPackagerV3 *self)
{
  g_return_val_if_fail (MODULEMD_IS_PACKAGER_V3 (self), NULL);

  return self->documentation;
}


void
modulemd_packager_v3_set_tracker (ModulemdPackagerV3 *self,
                                  const gchar *tracker)
{
  g_return_if_fail (MODULEMD_IS_PACKAGER_V3 (self));

  g_clear_pointer (&self->tracker, g_free);

  if (tracker)
    {
      self->tracker = g_strdup (tracker);
    }
}


const gchar *
modulemd_packager_v3_get_tracker (ModulemdPackagerV3 *self)
{
  g_return_val_if_fail (MODULEMD_IS_PACKAGER_V3 (self), NULL);

  return self->tracker;
}


void
modulemd_packager_v3_add_profile (ModulemdPackagerV3 *self,
                                  ModulemdProfile *profile)
{
  if (!profile)
    {
      return;
    }
  g_return_if_fail (MODULEMD_IS_PACKAGER_V3 (self));
  g_return_if_fail (MODULEMD_IS_PROFILE (profile));

  ModulemdProfile *copied_profile = modulemd_profile_copy (profile);

  g_hash_table_replace (self->profiles,
                        g_strdup (modulemd_profile_get_name (profile)),
                        copied_profile);
}


void
modulemd_packager_v3_clear_profiles (ModulemdPackagerV3 *self)
{
  g_return_if_fail (MODULEMD_IS_PACKAGER_V3 (self));

  g_hash_table_remove_all (self->profiles);
}


GStrv
modulemd_packager_v3_get_profile_names_as_strv (ModulemdPackagerV3 *self)
{
  g_return_val_if_fail (MODULEMD_IS_PACKAGER_V3 (self), NULL);

  return modulemd_ordered_str_keys_as_strv (self->profiles);
}


ModulemdProfile *
modulemd_packager_v3_get_profile (ModulemdPackagerV3 *self,
                                  const gchar *profile_name)
{
  g_return_val_if_fail (MODULEMD_IS_PACKAGER_V3 (self), NULL);

  return g_hash_table_lookup (self->profiles, profile_name);
}


void
modulemd_packager_v3_add_rpm_api (ModulemdPackagerV3 *self, const gchar *rpm)
{
  if (!rpm)
    {
      return;
    }

  g_return_if_fail (MODULEMD_IS_PACKAGER_V3 (self));

  g_hash_table_add (self->rpm_api, g_strdup (rpm));
}


void
modulemd_packager_v3_replace_rpm_api (ModulemdPackagerV3 *self,
                                      GHashTable *set)
{
  g_return_if_fail (MODULEMD_IS_PACKAGER_V3 (self));

  MODULEMD_REPLACE_SET (self->rpm_api, set);
}


void
modulemd_packager_v3_remove_rpm_api (ModulemdPackagerV3 *self,
                                     const gchar *rpm)
{
  if (!rpm)
    {
      return;
    }

  g_return_if_fail (MODULEMD_IS_PACKAGER_V3 (self));

  g_hash_table_remove (self->rpm_api, rpm);
}


void
modulemd_packager_v3_clear_rpm_api (ModulemdPackagerV3 *self)
{
  g_return_if_fail (MODULEMD_IS_PACKAGER_V3 (self));

  g_hash_table_remove_all (self->rpm_api);
}


GStrv
modulemd_packager_v3_get_rpm_api_as_strv (ModulemdPackagerV3 *self)
{
  g_return_val_if_fail (MODULEMD_IS_PACKAGER_V3 (self), NULL);

  return modulemd_ordered_str_keys_as_strv (self->rpm_api);
}

void
modulemd_packager_v3_add_rpm_filter (ModulemdPackagerV3 *self,
                                     const gchar *rpm)
{
  if (!rpm)
    {
      return;
    }

  g_return_if_fail (MODULEMD_IS_PACKAGER_V3 (self));

  g_hash_table_add (self->rpm_filters, g_strdup (rpm));
}


void
modulemd_packager_v3_replace_rpm_filters (ModulemdPackagerV3 *self,
                                          GHashTable *set)
{
  g_return_if_fail (MODULEMD_IS_PACKAGER_V3 (self));

  MODULEMD_REPLACE_SET (self->rpm_filters, set);
}


void
modulemd_packager_v3_remove_rpm_filter (ModulemdPackagerV3 *self,
                                        const gchar *rpm)
{
  if (!rpm)
    {
      return;
    }

  g_return_if_fail (MODULEMD_IS_PACKAGER_V3 (self));

  g_hash_table_remove (self->rpm_filters, rpm);
}


void
modulemd_packager_v3_clear_rpm_filters (ModulemdPackagerV3 *self)
{
  g_return_if_fail (MODULEMD_IS_PACKAGER_V3 (self));

  g_hash_table_remove_all (self->rpm_filters);
}


GStrv
modulemd_packager_v3_get_rpm_filters_as_strv (ModulemdPackagerV3 *self)
{
  g_return_val_if_fail (MODULEMD_IS_PACKAGER_V3 (self), NULL);

  return modulemd_ordered_str_keys_as_strv (self->rpm_filters);
}


void
modulemd_packager_v3_add_demodularized_rpm (ModulemdPackagerV3 *self,
                                            const gchar *rpm)
{
  if (!rpm)
    {
      return;
    }

  g_return_if_fail (MODULEMD_IS_PACKAGER_V3 (self));

  g_hash_table_add (self->demodularized_rpms, g_strdup (rpm));
}


void
modulemd_packager_v3_replace_demodularized_rpms (ModulemdPackagerV3 *self,
                                                 GHashTable *set)
{
  g_return_if_fail (MODULEMD_IS_PACKAGER_V3 (self));

  MODULEMD_REPLACE_SET (self->demodularized_rpms, set);
}


void
modulemd_packager_v3_remove_demodularized_rpm (ModulemdPackagerV3 *self,
                                               const gchar *rpm)
{
  if (!rpm)
    {
      return;
    }

  g_return_if_fail (MODULEMD_IS_PACKAGER_V3 (self));

  g_hash_table_remove (self->demodularized_rpms, rpm);
}


void
modulemd_packager_v3_clear_demodularized_rpms (ModulemdPackagerV3 *self)
{
  g_return_if_fail (MODULEMD_IS_PACKAGER_V3 (self));

  g_hash_table_remove_all (self->demodularized_rpms);
}


GStrv
modulemd_packager_v3_get_demodularized_rpms (ModulemdPackagerV3 *self)
{
  g_return_val_if_fail (MODULEMD_IS_PACKAGER_V3 (self), NULL);

  return modulemd_ordered_str_keys_as_strv (self->demodularized_rpms);
}


void
modulemd_packager_v3_add_component (ModulemdPackagerV3 *self,
                                    ModulemdComponent *component)
{
  GHashTable *table = NULL;

  /* Do nothing if we were passed a NULL component */
  if (!component)
    {
      return;
    }

  g_return_if_fail (MODULEMD_IS_PACKAGER_V3 (self));
  g_return_if_fail (MODULEMD_IS_COMPONENT (component));

  if (MODULEMD_IS_COMPONENT_RPM (component))
    {
      table = self->rpm_components;
    }
  else if (MODULEMD_IS_COMPONENT_MODULE (component))
    {
      table = self->module_components;
    }
  else
    {
      /* Unknown component. Raise a warning and return */
      g_return_if_reached ();
    }

  /* Add the component to the table. This will replace an existing component
   * with the same name
   */
  g_hash_table_replace (table,
                        g_strdup (modulemd_component_get_key (component)),
                        modulemd_component_copy (component, NULL));
}


void
modulemd_packager_v3_remove_module_component (ModulemdPackagerV3 *self,
                                              const gchar *component_name)
{
  /* Do nothing if we were passed a NULL component_name */
  if (!component_name)
    {
      return;
    }

  g_return_if_fail (MODULEMD_IS_PACKAGER_V3 (self));

  g_hash_table_remove (self->module_components, component_name);
}


void
modulemd_packager_v3_clear_module_components (ModulemdPackagerV3 *self)
{
  g_return_if_fail (MODULEMD_IS_PACKAGER_V3 (self));

  g_hash_table_remove_all (self->module_components);
}


void
modulemd_packager_v3_remove_rpm_component (ModulemdPackagerV3 *self,
                                           const gchar *component_name)
{
  /* Do nothing if we were passed a NULL component_name */
  if (!component_name)
    {
      return;
    }

  g_return_if_fail (MODULEMD_IS_PACKAGER_V3 (self));

  g_hash_table_remove (self->rpm_components, component_name);
}


void
modulemd_packager_v3_clear_rpm_components (ModulemdPackagerV3 *self)
{
  g_return_if_fail (MODULEMD_IS_PACKAGER_V3 (self));

  g_hash_table_remove_all (self->rpm_components);
}


GStrv
modulemd_packager_v3_get_module_component_names_as_strv (
  ModulemdPackagerV3 *self)
{
  g_return_val_if_fail (MODULEMD_IS_PACKAGER_V3 (self), NULL);

  return modulemd_ordered_str_keys_as_strv (self->module_components);
}


GStrv
modulemd_packager_v3_get_rpm_component_names_as_strv (ModulemdPackagerV3 *self)
{
  g_return_val_if_fail (MODULEMD_IS_PACKAGER_V3 (self), NULL);

  return modulemd_ordered_str_keys_as_strv (self->rpm_components);
}


ModulemdComponentModule *
modulemd_packager_v3_get_module_component (ModulemdPackagerV3 *self,
                                           const gchar *component_name)
{
  g_return_val_if_fail (MODULEMD_IS_PACKAGER_V3 (self), NULL);

  return g_hash_table_lookup (self->module_components, component_name);
}


ModulemdComponentRpm *
modulemd_packager_v3_get_rpm_component (ModulemdPackagerV3 *self,
                                        const gchar *component_name)
{
  g_return_val_if_fail (MODULEMD_IS_PACKAGER_V3 (self), NULL);

  return g_hash_table_lookup (self->rpm_components, component_name);
}


gboolean
modulemd_packager_v3_to_defaults (ModulemdPackagerV3 *self,
                                  ModulemdDefaults **defaults_ptr,
                                  GError **error)
{
  g_autoptr (ModulemdDefaultsV1) defaults = NULL;
  ModulemdProfile *profile;
  g_autoptr (GError) nested_error = NULL;
  GHashTableIter iter;
  gpointer value;

  g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
  g_return_val_if_fail (defaults_ptr == NULL || *defaults_ptr == NULL, FALSE);
  g_return_val_if_fail (MODULEMD_IS_PACKAGER_V3 (self), FALSE);

  g_hash_table_iter_init (&iter, self->profiles);
  while (g_hash_table_iter_next (&iter, NULL, &value))
    {
      profile = MODULEMD_PROFILE (value);
      if (modulemd_profile_is_default (profile))
        {
          if (!self->module_name)
            {
              g_set_error (
                error,
                MODULEMD_ERROR,
                MMD_ERROR_MISSING_REQUIRED,
                "A module name is required when generating "
                "a modulemd-defaults document for a default profile %s",
                modulemd_profile_get_name (profile));
              return FALSE;
            }
          if (!self->stream_name)
            {
              g_set_error (
                error,
                MODULEMD_ERROR,
                MMD_ERROR_MISSING_REQUIRED,
                "A module stream is required when generating "
                "a modulemd-defaults document for a default profile %s",
                modulemd_profile_get_name (profile));
              return FALSE;
            }
          if (!defaults)
            {
              defaults = modulemd_defaults_v1_new (self->module_name);
            }
          modulemd_defaults_v1_add_default_profile_for_stream (
            defaults,
            self->stream_name,
            modulemd_profile_get_name (profile),
            NULL);
        }
    }

  if (!defaults)
    {
      return TRUE;
    }

  if (!modulemd_defaults_validate (MODULEMD_DEFAULTS (defaults),
                                   &nested_error))
    {
      g_propagate_error (error, g_steal_pointer (&nested_error));
      return FALSE;
    }

  *defaults_ptr = MODULEMD_DEFAULTS (g_steal_pointer (&defaults));
  return TRUE;
}


static void
copy_packager_v3_common_to_stream_v2 (ModulemdModuleStreamV2 *stream_v2,
                                      ModulemdPackagerV3 *packager_v3)
{
  g_autoptr (ModulemdProfile) profile = NULL;
  GHashTableIter iter;
  gpointer value;

  modulemd_module_stream_v2_set_summary (
    stream_v2, modulemd_packager_v3_get_summary (packager_v3));

  modulemd_module_stream_v2_set_description (
    stream_v2, modulemd_packager_v3_get_description (packager_v3));

  /* Packager v3 "license" is optional. Stream v2 "license" is required
   * Fill in the default Packager v3 license if none has been specified.
   */
  if (g_hash_table_size (packager_v3->module_licenses) == 0)
    {
      modulemd_module_stream_v2_add_module_license (
        stream_v2, MMD_PACKAGER_DEFAULT_MODULE_LICENSE);
    }
  else
    {
      MODULEMD_REPLACE_SET (stream_v2->module_licenses,
                            packager_v3->module_licenses);
    }

  modulemd_module_stream_v2_set_xmd (
    stream_v2, modulemd_packager_v3_get_xmd (packager_v3));

  modulemd_module_stream_v2_set_community (
    stream_v2, modulemd_packager_v3_get_community (packager_v3));

  modulemd_module_stream_v2_set_documentation (
    stream_v2, modulemd_packager_v3_get_documentation (packager_v3));

  modulemd_module_stream_v2_set_tracker (
    stream_v2, modulemd_packager_v3_get_tracker (packager_v3));

  g_hash_table_iter_init (&iter, packager_v3->profiles);
  while (g_hash_table_iter_next (&iter, NULL, &value))
    {
      profile = modulemd_profile_copy (MODULEMD_PROFILE (value));
      modulemd_profile_unset_default (profile);
      modulemd_module_stream_v2_add_profile (stream_v2, profile);
      g_clear_object (&profile);
    }

  modulemd_module_stream_v2_replace_rpm_api (stream_v2, packager_v3->rpm_api);

  modulemd_module_stream_v2_replace_rpm_filters (stream_v2,
                                                 packager_v3->rpm_filters);

  modulemd_module_stream_v2_replace_demodularized_rpms (
    stream_v2, packager_v3->demodularized_rpms);

  COPY_HASHTABLE_BY_VALUE_ADDER (stream_v2,
                                 packager_v3,
                                 rpm_components,
                                 modulemd_module_stream_v2_add_component);

  COPY_HASHTABLE_BY_VALUE_ADDER (stream_v2,
                                 packager_v3,
                                 module_components,
                                 modulemd_module_stream_v2_add_component);
}

static void
copy_packager_v3_buildconfig_to_stream_v2 (ModulemdModuleStreamV2 *stream_v2,
                                           ModulemdBuildConfig *bc)
{
  g_autoptr (ModulemdDependencies) deps = NULL;
  g_auto (GStrv) modules = NULL;

  modulemd_module_stream_v2_set_buildopts (
    stream_v2, modulemd_build_config_get_buildopts (bc));

  deps = modulemd_dependencies_new ();

  modulemd_dependencies_add_buildtime_stream (
    deps, "platform", modulemd_build_config_get_platform (bc));
  modulemd_dependencies_add_runtime_stream (
    deps, "platform", modulemd_build_config_get_platform (bc));

  modules = modulemd_build_config_get_buildtime_modules_as_strv (bc);
  for (guint j = 0; j < g_strv_length (modules); j++)
    {
      modulemd_dependencies_add_buildtime_stream (
        deps,
        modules[j],
        modulemd_build_config_get_buildtime_requirement_stream (bc,
                                                                modules[j]));
    }
  g_clear_pointer (&modules, g_strfreev);

  modules = modulemd_build_config_get_runtime_modules_as_strv (bc);
  for (guint j = 0; j < g_strv_length (modules); j++)
    {
      modulemd_dependencies_add_runtime_stream (
        deps,
        modules[j],
        modulemd_build_config_get_runtime_requirement_stream (bc, modules[j]));
    }
  g_clear_pointer (&modules, g_strfreev);

  modulemd_module_stream_v2_add_dependencies (stream_v2, deps);
  g_clear_object (&deps);
}


ModulemdModuleStreamV2 *
modulemd_packager_v3_to_stream_v2 (ModulemdPackagerV3 *self, GError **error)
{
  g_autoptr (ModulemdModuleStreamV2) v2_stream = NULL;
  g_auto (GStrv) contexts = NULL;
  g_autoptr (GError) nested_error = NULL;
  ModulemdBuildopts *buildopts = NULL;
  ModulemdBuildConfig *bc;

  g_return_val_if_fail (error == NULL || *error == NULL, NULL);
  g_return_val_if_fail (MODULEMD_IS_PACKAGER_V3 (self), NULL);

  v2_stream = modulemd_module_stream_v2_new (
    modulemd_packager_v3_get_module_name (self),
    modulemd_packager_v3_get_stream_name (self));

  /* set attributes that are the same for all streams */
  copy_packager_v3_common_to_stream_v2 (v2_stream, self);

  /* get the list of packager build configuration contexts */
  contexts = modulemd_packager_v3_get_build_config_contexts_as_strv (self);

  /* If there is exactly one build configuration, use it for the stream */
  /* context. Otherwise, leave the stream context unset. */
  if (g_strv_length (contexts) == 1)
    {
      modulemd_module_stream_set_context (MODULEMD_MODULE_STREAM (v2_stream),
                                          contexts[0]);
    }

  /* map each BuildConfig object to a Dependencies object within the same StreamV2 object */
  for (guint i = 0; i < g_strv_length (contexts); i++)
    {
      bc = modulemd_packager_v3_get_build_config (self, contexts[i]);

      if (i == 0)
        {
          /* Save the buildopts from the first build configuration to */
          /* set the stream buildopts. */
          buildopts = modulemd_build_config_get_buildopts (bc);
        }

      /* set attributes that are unique per build configuration */
      copy_packager_v3_buildconfig_to_stream_v2 (v2_stream, bc);
    }
  g_clear_pointer (&contexts, g_strfreev);

  /* set the saved buildopts from the first build configuration */
  modulemd_module_stream_v2_set_buildopts (v2_stream, buildopts);

  if (!modulemd_module_stream_validate (MODULEMD_MODULE_STREAM (v2_stream),
                                        &nested_error))
    {
      g_propagate_error (error, g_steal_pointer (&nested_error));
      return NULL;
    }

  return g_steal_pointer (&v2_stream);
}

ModulemdModuleIndex *
modulemd_packager_v3_to_stream_v2_ext (ModulemdPackagerV3 *self,
                                       GError **error)
{
  g_autoptr (ModulemdModuleIndex) index = NULL;
  g_autoptr (ModulemdModuleStreamV2) v2_stream = NULL;
  g_autoptr (ModulemdDefaults) defaults = NULL;
  g_autoptr (GError) nested_error = NULL;

  g_return_val_if_fail (error == NULL || *error == NULL, NULL);
  g_return_val_if_fail (MODULEMD_IS_PACKAGER_V3 (self), NULL);

  v2_stream = modulemd_packager_v3_to_stream_v2 (self, &nested_error);
  if (!v2_stream)
    {
      g_propagate_error (error, g_steal_pointer (&nested_error));
      return NULL;
    }

  /* autogen module/stream names if necessary for adding to index */

  modulemd_module_stream_set_autogen_module_name (
    MODULEMD_MODULE_STREAM (v2_stream), 0);
  modulemd_module_stream_set_autogen_stream_name (
    MODULEMD_MODULE_STREAM (v2_stream), 0);

  index = modulemd_module_index_new ();
  if (!modulemd_module_index_add_module_stream (
        index, MODULEMD_MODULE_STREAM (v2_stream), &nested_error))
    {
      g_propagate_error (error, g_steal_pointer (&nested_error));
      return NULL;
    }

  g_clear_object (&v2_stream);

  if (!modulemd_packager_v3_to_defaults (self, &defaults, &nested_error))
    {
      g_propagate_error (error, g_steal_pointer (&nested_error));
      return NULL;
    }

  if (defaults)
    {
      if (!modulemd_module_index_add_defaults (index, defaults, &nested_error))
        {
          g_propagate_error (error, g_steal_pointer (&nested_error));
          return NULL;
        }

      g_clear_object (&defaults);
    }

  return g_steal_pointer (&index);
}


ModulemdModuleIndex *
modulemd_packager_v3_convert_to_index (ModulemdPackagerV3 *self,
                                       GError **error)
{
  g_auto (GStrv) contexts = NULL;
  g_autoptr (ModulemdModuleIndex) index = NULL;
  g_autoptr (ModulemdModuleStreamV2) v2_stream = NULL;
  g_autoptr (ModulemdDefaults) defaults = NULL;
  g_autoptr (GError) nested_error = NULL;
  ModulemdBuildConfig *bc;

  g_return_val_if_fail (error == NULL || *error == NULL, NULL);
  g_return_val_if_fail (MODULEMD_IS_PACKAGER_V3 (self), NULL);

  /* get the list of packager build configuration contexts */
  contexts = modulemd_packager_v3_get_build_config_contexts_as_strv (self);

  if (g_strv_length (contexts) == 0)
    {
      g_set_error_literal (error,
                           MODULEMD_ERROR,
                           MMD_ERROR_VALIDATE,
                           "Packager document with no build configurations "
                           "cannot be converted to stream");
      return NULL;
    }

  /* create a ModuleIndex to contain the results */
  index = modulemd_module_index_new ();

  /* create a StreamV2 object for each BuildConfig object */
  for (guint i = 0; i < g_strv_length (contexts); i++)
    {
      bc = modulemd_packager_v3_get_build_config (self, contexts[i]);

      v2_stream = modulemd_module_stream_v2_new (
        modulemd_packager_v3_get_module_name (self),
        modulemd_packager_v3_get_stream_name (self));

      modulemd_module_stream_set_context (MODULEMD_MODULE_STREAM (v2_stream),
                                          contexts[i]);

      /* set attributes that are the same for all streams */
      copy_packager_v3_common_to_stream_v2 (v2_stream, self);

      /* set attributes that are unique per build configuration */
      copy_packager_v3_buildconfig_to_stream_v2 (v2_stream, bc);

      if (!modulemd_module_stream_validate (MODULEMD_MODULE_STREAM (v2_stream),
                                            &nested_error))
        {
          g_propagate_error (error, g_steal_pointer (&nested_error));
          return NULL;
        }

      /* autogen module/stream names if necessary for adding to index */

      modulemd_module_stream_set_autogen_module_name (
        MODULEMD_MODULE_STREAM (v2_stream), 0);
      modulemd_module_stream_set_autogen_stream_name (
        MODULEMD_MODULE_STREAM (v2_stream), 0);

      if (!modulemd_module_index_add_module_stream (
            index, MODULEMD_MODULE_STREAM (v2_stream), &nested_error))
        {
          g_propagate_error (error, g_steal_pointer (&nested_error));
          return NULL;
        }

      g_clear_object (&v2_stream);
    }
  g_clear_pointer (&contexts, g_strfreev);

  if (!modulemd_packager_v3_to_defaults (self, &defaults, &nested_error))
    {
      g_propagate_error (error, g_steal_pointer (&nested_error));
      return NULL;
    }

  if (defaults)
    {
      if (!modulemd_module_index_add_defaults (index, defaults, &nested_error))
        {
          g_propagate_error (error, g_steal_pointer (&nested_error));
          return NULL;
        }

      g_clear_object (&defaults);
    }

  return g_steal_pointer (&index);
}


static gboolean
modulemd_packager_v3_parse_build_configs (yaml_parser_t *parser,
                                          ModulemdPackagerV3 *packager,
                                          gboolean strict,
                                          GError **error);

static gboolean
modulemd_packager_v3_parse_refs (yaml_parser_t *parser,
                                 ModulemdPackagerV3 *packager,
                                 gboolean strict,
                                 GError **error);

static gboolean
modulemd_packager_v3_parse_profiles (yaml_parser_t *parser,
                                     ModulemdPackagerV3 *packager,
                                     gboolean strict,
                                     GError **error);

static gboolean
modulemd_packager_v3_parse_components (yaml_parser_t *parser,
                                       ModulemdPackagerV3 *packager,
                                       gboolean strict,
                                       GError **error);


ModulemdPackagerV3 *
modulemd_packager_v3_parse_yaml (ModulemdSubdocumentInfo *subdoc,
                                 GError **error)
{
  MODULEMD_INIT_TRACE ();
  MMD_INIT_YAML_PARSER (parser);
  MMD_INIT_YAML_EVENT (event);
  gboolean done = FALSE;
  const gboolean strict = TRUE; /* PackagerV3 should always parse strictly */
  g_autoptr (GError) nested_error = NULL;
  g_autoptr (ModulemdPackagerV3) packager = NULL;
  g_autoptr (GHashTable) set = NULL;
  g_autoptr (GVariant) xmd = NULL;

  g_return_val_if_fail (error == NULL || *error == NULL, FALSE);

  if (!modulemd_subdocument_info_get_data_parser (
        subdoc, &parser, strict, error))
    {
      return FALSE;
    }

  packager = modulemd_packager_v3_new ();

  /* Read the MAPPING_START */
  YAML_PARSER_PARSE_WITH_EXIT (&parser, &event, error);
  if (event.type != YAML_MAPPING_START_EVENT)
    {
      MMD_YAML_ERROR_EVENT_EXIT (
        error, event, "Data section did not begin with a map.");
    }

  /* Process through the mapping */
  while (!done)
    {
      YAML_PARSER_PARSE_WITH_EXIT (&parser, &event, error);

      switch (event.type)
        {
        case YAML_MAPPING_END_EVENT: done = TRUE; break;

        case YAML_SCALAR_EVENT:
          /* Mapping Keys */
          if (g_str_equal ((const gchar *)event.data.scalar.value, "name"))
            {
              MMD_SET_PARSED_YAML_STRING (&parser,
                                          error,
                                          modulemd_packager_v3_set_module_name,
                                          packager);
            }

          else if (g_str_equal ((const gchar *)event.data.scalar.value,
                                "stream"))
            {
              MMD_SET_PARSED_YAML_STRING (&parser,
                                          error,
                                          modulemd_packager_v3_set_stream_name,
                                          packager);
            }

          else if (g_str_equal ((const gchar *)event.data.scalar.value,
                                "summary"))
            {
              MMD_SET_PARSED_YAML_STRING (
                &parser, error, modulemd_packager_v3_set_summary, packager);
            }

          else if (g_str_equal ((const gchar *)event.data.scalar.value,
                                "description"))
            {
              MMD_SET_PARSED_YAML_STRING (&parser,
                                          error,
                                          modulemd_packager_v3_set_description,
                                          packager);
            }

          else if (g_str_equal ((const gchar *)event.data.scalar.value,
                                "license"))
            {
              set = modulemd_yaml_parse_string_set (&parser, &nested_error);
              MODULEMD_REPLACE_SET (packager->module_licenses, set);
              g_clear_pointer (&set, g_hash_table_unref);
            }

          else if (g_str_equal ((const gchar *)event.data.scalar.value, "xmd"))
            {
              xmd = mmd_parse_xmd (&parser, &nested_error);
              if (!xmd)
                {
                  g_propagate_error (error, g_steal_pointer (&nested_error));
                  return NULL;
                }
              modulemd_packager_v3_set_xmd (packager, xmd);
              g_clear_pointer (&xmd, g_variant_unref);
            }

          else if (g_str_equal ((const gchar *)event.data.scalar.value,
                                "configurations"))
            {
              if (!modulemd_packager_v3_parse_build_configs (
                    &parser, packager, strict, &nested_error))
                {
                  g_propagate_error (error, g_steal_pointer (&nested_error));
                  return NULL;
                }
            }

          else if (g_str_equal ((const gchar *)event.data.scalar.value,
                                "references"))
            {
              if (!modulemd_packager_v3_parse_refs (
                    &parser, packager, strict, &nested_error))
                {
                  g_propagate_error (error, g_steal_pointer (&nested_error));
                  return NULL;
                }
            }

          else if (g_str_equal ((const gchar *)event.data.scalar.value,
                                "profiles"))
            {
              if (!modulemd_packager_v3_parse_profiles (
                    &parser, packager, strict, &nested_error))
                {
                  g_propagate_error (error, g_steal_pointer (&nested_error));
                  return NULL;
                }
            }

          else if (g_str_equal ((const gchar *)event.data.scalar.value, "api"))
            {
              set = modulemd_yaml_parse_string_set_from_map (
                &parser, "rpms", strict, &nested_error);
              modulemd_packager_v3_replace_rpm_api (packager, set);
              g_clear_pointer (&set, g_hash_table_unref);
            }

          else if (g_str_equal ((const gchar *)event.data.scalar.value,
                                "filter"))
            {
              set = modulemd_yaml_parse_string_set_from_map (
                &parser, "rpms", strict, &nested_error);
              modulemd_packager_v3_replace_rpm_filters (packager, set);
              g_clear_pointer (&set, g_hash_table_unref);
            }

          else if (g_str_equal ((const gchar *)event.data.scalar.value,
                                "demodularized"))
            {
              set = modulemd_yaml_parse_string_set_from_map (
                &parser, "rpms", strict, &nested_error);
              modulemd_packager_v3_replace_demodularized_rpms (packager, set);
              g_clear_pointer (&set, g_hash_table_unref);
            }

          else if (g_str_equal ((const gchar *)event.data.scalar.value,
                                "components"))
            {
              if (!modulemd_packager_v3_parse_components (
                    &parser, packager, strict, &nested_error))
                {
                  g_propagate_error (error, g_steal_pointer (&nested_error));
                  return NULL;
                }
            }

          else
            {
              SKIP_UNKNOWN (&parser,
                            NULL,
                            "Unexpected key in packager v3 document: %s",
                            (const gchar *)event.data.scalar.value);
              break;
            }

          break;

        default:
          MMD_YAML_ERROR_EVENT_EXIT (
            error,
            event,
            "Unexpected YAML event in ModuleStreamV2: %s",
            mmd_yaml_get_event_name (event.type));
          break;
        }
      yaml_event_delete (&event);
    }

  return g_steal_pointer (&packager);
}


static gboolean
modulemd_packager_v3_parse_build_configs (yaml_parser_t *parser,
                                          ModulemdPackagerV3 *packager,
                                          gboolean strict,
                                          GError **error)
{
  MMD_INIT_YAML_EVENT (event);
  gboolean done = FALSE;
  g_autoptr (ModulemdBuildConfig) buildconfig = NULL;
  g_autoptr (GError) nested_error = NULL;
  const gchar *context;

  YAML_PARSER_PARSE_WITH_EXIT_BOOL (parser, &event, error);
  if (event.type != YAML_SEQUENCE_START_EVENT)
    {
      MMD_YAML_ERROR_EVENT_EXIT_BOOL (
        error, event, "Unexpected YAML event in build_configs");
    }

  while (!done)
    {
      YAML_PARSER_PARSE_WITH_EXIT_BOOL (parser, &event, error);

      switch (event.type)
        {
        case YAML_SEQUENCE_END_EVENT: done = TRUE; break;

        case YAML_MAPPING_START_EVENT:
          buildconfig =
            modulemd_build_config_parse_yaml (parser, strict, &nested_error);
          if (!buildconfig)
            {
              g_propagate_error (error, g_steal_pointer (&nested_error));
              return FALSE;
            }
          /* A context must be unique. We check explicitly here because glib2
           * < 2.40 does not distinguish a replacement from an addition. */
          context = modulemd_build_config_get_context (buildconfig);
          if (g_hash_table_contains (packager->build_configs, context))
            {
              g_set_error (error,
                           MODULEMD_ERROR,
                           MMD_ERROR_VALIDATE,
                           "Duplicate context: %s",
                           context);
              return FALSE;
            }
          modulemd_packager_v3_add_build_config (packager, buildconfig);
          g_clear_object (&buildconfig);
          break;

        default:
          MMD_YAML_ERROR_EVENT_EXIT_BOOL (
            error, event, "Unexpected YAML event in build_config list");
          break;
        }
      yaml_event_delete (&event);
    }

  return TRUE;
}


static gboolean
modulemd_packager_v3_parse_refs (yaml_parser_t *parser,
                                 ModulemdPackagerV3 *packager,
                                 gboolean strict,
                                 GError **error)
{
  MODULEMD_INIT_TRACE ();
  MMD_INIT_YAML_EVENT (event);
  gboolean done = FALSE;
  g_autofree gchar *scalar = NULL;
  g_autoptr (GError) nested_error = NULL;

  g_return_val_if_fail (error == NULL || *error == NULL, FALSE);

  /* Process through the map */
  /* We *must* get a MAPPING_START here */
  YAML_PARSER_PARSE_WITH_EXIT_BOOL (parser, &event, error);
  if (event.type != YAML_MAPPING_START_EVENT)
    {
      MMD_YAML_ERROR_EVENT_EXIT_BOOL (
        error,
        event,
        "Got %s instead of MAPPING_START in references.",
        mmd_yaml_get_event_name (event.type));
    }

  while (!done)
    {
      YAML_PARSER_PARSE_WITH_EXIT_BOOL (parser, &event, error);

      switch (event.type)
        {
        case YAML_MAPPING_END_EVENT: done = TRUE; break;

        case YAML_SCALAR_EVENT:
          if (g_str_equal ((const gchar *)event.data.scalar.value,
                           "community"))
            {
              scalar = modulemd_yaml_parse_string (parser, &nested_error);
              if (!scalar)
                {
                  g_propagate_error (error, g_steal_pointer (&nested_error));
                  return FALSE;
                }

              modulemd_packager_v3_set_community (packager, scalar);
              g_clear_pointer (&scalar, g_free);
            }

          else if (g_str_equal ((const gchar *)event.data.scalar.value,
                                "documentation"))
            {
              scalar = modulemd_yaml_parse_string (parser, &nested_error);
              if (!scalar)
                {
                  g_propagate_error (error, g_steal_pointer (&nested_error));
                  return FALSE;
                }

              modulemd_packager_v3_set_documentation (packager, scalar);
              g_clear_pointer (&scalar, g_free);
            }

          else if (g_str_equal ((const gchar *)event.data.scalar.value,
                                "tracker"))
            {
              scalar = modulemd_yaml_parse_string (parser, &nested_error);
              if (!scalar)
                {
                  g_propagate_error (error, g_steal_pointer (&nested_error));
                  return FALSE;
                }

              modulemd_packager_v3_set_tracker (packager, scalar);
              g_clear_pointer (&scalar, g_free);
            }

          else
            {
              SKIP_UNKNOWN (parser,
                            FALSE,
                            "Unexpected key in references: %s",
                            (const gchar *)event.data.scalar.value);
              break;
            }
          break;

        default:
          MMD_YAML_ERROR_EVENT_EXIT_BOOL (
            error,
            event,
            "Unexpected YAML event in references: %s",
            mmd_yaml_get_event_name (event.type));
          break;
        }
      yaml_event_delete (&event);
    }

  return TRUE;
}


static gboolean
modulemd_packager_v3_parse_profiles (yaml_parser_t *parser,
                                     ModulemdPackagerV3 *packager,
                                     gboolean strict,
                                     GError **error)
{
  MODULEMD_INIT_TRACE ();
  MMD_INIT_YAML_EVENT (event);
  gboolean done = FALSE;
  g_autoptr (GError) nested_error = NULL;
  g_autoptr (ModulemdProfile) profile = NULL;

  g_return_val_if_fail (error == NULL || *error == NULL, FALSE);

  /* Process through the map */
  /* We *must* get a MAPPING_START here */
  YAML_PARSER_PARSE_WITH_EXIT_BOOL (parser, &event, error);
  if (event.type != YAML_MAPPING_START_EVENT)
    {
      MMD_YAML_ERROR_EVENT_EXIT_BOOL (
        error,
        event,
        "Got %s instead of MAPPING_START in profiles.",
        mmd_yaml_get_event_name (event.type));
    }

  while (!done)
    {
      YAML_PARSER_PARSE_WITH_EXIT_BOOL (parser, &event, error);

      switch (event.type)
        {
        case YAML_MAPPING_END_EVENT: done = TRUE; break;

        case YAML_SCALAR_EVENT:
          profile = modulemd_profile_parse_yaml (
            parser,
            (const gchar *)event.data.scalar.value,
            strict,
            &nested_error);
          if (!profile)
            {
              g_propagate_error (error, g_steal_pointer (&nested_error));
              return FALSE;
            }

          modulemd_packager_v3_add_profile (packager, profile);
          g_clear_pointer (&profile, g_object_unref);
          break;

        default:
          MMD_YAML_ERROR_EVENT_EXIT_BOOL (
            error,
            event,
            "Unexpected YAML event in profiles: %s",
            mmd_yaml_get_event_name (event.type));
          break;
        }
      yaml_event_delete (&event);
    }

  return TRUE;
}


static gboolean
modulemd_packager_v3_parse_rpm_components (yaml_parser_t *parser,
                                           ModulemdPackagerV3 *packager,
                                           gboolean strict,
                                           GError **error);
static gboolean
modulemd_packager_v3_parse_module_components (yaml_parser_t *parser,
                                              ModulemdPackagerV3 *packager,
                                              gboolean strict,
                                              GError **error);


static gboolean
modulemd_packager_v3_parse_components (yaml_parser_t *parser,
                                       ModulemdPackagerV3 *packager,
                                       gboolean strict,
                                       GError **error)
{
  MODULEMD_INIT_TRACE ();
  MMD_INIT_YAML_EVENT (event);
  gboolean done = FALSE;
  g_autoptr (GError) nested_error = NULL;

  g_return_val_if_fail (error == NULL || *error == NULL, FALSE);

  /* Process through the sequence */
  /* We *must* get a MAPPING_START here */
  YAML_PARSER_PARSE_WITH_EXIT_BOOL (parser, &event, error);
  if (event.type != YAML_MAPPING_START_EVENT)
    {
      MMD_YAML_ERROR_EVENT_EXIT_BOOL (
        error,
        event,
        "Got %s instead of MAPPING_START in components.",
        mmd_yaml_get_event_name (event.type));
    }

  while (!done)
    {
      YAML_PARSER_PARSE_WITH_EXIT_BOOL (parser, &event, error);

      switch (event.type)
        {
        case YAML_MAPPING_END_EVENT: done = TRUE; break;

        case YAML_SCALAR_EVENT:
          if (g_str_equal ((const gchar *)event.data.scalar.value, "rpms"))
            {
              if (!modulemd_packager_v3_parse_rpm_components (
                    parser, packager, strict, &nested_error))
                {
                  g_propagate_error (error, g_steal_pointer (&nested_error));
                  return FALSE;
                }
            }

          else if (g_str_equal ((const gchar *)event.data.scalar.value,
                                "modules"))
            {
              if (!modulemd_packager_v3_parse_module_components (
                    parser, packager, strict, &nested_error))
                {
                  g_propagate_error (error, g_steal_pointer (&nested_error));
                  return FALSE;
                }
            }

          else
            {
              SKIP_UNKNOWN (parser,
                            FALSE,
                            "Unexpected key in components: %s",
                            (const gchar *)event.data.scalar.value);
            }

          break;


        default:
          MMD_YAML_ERROR_EVENT_EXIT_BOOL (
            error,
            event,
            "Unexpected YAML event in components: %s",
            mmd_yaml_get_event_name (event.type));
          break;
        }
      yaml_event_delete (&event);
    }

  return TRUE;
}


static gboolean
modulemd_packager_v3_parse_rpm_components (yaml_parser_t *parser,
                                           ModulemdPackagerV3 *packager,
                                           gboolean strict,
                                           GError **error)
{
  MODULEMD_INIT_TRACE ();
  MMD_INIT_YAML_EVENT (event);
  gboolean done = FALSE;
  g_autoptr (GError) nested_error = NULL;
  g_autoptr (ModulemdComponentRpm) component = NULL;

  g_return_val_if_fail (error == NULL || *error == NULL, FALSE);

  /* We *must* get a MAPPING_START here */
  YAML_PARSER_PARSE_WITH_EXIT_BOOL (parser, &event, error);
  if (event.type != YAML_MAPPING_START_EVENT)
    {
      MMD_YAML_ERROR_EVENT_EXIT_BOOL (
        error,
        event,
        "Got %s instead of MAPPING_START in rpm components.",
        mmd_yaml_get_event_name (event.type));
    }

  while (!done)
    {
      YAML_PARSER_PARSE_WITH_EXIT_BOOL (parser, &event, error);

      switch (event.type)
        {
        case YAML_MAPPING_END_EVENT: done = TRUE; break;

        case YAML_SCALAR_EVENT:
          component = modulemd_component_rpm_parse_yaml (
            parser,
            (const gchar *)event.data.scalar.value,
            strict,
            &nested_error);
          if (!component)
            {
              g_propagate_error (error, g_steal_pointer (&nested_error));
              return FALSE;
            }
          modulemd_packager_v3_add_component (packager,
                                              (ModulemdComponent *)component);
          g_clear_pointer (&component, g_object_unref);
          break;

        default:
          MMD_YAML_ERROR_EVENT_EXIT_BOOL (
            error,
            event,
            "Unexpected YAML event in RPM component: %s",
            mmd_yaml_get_event_name (event.type));
          break;
        }
      yaml_event_delete (&event);
    }

  return TRUE;
}


static gboolean
modulemd_packager_v3_parse_module_components (yaml_parser_t *parser,
                                              ModulemdPackagerV3 *packager,
                                              gboolean strict,
                                              GError **error)
{
  MODULEMD_INIT_TRACE ();
  MMD_INIT_YAML_EVENT (event);
  gboolean done = FALSE;
  g_autoptr (GError) nested_error = NULL;
  g_autoptr (ModulemdComponentModule) component = NULL;

  g_return_val_if_fail (error == NULL || *error == NULL, FALSE);

  /* We *must* get a MAPPING_START here */
  YAML_PARSER_PARSE_WITH_EXIT_BOOL (parser, &event, error);
  if (event.type != YAML_MAPPING_START_EVENT)
    {
      MMD_YAML_ERROR_EVENT_EXIT_BOOL (
        error,
        event,
        "Got %s instead of MAPPING_START in module components.",
        mmd_yaml_get_event_name (event.type));
    }

  while (!done)
    {
      YAML_PARSER_PARSE_WITH_EXIT_BOOL (parser, &event, error);

      switch (event.type)
        {
        case YAML_MAPPING_END_EVENT: done = TRUE; break;

        case YAML_SCALAR_EVENT:
          component = modulemd_component_module_parse_yaml (
            parser,
            (const gchar *)event.data.scalar.value,
            strict,
            &nested_error);
          if (!component)
            {
              g_propagate_error (error, g_steal_pointer (&nested_error));
              return FALSE;
            }
          modulemd_packager_v3_add_component (packager,
                                              (ModulemdComponent *)component);
          g_clear_pointer (&component, g_object_unref);
          break;

        default:
          MMD_YAML_ERROR_EVENT_EXIT_BOOL (
            error,
            event,
            "Unexpected YAML event in module component: %s",
            mmd_yaml_get_event_name (event.type));
          break;
        }
      yaml_event_delete (&event);
    }

  return TRUE;
}


gboolean
modulemd_packager_v3_emit_yaml (ModulemdPackagerV3 *self,
                                yaml_emitter_t *emitter,
                                GError **error)
{
  MODULEMD_INIT_TRACE ();
  gsize i;
  g_autoptr (GPtrArray) keys = NULL;
  gboolean ret;

  /* Emit document headers */
  if (!modulemd_yaml_emit_document_headers (
        emitter, MODULEMD_YAML_DOC_PACKAGER, MD_PACKAGER_VERSION_THREE, error))
    {
      return FALSE;
    }

  /* Start data: */
  EMIT_MAPPING_START (emitter, error);

  if (modulemd_packager_v3_get_module_name (self) != NULL)
    {
      EMIT_KEY_VALUE (
        emitter, error, "name", modulemd_packager_v3_get_module_name (self));
    }

  if (modulemd_packager_v3_get_stream_name (self) != NULL)
    {
      EMIT_KEY_VALUE_FULL (emitter,
                           error,
                           "stream",
                           modulemd_packager_v3_get_stream_name (self),
                           YAML_DOUBLE_QUOTED_SCALAR_STYLE);
    }

  EMIT_KEY_VALUE (
    emitter, error, "summary", modulemd_packager_v3_get_summary (self));
  EMIT_KEY_VALUE_FULL (emitter,
                       error,
                       "description",
                       modulemd_packager_v3_get_description (self),
                       YAML_FOLDED_SCALAR_STYLE);

  if (NON_EMPTY_TABLE (self->module_licenses))
    {
      EMIT_STRING_SET (emitter, error, "license", self->module_licenses);
    }

  if (self->xmd != NULL)
    {
      EMIT_SCALAR (emitter, error, "xmd");
      if (!modulemd_yaml_emit_variant (emitter, self->xmd, error))
        {
          return FALSE;
        }
    }

  if (NON_EMPTY_TABLE (self->build_configs))
    {
      EMIT_SCALAR (emitter, error, "configurations");
      EMIT_SEQUENCE_START (emitter, error);
      keys =
        modulemd_ordered_str_keys (self->build_configs, modulemd_strcmp_sort);
      for (i = 0; i < keys->len; i++)
        {
          ret = modulemd_build_config_emit_yaml (
            g_hash_table_lookup (self->build_configs,
                                 g_ptr_array_index (keys, i)),
            emitter,
            error);
          if (!ret)
            {
              return FALSE;
            }
        }
      EMIT_SEQUENCE_END (emitter, error);
    }


  if (self->community || self->documentation || self->tracker)
    {
      EMIT_SCALAR (emitter, error, "references");
      EMIT_MAPPING_START (emitter, error);
      EMIT_KEY_VALUE_STRING_IF_SET (
        emitter, error, "community", self->community);
      EMIT_KEY_VALUE_STRING_IF_SET (
        emitter, error, "documentation", self->documentation);
      EMIT_KEY_VALUE_STRING_IF_SET (emitter, error, "tracker", self->tracker);
      EMIT_MAPPING_END (emitter, error);
    }

  EMIT_HASHTABLE_VALUES_IF_NON_EMPTY (
    emitter, error, "profiles", self->profiles, modulemd_profile_emit_yaml);

  if (NON_EMPTY_TABLE (self->rpm_api))
    {
      EMIT_SCALAR (emitter, error, "api");
      EMIT_MAPPING_START (emitter, error);
      EMIT_STRING_SET (emitter, error, "rpms", self->rpm_api);
      EMIT_MAPPING_END (emitter, error);
    }

  if (NON_EMPTY_TABLE (self->rpm_filters))
    {
      EMIT_SCALAR (emitter, error, "filter");
      EMIT_MAPPING_START (emitter, error);
      EMIT_STRING_SET (emitter, error, "rpms", self->rpm_filters);
      EMIT_MAPPING_END (emitter, error);
    }

  if (NON_EMPTY_TABLE (self->demodularized_rpms))
    {
      EMIT_SCALAR (emitter, error, "demodularized");
      EMIT_MAPPING_START (emitter, error);
      EMIT_STRING_SET (emitter, error, "rpms", self->demodularized_rpms);
      EMIT_MAPPING_END (emitter, error);
    }

  if (NON_EMPTY_TABLE (self->rpm_components) ||
      NON_EMPTY_TABLE (self->module_components))
    {
      EMIT_SCALAR (emitter, error, "components");
      EMIT_MAPPING_START (emitter, error);
      EMIT_HASHTABLE_VALUES_IF_NON_EMPTY (emitter,
                                          error,
                                          "rpms",
                                          self->rpm_components,
                                          modulemd_component_rpm_emit_yaml);
      EMIT_HASHTABLE_VALUES_IF_NON_EMPTY (emitter,
                                          error,
                                          "modules",
                                          self->module_components,
                                          modulemd_component_module_emit_yaml);
      EMIT_MAPPING_END (emitter, error);
    }

  /* The "data" mapping */
  EMIT_MAPPING_END (emitter, error);
  /* The overall document mapping */
  EMIT_MAPPING_END (emitter, error);
  if (!mmd_emitter_end_document (emitter, error))
    {
      return FALSE;
    }

  return TRUE;
}
