/*
 * This file is part of libmodulemd
 * Copyright (C) 2017-2018 Stephen Gallagher
 *
 * 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 "modulemd.h"
#include <glib.h>
#include <glib/gstdio.h>
#include <inttypes.h>
#include <yaml.h>
#include <errno.h>
#include "private/modulemd-yaml.h"
#include "private/modulemd-util.h"
#include "private/modulemd-subdocument-private.h"

GQuark
modulemd_yaml_error_quark (void)
{
  return g_quark_from_static_string ("modulemd-yaml-error-quark");
}


static gboolean
_parse_yaml (yaml_parser_t *parser,
             GPtrArray **data,
             GPtrArray **failures,
             GError **error);

static gboolean
_read_yaml_and_type (yaml_parser_t *parser, ModulemdSubdocument **subdocument);

static gboolean
_parse_subdocument (ModulemdSubdocument *subdocument,
                    ModulemdParsingFunc parse_func,
                    GObject **data,
                    guint64 version,
                    GError **error);


gboolean
parse_yaml_file (const gchar *path,
                 GPtrArray **data,
                 GPtrArray **failures,
                 GError **error)
{
  gboolean result = FALSE;
  FILE *yaml_file = NULL;
  yaml_parser_t parser;

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

  g_debug ("TRACE: entering parse_yaml_file");

  yaml_parser_initialize (&parser);

  errno = 0;
  yaml_file = g_fopen (path, "rb");
  if (!yaml_file)
    {
      g_set_error (error,
                   MODULEMD_YAML_ERROR,
                   MODULEMD_YAML_ERROR_OPEN,
                   "Failed to open file: %s",
                   g_strerror (errno));
      goto error;
    }

  yaml_parser_set_input_file (&parser, yaml_file);

  if (!_parse_yaml (&parser, data, failures, error))
    {
      MMD_YAML_ERROR_RETURN_RETHROW (error, "Could not parse YAML");
    }

  result = TRUE;

error:
  yaml_parser_delete (&parser);
  if (yaml_file)
    {
      fclose (yaml_file);
    }
  g_debug ("TRACE: exiting parse_yaml_file");
  return result;
}

gboolean
parse_yaml_string (const gchar *yaml,
                   GPtrArray **data,
                   GPtrArray **failures,
                   GError **error)
{
  gboolean result = FALSE;
  yaml_parser_t parser;

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

  g_debug ("TRACE: entering parse_yaml_string");

  yaml_parser_initialize (&parser);

  yaml_parser_set_input_string (
    &parser, (const unsigned char *)yaml, strlen (yaml));

  if (!_parse_yaml (&parser, data, failures, error))
    {
      MMD_YAML_ERROR_RETURN_RETHROW (error, "Could not parse YAML");
    }

  result = TRUE;

error:
  yaml_parser_delete (&parser);

  g_debug ("TRACE: exiting parse_yaml_string");
  return result;
}


gboolean
parse_yaml_stream (FILE *stream,
                   GPtrArray **data,
                   GPtrArray **failures,
                   GError **error)
{
  gboolean result = FALSE;
  yaml_parser_t parser;

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

  g_debug ("TRACE: entering parse_yaml_stream");

  yaml_parser_initialize (&parser);

  yaml_parser_set_input_file (&parser, stream);

  if (!_parse_yaml (&parser, data, failures, error))
    {
      MMD_YAML_ERROR_RETURN_RETHROW (error, "Could not parse YAML");
    }

  result = TRUE;

error:
  yaml_parser_delete (&parser);
  g_debug ("TRACE: exiting parse_yaml_stream");
  return result;
}


GHashTable *
parse_module_index_from_file (const gchar *path,
                              GPtrArray **failures,
                              GError **error)
{
  g_autoptr (FILE) yaml_file = NULL;
  g_autoptr (GPtrArray) data = NULL;
  g_auto (yaml_parser_t) parser;
  GHashTable *module_index = NULL;
  g_autoptr (GError) nested_error = NULL;

  g_debug ("TRACE: entering parse_module_index_from_file");
  yaml_parser_initialize (&parser);

  if (error != NULL && *error != NULL)
    {
      g_set_error_literal (error,
                           MODULEMD_YAML_ERROR,
                           MODULEMD_YAML_ERROR_PROGRAMMING,
                           "GError is initialized.");
      return NULL;
    }

  if (!path)
    {
      g_set_error_literal (error,
                           MODULEMD_YAML_ERROR,
                           MODULEMD_YAML_ERROR_PROGRAMMING,
                           "Path not supplied.");
      return NULL;
    }

  errno = 0;
  yaml_file = g_fopen (path, "rb");
  if (!yaml_file)
    {
      g_set_error (error,
                   MODULEMD_YAML_ERROR,
                   MODULEMD_YAML_ERROR_OPEN,
                   "Failed to open file: %s",
                   g_strerror (errno));
      return NULL;
    }

  yaml_parser_set_input_file (&parser, yaml_file);

  if (!_parse_yaml (&parser, &data, failures, &nested_error))
    {
      g_debug ("Could not parse YAML: %s", nested_error->message);
      g_propagate_error (error, g_steal_pointer (&nested_error));
      return NULL;
    }

  module_index = module_index_from_data (data, &nested_error);
  if (!module_index)
    {
      g_debug ("Could not get module_index: %s", nested_error->message);
      g_propagate_error (error, g_steal_pointer (&nested_error));
      return NULL;
    }

  g_debug ("TRACE: exiting parse_module_index_from_file");
  return module_index;
}


GHashTable *
parse_module_index_from_string (const gchar *yaml,
                                GPtrArray **failures,
                                GError **error)
{
  g_autoptr (GPtrArray) data = NULL;
  g_auto (yaml_parser_t) parser;
  GHashTable *module_index = NULL;
  g_autoptr (GError) nested_error = NULL;

  g_debug ("TRACE: entering parse_module_index_from_string");
  yaml_parser_initialize (&parser);

  if (error != NULL && *error != NULL)
    {
      g_set_error_literal (error,
                           MODULEMD_YAML_ERROR,
                           MODULEMD_YAML_ERROR_PROGRAMMING,
                           "GError is initialized.");
      return NULL;
    }

  if (!yaml)
    {
      g_set_error_literal (error,
                           MODULEMD_YAML_ERROR,
                           MODULEMD_YAML_ERROR_PROGRAMMING,
                           "String not supplied.");
      return NULL;
    }


  yaml_parser_set_input_string (
    &parser, (const unsigned char *)yaml, strlen (yaml));

  if (!_parse_yaml (&parser, &data, failures, &nested_error))
    {
      g_debug ("Could not parse YAML: %s", nested_error->message);
      g_propagate_error (error, g_steal_pointer (&nested_error));
      return NULL;
    }

  module_index = module_index_from_data (data, &nested_error);
  if (!module_index)
    {
      g_debug ("Could not get module_index: %s", nested_error->message);
      g_propagate_error (error, g_steal_pointer (&nested_error));
      return NULL;
    }

  g_debug ("TRACE: exiting parse_module_index_from_string");
  return module_index;
}


GHashTable *
parse_module_index_from_stream (FILE *iostream,
                                GPtrArray **failures,
                                GError **error)
{
  g_autoptr (GPtrArray) data = NULL;
  g_auto (yaml_parser_t) parser;
  GHashTable *module_index = NULL;
  g_autoptr (GError) nested_error = NULL;

  g_debug ("TRACE: entering parse_module_index_from_stream");
  yaml_parser_initialize (&parser);

  if (error != NULL && *error != NULL)
    {
      g_set_error_literal (error,
                           MODULEMD_YAML_ERROR,
                           MODULEMD_YAML_ERROR_PROGRAMMING,
                           "GError is initialized.");
      return NULL;
    }

  if (!iostream)
    {
      g_set_error_literal (error,
                           MODULEMD_YAML_ERROR,
                           MODULEMD_YAML_ERROR_PROGRAMMING,
                           "Stream not supplied.");
      return NULL;
    }

  yaml_parser_set_input_file (&parser, iostream);

  if (!_parse_yaml (&parser, &data, failures, &nested_error))
    {
      g_debug ("Could not parse YAML: %s", nested_error->message);
      g_propagate_error (error, g_steal_pointer (&nested_error));
      return NULL;
    }

  module_index = module_index_from_data (data, &nested_error);
  if (!module_index)
    {
      g_debug ("Could not get module_index: %s", nested_error->message);
      g_propagate_error (error, g_steal_pointer (&nested_error));
      return NULL;
    }

  g_debug ("TRACE: exiting parse_module_index_from_stream");

  return module_index;
}


static gboolean
_parse_yaml (yaml_parser_t *parser,
             GPtrArray **data,
             GPtrArray **failures,
             GError **error)
{
  gboolean result = FALSE;
  gboolean done = FALSE;
  MMD_INIT_YAML_EVENT (event);
  g_autoptr (GPtrArray) subdocuments = NULL;
  g_autoptr (GPtrArray) failed_subdocuments = NULL;
  g_autoptr (GPtrArray) objects = NULL;
  ModulemdSubdocument *document = NULL;
  ModulemdSubdocument *subdocument = NULL;
  g_autoptr (GError) subdocument_error = NULL;

  GObject *object = NULL;

  g_debug ("TRACE: entering _parse_yaml");

  /* Read through the complete stream once, separating subdocuments and
   * identifying their types
   */
  subdocuments = g_ptr_array_new_full (1, g_object_unref);
  failed_subdocuments = g_ptr_array_new_with_free_func (g_object_unref);
  objects = g_ptr_array_new_full (1, g_object_unref);

  while (!done)
    {
      YAML_PARSER_PARSE_WITH_ERROR_RETURN (
        parser, &event, error, "Parser error");

      switch (event.type)
        {
        case YAML_STREAM_START_EVENT:
          /* The beginning of the YAML stream */
          break;

        case YAML_STREAM_END_EVENT:
          /* All of the subdocuments have been processed */
          done = TRUE;
          break;

        case YAML_DOCUMENT_START_EVENT:
          if (!_read_yaml_and_type (parser, &document))
            {
              g_ptr_array_add (failed_subdocuments, document);

              if (error)
                {
                  *error =
                    g_error_copy (modulemd_subdocument_get_gerror (document));
                }
              MMD_YAML_ERROR_EVENT_RETURN_RETHROW (
                error, event, "Parse error during preprocessing");
            }

          /* Add all valid documents to the list */
          if (modulemd_subdocument_get_doctype (document) != G_TYPE_INVALID)
            {
              g_ptr_array_add (subdocuments, g_object_ref (document));
            }
          else
            {
              /* Any documents we're skipping should also go into this list */
              g_ptr_array_add (failed_subdocuments, g_object_ref (document));
            }

          g_clear_pointer (&document, g_object_unref);
          break;

        default:
          /* We received a YAML event we shouldn't expect at this level */
          MMD_YAML_ERROR_RETURN (error,
                                 "Unexpected YAML event during preprocessing");
          break;
        }

      yaml_event_delete (&event);
    }

  /* Iterate through the subdocuments and process them by type */
  for (gsize i = 0; i < subdocuments->len; i++)
    {
      subdocument = g_ptr_array_index (subdocuments, i);
      if (modulemd_subdocument_get_doctype (subdocument) ==
          MODULEMD_TYPE_MODULESTREAM)
        {
          result =
            _parse_subdocument (subdocument,
                                _parse_module_stream,
                                &object,
                                modulemd_subdocument_get_version (subdocument),
                                &subdocument_error);
        }
      /* Parsers for other types go here */
      else if (modulemd_subdocument_get_doctype (subdocument) ==
               MODULEMD_TYPE_DEFAULTS)
        {
          result =
            _parse_subdocument (subdocument,
                                _parse_defaults,
                                &object,
                                modulemd_subdocument_get_version (subdocument),
                                &subdocument_error);
        }
      else if (modulemd_subdocument_get_doctype (subdocument) ==
               MODULEMD_TYPE_TRANSLATION)
        {
          result =
            _parse_subdocument (subdocument,
                                _parse_translation,
                                &object,
                                modulemd_subdocument_get_version (subdocument),
                                &subdocument_error);
        }
      /* else if (document->type == <...>) */
      else
        {
          /* Unknown document type */
          g_set_error_literal (error,
                               MODULEMD_YAML_ERROR,
                               MODULEMD_YAML_ERROR_PARSE,
                               "Unknown document type");
          result = FALSE;
        }

      if (result)
        {
          g_ptr_array_add (objects, object);
        }
      else
        {
          modulemd_subdocument_set_gerror (subdocument, subdocument_error);
          g_clear_error (&subdocument_error);

          g_ptr_array_add (failed_subdocuments, g_object_ref (subdocument));

          g_debug ("Skipping invalid document");
          g_clear_error (error);
        }
    }

  if (data)
    {
      *data = g_ptr_array_ref (objects);
    }

  result = TRUE;

error:
  if (failures)
    {
      *failures = g_ptr_array_ref (failed_subdocuments);
    }

  return result;
}


static gboolean
_read_yaml_and_type (yaml_parser_t *parser, ModulemdSubdocument **subdocument)
{
  g_autoptr (ModulemdSubdocument) document = NULL;
  g_autoptr (GError) error = NULL;
  gboolean result = FALSE;
  gboolean done = FALSE;
  gboolean finish_invalid_document = FALSE;
  gsize depth = 0;
  g_autoptr (modulemd_yaml_string) yaml_string = NULL;
  MMD_INIT_YAML_EVENT (event);
  MMD_INIT_YAML_EVENT (value_event);
  yaml_emitter_t emitter;

  g_debug ("TRACE: entering _read_yaml_and_type");

  document = modulemd_subdocument_new ();

  yaml_string = g_malloc0_n (1, sizeof (modulemd_yaml_string));
  yaml_emitter_initialize (&emitter);

  yaml_emitter_set_output (&emitter, _write_yaml_string, (void *)yaml_string);

  yaml_stream_start_event_initialize (&event, YAML_UTF8_ENCODING);
  YAML_EMITTER_EMIT_WITH_ERROR_RETURN (
    &emitter, &event, &error, "Error starting stream");

  yaml_document_start_event_initialize (&event, NULL, NULL, NULL, 0);
  YAML_EMITTER_EMIT_WITH_ERROR_RETURN (
    &emitter, &event, &error, "Error starting document");


  while (!done)
    {
      value_event.type = YAML_NO_EVENT;
      YAML_PARSER_PARSE_WITH_ERROR_RETURN (
        parser, &event, &error, "Parser error");

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

        case YAML_SEQUENCE_START_EVENT:
        case YAML_MAPPING_START_EVENT: depth++; break;

        case YAML_SEQUENCE_END_EVENT:
        case YAML_MAPPING_END_EVENT: depth--; break;

        case YAML_SCALAR_EVENT:
          if (depth == 1 && !finish_invalid_document)
            {
              /* If we're in the root of the document, check for the
               * document type and version
               */

              if (!g_strcmp0 ((const gchar *)event.data.scalar.value,
                              "document"))
                {
                  if (modulemd_subdocument_get_doctype (document) !=
                      G_TYPE_INVALID)
                    {
                      /* We encountered document-type twice in the same
                       * document root mapping. This shouldn't ever happen
                       */
                      g_debug ("Document type specified more than once");
                      modulemd_subdocument_set_doctype (document,
                                                        G_TYPE_INVALID);

                      /*
                       * This is a recoverable parsing error, so we don't want
                       * to exit with FALSE.
                       */
                      finish_invalid_document = TRUE;

                      g_set_error (
                        &error,
                        MODULEMD_YAML_ERROR,
                        MODULEMD_YAML_ERROR_PARSE,
                        "Document type was specified more than once");

                      break;
                    }

                  YAML_PARSER_PARSE_WITH_ERROR_RETURN (
                    parser, &value_event, &error, "Parser error");

                  if (value_event.type != YAML_SCALAR_EVENT)
                    {
                      g_debug ("Document type not a scalar");
                      modulemd_subdocument_set_doctype (document,
                                                        G_TYPE_INVALID);

                      switch (value_event.type)
                        {
                        case YAML_SEQUENCE_START_EVENT:
                        case YAML_MAPPING_START_EVENT: depth++; break;

                        case YAML_SEQUENCE_END_EVENT:
                        case YAML_MAPPING_END_EVENT: depth--; break;

                        default: break;
                        }

                      /*
                       * This is a recoverable parsing error, so we don't want
                       * to exit with FALSE.
                       */
                      finish_invalid_document = TRUE;

                      g_set_error (&error,
                                   MODULEMD_YAML_ERROR,
                                   MODULEMD_YAML_ERROR_PARSE,
                                   "Document type was not a scalar value");

                      break;
                    }

                  if (g_strcmp0 ((const gchar *)value_event.data.scalar.value,
                                 "modulemd") == 0)
                    {
                      modulemd_subdocument_set_doctype (
                        document, MODULEMD_TYPE_MODULESTREAM);
                    }

                  else if (g_strcmp0 (
                             (const gchar *)value_event.data.scalar.value,
                             "modulemd-defaults") == 0)
                    {
                      modulemd_subdocument_set_doctype (
                        document, MODULEMD_TYPE_DEFAULTS);
                    }

                  else if (g_strcmp0 (
                             (const gchar *)value_event.data.scalar.value,
                             "modulemd-translations") == 0)
                    {
                      modulemd_subdocument_set_doctype (
                        document, MODULEMD_TYPE_TRANSLATION);
                    }
                  /* Handle additional types here */

                  else
                    {
                      /* Unknown document type */
                      modulemd_subdocument_set_doctype (document,
                                                        G_TYPE_INVALID);

                      finish_invalid_document = TRUE;

                      g_set_error (&error,
                                   MODULEMD_YAML_ERROR,
                                   MODULEMD_YAML_ERROR_PARSE,
                                   "Document type is not recognized "
                                   "[line %zu col %zu]",
                                   event.start_mark.line,
                                   event.start_mark.column);
                    }

                  g_debug (
                    "Document type: %s",
                    g_type_name (modulemd_subdocument_get_doctype (document)));
                }

              else if (g_strcmp0 ((const gchar *)event.data.scalar.value,
                                  "version") == 0)
                {
                  if (modulemd_subdocument_get_version (document) != 0)
                    {
                      g_debug ("Document version specified more than once");
                      modulemd_subdocument_set_doctype (document,
                                                        G_TYPE_INVALID);

                      /*
                       * This is a recoverable parsing error, so we don't want
                       * to exit with FALSE.
                       */
                      finish_invalid_document = TRUE;

                      g_set_error (
                        &error,
                        MODULEMD_YAML_ERROR,
                        MODULEMD_YAML_ERROR_PARSE,
                        "Document version was specified more than once");

                      break;
                    }

                  YAML_PARSER_PARSE_WITH_ERROR_RETURN (
                    parser, &value_event, &error, "Parser error");

                  if (value_event.type != YAML_SCALAR_EVENT)
                    {
                      g_debug ("Document version not a scalar");
                      modulemd_subdocument_set_doctype (document,
                                                        G_TYPE_INVALID);

                      /*
                       * This is a recoverable parsing error, so we don't want
                       * to exit with FALSE.
                       */
                      finish_invalid_document = TRUE;

                      g_set_error (&error,
                                   MODULEMD_YAML_ERROR,
                                   MODULEMD_YAML_ERROR_PARSE,
                                   "Document version was not a scalar");

                      switch (value_event.type)
                        {
                        case YAML_SEQUENCE_START_EVENT:
                        case YAML_MAPPING_START_EVENT: depth++; break;

                        case YAML_SEQUENCE_END_EVENT:
                        case YAML_MAPPING_END_EVENT: depth--; break;

                        default: break;
                        }

                      break;
                    }
                  modulemd_subdocument_set_version (
                    document,
                    g_ascii_strtoull (
                      (const gchar *)value_event.data.scalar.value, NULL, 10));

                  g_debug ("Document version: %" PRIx64,
                           modulemd_subdocument_get_version (document));
                }
            }
          break;

        default:
          /* Just fall through here. */
          break;
        }

      /* Copy this event to the string */
      YAML_EMITTER_EMIT_WITH_ERROR_RETURN (
        &emitter, &event, &error, "Error storing YAML event");

      if (value_event.type != YAML_NO_EVENT)
        {
          /* Copy this event to the string */
          YAML_EMITTER_EMIT_WITH_ERROR_RETURN (
            &emitter, &value_event, &error, "Error storing YAML event");
        }
    }

  yaml_stream_end_event_initialize (&event);
  YAML_EMITTER_EMIT_WITH_ERROR_RETURN (
    &emitter, &event, &error, "Error ending stream");

  /* If we get here with an invalid document type and no error */
  if (modulemd_subdocument_get_doctype (document) == G_TYPE_INVALID &&
      error == NULL)
    {
      g_set_error (&error,
                   MODULEMD_YAML_ERROR,
                   MODULEMD_YAML_ERROR_PARSE,
                   "Document type was unspecified or unknown");
    }

  result = TRUE;
error:
  yaml_emitter_delete (&emitter);

  modulemd_subdocument_set_gerror (document, error);

  /* Copy the string, even if it was only partial because it's still useful
   * to know where parsing broke
   */
  modulemd_subdocument_set_yaml (document, yaml_string->str);
  if (subdocument)
    *subdocument = g_object_ref (document);

  g_debug ("TRACE: exiting _read_yaml_and_type");
  return result;
}

static gboolean
_parse_subdocument (ModulemdSubdocument *subdocument,
                    ModulemdParsingFunc parse_func,
                    GObject **data,
                    guint64 version,
                    GError **error)
{
  gboolean result = FALSE;
  MMD_INIT_YAML_EVENT (event);
  gboolean done = FALSE;
  GObject *object = NULL;
  yaml_parser_t parser;

  yaml_parser_initialize (&parser);
  yaml_parser_set_input_string (
    &parser,
    (const unsigned char *)modulemd_subdocument_get_yaml (subdocument),
    strlen (modulemd_subdocument_get_yaml (subdocument)));

  while (!done)
    {
      YAML_PARSER_PARSE_WITH_ERROR_RETURN (
        &parser, &event, error, "Parser error");

      switch (event.type)
        {
        case YAML_STREAM_START_EVENT:
          /* Starting the stream here */
          break;

        case YAML_DOCUMENT_START_EVENT:
          if (!parse_func (&parser, &object, version, error))
            {
              goto error;
            }
          break;

        case YAML_DOCUMENT_END_EVENT:
          /* This document is complete. */
          break;

        case YAML_STREAM_END_EVENT: done = TRUE; break;

        default:
          /* We received a YAML event we shouldn't expect at this level */
          MMD_YAML_ERROR_EVENT_RETURN (
            error, event, "Unexpected YAML event at toplevel");
          break;
        }

      yaml_event_delete (&event);
    }

  *data = object;
  result = TRUE;

error:
  yaml_parser_delete (&parser);
  g_debug ("TRACE: exiting _parse_yaml");
  return result;
}


gboolean
_parse_modulemd_date (yaml_parser_t *parser, GDate **_date, GError **error)
{
  gboolean result = FALSE;
  MMD_INIT_YAML_EVENT (event);
  g_auto (GStrv) strv = NULL;

  YAML_PARSER_PARSE_WITH_ERROR_RETURN (parser, &event, error, "Parser error");
  if (event.type != YAML_SCALAR_EVENT)
    {
      MMD_YAML_ERROR_EVENT_RETURN (error, event, "Failed to parse date");
    }

  strv = g_strsplit ((const gchar *)event.data.scalar.value, "-", 4);

  if (!strv[0] || !strv[1] || !strv[2])
    {
      MMD_YAML_ERROR_EVENT_RETURN (
        error, event, "Date not in the form YYYY-MM-DD");
    }

  *_date = g_date_new_dmy (g_ascii_strtoull (strv[2], NULL, 10), /* Day */
                           g_ascii_strtoull (strv[1], NULL, 10), /* Month */
                           g_ascii_strtoull (strv[0], NULL, 10)); /* Year */

  result = TRUE;

error:
  return result;
}

gboolean
_simpleset_from_sequence (yaml_parser_t *parser,
                          ModulemdSimpleSet **_set,
                          GError **error)
{
  gboolean result = FALSE;
  MMD_INIT_YAML_EVENT (event);
  gboolean started = FALSE;
  gboolean done = FALSE;
  g_autoptr (ModulemdSimpleSet) set = NULL;

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

  g_debug ("TRACE: entering _simpleset_from_sequence");

  set = modulemd_simpleset_new ();


  while (!done)
    {
      YAML_PARSER_PARSE_WITH_ERROR_RETURN (
        parser, &event, error, "Parser error");

      switch (event.type)
        {
        case YAML_SEQUENCE_START_EVENT:
          /* Sequence has begun */
          started = TRUE;
          break;

        case YAML_SEQUENCE_END_EVENT:
          /* Sequence has concluded. Return */
          done = TRUE;
          break;

        case YAML_SCALAR_EVENT:
          if (!started)
            {
              MMD_YAML_ERROR_EVENT_RETURN (
                error, event, "Received scalar where sequence expected");
            }
          modulemd_simpleset_add (set, (const gchar *)event.data.scalar.value);
          break;

        default:
          /* We received a YAML event we shouldn't expect at this level */
          MMD_YAML_ERROR_EVENT_RETURN (
            error, event, "Unexpected YAML event in sequence");
          break;
        }
      yaml_event_delete (&event);
    }

  *_set = g_object_ref (set);
  result = TRUE;

error:
  g_debug ("TRACE: exiting _simpleset_from_sequence");
  return result;
}

gboolean
_hashtable_from_mapping (yaml_parser_t *parser,
                         GHashTable **_htable,
                         GError **error)
{
  gboolean result = FALSE;
  MMD_INIT_YAML_EVENT (event);
  MMD_INIT_YAML_EVENT (value_event);
  gboolean started = FALSE;
  gboolean done = FALSE;
  g_autoptr (GHashTable) htable = NULL;
  gchar *name = NULL;
  gchar *value = NULL;

  g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
  g_debug ("TRACE: entering _hashtable_from_mapping");

  htable = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);

  while (!done)
    {
      YAML_PARSER_PARSE_WITH_ERROR_RETURN (
        parser, &event, error, "Parser error");

      switch (event.type)
        {
        case YAML_MAPPING_START_EVENT:
          /* The dictionary has begun */
          started = TRUE;
          break;

        case YAML_MAPPING_END_EVENT:
          /* We've processed the whole dictionary */
          done = TRUE;
          break;

        case YAML_SCALAR_EVENT:
          if (!started)
            {
              MMD_YAML_ERROR_EVENT_RETURN (
                error, event, "Received scalar where mapping expected");
            }
          name = g_strdup ((const gchar *)event.data.scalar.value);
          YAML_PARSER_PARSE_WITH_ERROR_RETURN (
            parser, &value_event, error, "Parser error");
          if (value_event.type != YAML_SCALAR_EVENT)
            {
              g_free (name);
              MMD_YAML_ERROR_EVENT_RETURN (
                error, value_event, "Non-scalar value for dictionary.");
            }
          value = g_strdup ((const gchar *)value_event.data.scalar.value);
          yaml_event_delete (&value_event);

          /* Set this key and value to the hash table */
          g_hash_table_insert (htable, name, value);

          break;


        default:
          /* We received a YAML event we shouldn't expect at this level */
          MMD_YAML_ERROR_EVENT_RETURN (
            error, event, "Unexpected YAML event in sequence");
          break;
        }

      yaml_event_delete (&event);
    }
  *_htable = g_hash_table_ref (htable);

  result = TRUE;

error:

  g_debug ("TRACE: exiting _hashtable_from_mapping");
  return result;
}

/* Helper function to skip over sections that aren't yet implemented */
gboolean
_parse_skip (yaml_parser_t *parser, GError **error)
{
  MMD_INIT_YAML_EVENT (event);
  gboolean result = FALSE;
  gboolean done = FALSE;
  gsize depth = 0;

  while (!done)
    {
      YAML_PARSER_PARSE_WITH_ERROR_RETURN (
        parser, &event, error, "Parser error");

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

        case YAML_SEQUENCE_START_EVENT:
        case YAML_MAPPING_START_EVENT: depth++; break;

        case YAML_SEQUENCE_END_EVENT:
        case YAML_MAPPING_END_EVENT:
          depth--;

          if (depth <= 0)
            {
              /* We've come back up to the original level from which we
               * started
               */
              done = TRUE;
            }
          break;

        default:
          /* Just fall through here. */
          break;
        }
      yaml_event_delete (&event);
    }

  result = TRUE;
error:
  return result;
}