#include <gst/gst.h>
#include "../../gst/validate/validate.h"
#include "../../gst/validate/gst-validate-utils.h"

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#define EXTRA_CHECKS_WRONG_NUMBER_OF_INSTANCES g_quark_from_static_string ("extrachecks::wrong-number-of-instances")

typedef struct
{
  gchar *pname;
  gchar *klass;
  gint expected_n_instances;
  gint n_instances;
} CheckNumInstanceData;

static CheckNumInstanceData *
gst_validate_check_num_instances_data_new (GstStructure * check)
{
  CheckNumInstanceData *data = g_new0 (CheckNumInstanceData, 1);

  if (!gst_structure_get_int (check, "num-instances",
          &data->expected_n_instances)) {
    g_error ("[CONFIG ERROR] Mandatory field `num-instances` not found in "
        "extra-check `num-instances`");
    goto failed;
  }

  data->pname = g_strdup (gst_structure_get_string (check, "pipeline-name"));
  if (!data->pname) {
    g_error ("[CONFIG ERROR] Mandatory field `pipeline` not found in "
        "extra-check `num-instances`");
    goto failed;
  }

  data->klass = g_strdup (gst_structure_get_string (check, "element-klass"));
  if (!data->klass) {
    g_error ("[CONFIG ERROR] Mandatory field `element-klass` not found in "
        "extra-check `num-instances`");
    goto failed;
  }

  return data;

failed:
  g_free (data);
  g_free (data->klass);

  return NULL;
}

static void
gst_validate_check_num_instances_data_free (CheckNumInstanceData * data)
{
  g_free (data->pname);
  g_free (data);
}

static void
gst_validate_check_num_instances (GstValidateOverride * o,
    GstValidateMonitor * monitor, GstElement * nelem)
{
  gchar *pname;
  CheckNumInstanceData *data = g_object_get_data (G_OBJECT (o), "check-data");
  GstPipeline *pipe = gst_validate_monitor_get_pipeline (monitor);

  if (!pipe)
    return;

  pname = gst_object_get_name (GST_OBJECT (pipe));
  if (g_strcmp0 (data->pname, pname))
    goto done;

  if (!gst_validate_element_has_klass (nelem, data->klass))
    return;

  data->n_instances++;

  if (data->n_instances > data->expected_n_instances) {
    GST_VALIDATE_REPORT (o, EXTRA_CHECKS_WRONG_NUMBER_OF_INSTANCES,
        "%d instances allows in pipeline %s but already %d where added.",
        data->expected_n_instances, pname, data->n_instances);
  }
  GST_ERROR_OBJECT (nelem, "HERE I AM %d", data->n_instances);

done:
  g_free (pname);
  gst_object_unref (pipe);
}

static void
runner_stopping (GstValidateRunner * runner, GstValidateOverride * o)
{
  CheckNumInstanceData *data = g_object_get_data (G_OBJECT (o), "check-data");

  if (data->expected_n_instances != data->n_instances) {
    GST_VALIDATE_REPORT (o, EXTRA_CHECKS_WRONG_NUMBER_OF_INSTANCES,
        "%d instances expected in pipeline %s but %d where added.",
        data->expected_n_instances, data->pname, data->n_instances);
  }
}

static void
_runner_set (GObject * object, GParamSpec * pspec, gpointer user_data)
{
  GstValidateRunner *runner =
      gst_validate_reporter_get_runner (GST_VALIDATE_REPORTER (object));

  g_signal_connect (runner, "stopping", G_CALLBACK (runner_stopping), object);
  gst_object_unref (runner);
}

static void
gst_validate_add_num_instances_check (GstStructure * structure)
{
  CheckNumInstanceData *data =
      gst_validate_check_num_instances_data_new (structure);
  GstValidateOverride *o = gst_validate_override_new ();

  g_object_set_data_full (G_OBJECT (o), "check-data", data,
      (GDestroyNotify) gst_validate_check_num_instances_data_free);

  gst_validate_override_set_element_added_handler (o,
      gst_validate_check_num_instances);

  g_signal_connect (o, "notify::validate-runner", G_CALLBACK (_runner_set),
      NULL);

  gst_validate_override_register_by_type (GST_TYPE_BIN, o);
  gst_object_unref (o);
}

static gboolean
gst_validate_extra_checks_init (GstPlugin * plugin)
{
  GList *config, *tmp;
  config = gst_validate_plugin_get_config (plugin);

  if (!config)
    return TRUE;

  for (tmp = config; tmp; tmp = tmp->next) {
    GstStructure *check = tmp->data;

    if (gst_structure_has_field (check, "num-instances"))
      gst_validate_add_num_instances_check (check);
  }

  gst_validate_issue_register (gst_validate_issue_new
      (EXTRA_CHECKS_WRONG_NUMBER_OF_INSTANCES,
          "The configured number of possible instances of an element type"
          " in a pipeline is not respected.",
          "The `num-instances` extra checks allow user to make sure that"
          " a previously defined number of instances of an element is added"
          " in a given pipeline, that test failed.",
          GST_VALIDATE_REPORT_LEVEL_CRITICAL));

  return TRUE;
}

GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
    GST_VERSION_MINOR,
    extrachecks,
    "GstValidate plugin that implements extra, configurable tests.",
    gst_validate_extra_checks_init, VERSION, "LGPL", GST_PACKAGE_NAME,
    GST_PACKAGE_ORIGIN)