/*
 * Copyright (C) 2014 Collabora Ltd.
 *     Author: Nicolas Dufresne <nicolas.dufresne@collabora.co.uk>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 *
 */

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

#include "v4l2-utils.h"

/**************************/
/* Common device iterator */
/**************************/

#ifdef HAVE_GUDEV
#include <gudev/gudev.h>

struct _GstV4l2GUdevIterator
{
  GstV4l2Iterator parent;
  GList *devices;
  GUdevDevice *device;
  GUdevClient *client;
};

GstV4l2Iterator *
gst_v4l2_iterator_new (void)
{
  static const gchar *subsystems[] = { "video4linux", NULL };
  struct _GstV4l2GUdevIterator *it;

  it = g_new0 (struct _GstV4l2GUdevIterator, 1);

  it->client = g_udev_client_new (subsystems);
  it->devices = g_udev_client_query_by_subsystem (it->client, "video4linux");

  return (GstV4l2Iterator *) it;
}

gboolean
gst_v4l2_iterator_next (GstV4l2Iterator * _it)
{
  struct _GstV4l2GUdevIterator *it = (struct _GstV4l2GUdevIterator *) _it;
  const gchar *device_name;

  if (it->device)
    g_object_unref (it->device);

  it->device = NULL;
  it->parent.device_path = NULL;
  it->parent.device_name = NULL;

  if (it->devices == NULL)
    return FALSE;

  it->device = it->devices->data;
  it->devices = g_list_delete_link (it->devices, it->devices);

  device_name = g_udev_device_get_property (it->device, "ID_V4L_PRODUCT");
  if (!device_name)
    device_name = g_udev_device_get_property (it->device, "ID_MODEL_ENC");
  if (!device_name)
    device_name = g_udev_device_get_property (it->device, "ID_MODEL");

  it->parent.device_path = g_udev_device_get_device_file (it->device);
  it->parent.device_name = device_name;
  it->parent.sys_path = g_udev_device_get_sysfs_path (it->device);

  return TRUE;
}

void
gst_v4l2_iterator_free (GstV4l2Iterator * _it)
{
  struct _GstV4l2GUdevIterator *it = (struct _GstV4l2GUdevIterator *) _it;
  g_list_free_full (it->devices, g_object_unref);
  gst_object_unref (it->client);
  g_free (it);
}

#else /* No GUDEV */

struct _GstV4l2FsIterator
{
  GstV4l2Iterator parent;
  gint base_idx;
  gint video_idx;
  gchar *device;
};

GstV4l2Iterator *
gst_v4l2_iterator_new (void)
{
  struct _GstV4l2FsIterator *it;

  it = g_new0 (struct _GstV4l2FsIterator, 1);
  it->base_idx = 0;
  it->video_idx = -1;
  it->device = NULL;

  return (GstV4l2Iterator *) it;
}

gboolean
gst_v4l2_iterator_next (GstV4l2Iterator * _it)
{
  struct _GstV4l2FsIterator *it = (struct _GstV4l2FsIterator *) _it;
  static const gchar *dev_base[] = { "/dev/video", "/dev/v4l2/video", NULL };
  gchar *device = NULL;

  g_free ((gchar *) it->parent.device_path);
  it->parent.device_path = NULL;

  while (device == NULL) {
    it->video_idx++;

    if (it->video_idx >= 64) {
      it->video_idx = 0;
      it->base_idx++;
    }

    if (dev_base[it->base_idx] == NULL) {
      it->video_idx = 0;
      break;
    }

    device = g_strdup_printf ("%s%d", dev_base[it->base_idx], it->video_idx);

    if (g_file_test (device, G_FILE_TEST_EXISTS)) {
      it->parent.device_path = device;
      break;
    }

    g_free (device);
    device = NULL;
  }

  return it->parent.device_path != NULL;
}

void
gst_v4l2_iterator_free (GstV4l2Iterator * _it)
{
  struct _GstV4l2FsIterator *it = (struct _GstV4l2FsIterator *) _it;
  g_free ((gchar *) it->parent.device_path);
  g_free (it);
}

#endif

void
gst_v4l2_clear_error (GstV4l2Error * v4l2err)
{
  if (v4l2err) {
    g_clear_error (&v4l2err->error);
    g_free (v4l2err->dbg_message);
    v4l2err->dbg_message = NULL;
  }
}

void
gst_v4l2_error (gpointer element, GstV4l2Error * v4l2err)
{
  GError *error;

  if (!v4l2err || !v4l2err->error)
    return;

  error = v4l2err->error;

  if (error->message)
    GST_WARNING_OBJECT (element, "error: %s", error->message);

  if (v4l2err->dbg_message)
    GST_WARNING_OBJECT (element, "error: %s", v4l2err->dbg_message);

  gst_element_message_full (GST_ELEMENT (element), GST_MESSAGE_ERROR,
      error->domain, error->code, error->message, v4l2err->dbg_message,
      v4l2err->file, v4l2err->func, v4l2err->line);

  error->message = NULL;
  v4l2err->dbg_message = NULL;

  gst_v4l2_clear_error (v4l2err);
}
