/* Video4Linux operation for GEGL
 *
 * GEGL is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 3 of the License, or (at your option) any later version.
 *
 * GEGL 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with GEGL; if not, see <https://www.gnu.org/licenses/>.
 *
 * Copyright 2004-2008 Øyvind Kolås <pippin@gimp.org>
 *                     originally written for gggl
 */

#include "config.h"
#include <glib/gi18n-lib.h>


#ifdef GEGL_PROPERTIES

property_file_path (path,   _("Path"), "/dev/video0")
  description (_("Path to v4l device"))
property_int  (width,  _("Width"), 320)
  description (_("Width for rendered image"))
  value_range (0, G_MAXINT)
property_int  (height, _("Height"), 240)
  description (_("Height for rendered image"))
  value_range (0, G_MAXINT)
property_int  (frame,  _("Frame"), 0)
  description (_("current frame number, can be changed to trigger a reload of the image."))
  value_range (0, G_MAXINT)
property_int  (fps,    _("FPS"), 0)
  description (_("autotrigger reload this many times a second."))
  value_range (0, G_MAXINT)

#else

#define GEGL_OP_SOURCE
#define GEGL_OP_NAME     v4l
#define GEGL_OP_C_SOURCE v4l.c

#include "gegl-op.h"

#include "v4lutils/v4lutils.h"
#include "v4lutils/v4lutils.c"

#define GEGL_OP_C_SOURCE v4l.c

typedef struct
{
  gint active;
  gint w;
  gint h;
  gint w_stored;
  gint h_stored;
  gint frame;
  gint decode;
  v4ldevice *vd;
} Priv;

static void
init (GeglProperties *o)
{
  Priv *p = (Priv*)o->user_data;

  if (p==NULL)
    {
      p = g_new0 (Priv, 1);
      o->user_data = (void*) p;
    }

  p->w = 320;
  p->h = 240;
}


static GeglRectangle
get_bounding_box (GeglOperation *operation)
{
  GeglRectangle result ={0,0,320,200};
  GeglProperties *o = GEGL_PROPERTIES (operation);

  result.width = o->width;
  result.height = o->height;
  return result;
}

static void
prepare (GeglOperation *operation)
{
  GeglProperties *o = GEGL_PROPERTIES (operation);
  Priv *p= (Priv*)o->user_data;

  if (p == NULL)
    init (o);
  p = (Priv*)o->user_data;

  gegl_operation_set_format (operation, "output",
                            babl_format_new (
                                  babl_model ("R'G'B'"),
                                  babl_type ("u8"),
                                  babl_component ("B'"),
                                  babl_component ("G'"),
                                  babl_component ("R'"),
                                  NULL));

  p->w = o->width;
  p->h = o->height;

  if (!p->vd)
    {
      p->vd = g_malloc0 (sizeof (v4ldevice));

      if (v4lopen (o->path, p->vd))
        return;

      p->active = 1;

      if (v4lmmap (p->vd))
        return;

      v4lsetdefaultnorm (p->vd, VIDEO_MODE_PAL);
      v4lgetcapability (p->vd);

      if (!(p->vd->capability.type & VID_TYPE_CAPTURE)) {
          g_warning (
               "video_init: This device seems not to support video capturing.\n");
         return;
      }
    }

  if (p->w != p->w_stored || p->h != p->h_stored)
    {

      if (p->w > p->vd->capability.maxwidth
          || p->h > p->vd->capability.maxheight)
        {
          p->w = p->vd->capability.maxwidth;
          p->h = p->vd->capability.maxheight;
          o->width = p->w;
          o->height = p->h;
          g_warning ( "capturing size is set to %dx%d.\n", p->w, p->h);
        }
      else if (p->w < p->vd->capability.minwidth
               || p->h < p->vd->capability.minheight)
        {
          p->w = p->vd->capability.minwidth;
          p->h = p->vd->capability.minheight;
          o->width = p->w;
          o->height = p->h;
          g_warning ( "capturing size is set to %dx%d.\n", p->w, p->h);
        }

      p->w_stored = p->w;
      p->h_stored = p->h;

      /* FIXME: try other palettes as well, do some profiling on the spca
       * based cameras to see what is the ideal format wrt performance
       */

      if (!v4lsetpalette (p->vd, VIDEO_PALETTE_RGB24))
        {
           p->decode=0;
        }
      else if (!v4lsetpalette (p->vd, VIDEO_PALETTE_YUV420P))
        {
           p->decode=1;
        }
      else
        {
          g_warning ( "oops,. no usable v4l format found\n");
          return;
        }
      v4lgrabinit (p->vd, p->w, p->h);
      v4lgrabf (p->vd);
    }
}

static void
finalize (GObject *object)
{
 GeglProperties *o = GEGL_PROPERTIES (object);

  if (o->user_data)
    {
      Priv *p = (Priv*)o->user_data;

      if (p->vd)
        {
          v4lmunmap (p->vd);
          v4lclose (p->vd);
          g_free (p->vd);
        }

      g_clear_pointer (&o->user_data, g_free);
    }

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

static gboolean update (gpointer operation)
{
  GeglRectangle bounds = gegl_operation_get_bounding_box (operation);
  gegl_operation_invalidate (operation, &bounds, FALSE);
  return TRUE;
}

static gboolean
process (GeglOperation       *operation,
         GeglBuffer          *output,
         const GeglRectangle *result,
         gint                 level)
{
  GeglProperties *o = GEGL_PROPERTIES (operation);
  Priv       *p = (Priv*)o->user_data;
  guchar     *capbuf;
  static gboolean inited = FALSE;

    if (!inited && o->fps != 0)
      {
        inited= TRUE;
        g_timeout_add (1000/o->fps, update, operation);
      }

  if (!p->active)
    return FALSE;

  v4lgrabf (p->vd);
  capbuf = v4lgetaddress (p->vd);
  v4lsyncf (p->vd);

  if (!capbuf)
    {
      g_warning ("no capbuf found");
      return FALSE;
    }

  if (p->decode)
    {
      guchar foobuf[o->width*o->height*3];
          /* XXX: foobuf is unneeded the conversions resets for every
           * scanline and could thus have been done in a line by line
           * manner an fed into the output buffer
           */
      gint y;
      for (y = 0; y < p->h; y++)
        {
          gint       x;

          guchar *dst = &foobuf[y*p->w*3];
          guchar *ysrc = (guchar *) (capbuf + (y) * (p->w) * 1);
          guchar *usrc = (guchar *) (capbuf + (p->w) * (p->h) + (y) / 2 * (p->w) / 2);
          guchar *vsrc = (guchar *) (capbuf + (p->w) * (p->h) + ((p->w) * (p->h)) / 4 + (y) / 2 * (p->w) / 2);

          for (x = 0; x < p->w; x++)
            {

              gint       R, G, B;

#ifndef byteclamp
#define byteclamp(j) do{if(j<0)j=0; else if(j>255)j=255;}while(0)
#endif

#define YUV82RGB8(Y,U,V,R,G,B)do{\
                  R= ((Y<<15)                 + 37355*(V-128))>>15;\
                  G= ((Y<<15) -12911* (U-128) - 19038*(V-128))>>15;\
                  B= ((Y<<15) +66454* (U-128)                )>>15;\
                  byteclamp(R);\
                  byteclamp(G);\
                  byteclamp(B);\
              }while(0)

      /* the format support for this code is not very good, but it
       * works for the devices I have tested with, conversion even
       * for chroma subsampled images is something we should let
       * babl handle.
       */
              YUV82RGB8 (*ysrc, *usrc, *vsrc, R, G, B);
              dst[0] = B;
              dst[1] = G;
              dst[2] = R;

              dst += 3;
              ysrc++;
              if (x % 2)
                {
                  usrc++;
                  vsrc++;
                }
            }
        }
      gegl_buffer_set (output, NULL, 0, NULL, foobuf, GEGL_AUTO_ROWSTRIDE);
    }
  else
    {
      gegl_buffer_set (output, NULL, 0, NULL, capbuf, GEGL_AUTO_ROWSTRIDE);
    }
  return  TRUE;
}

static GeglRectangle
get_cached_region (GeglOperation       *operation,
                   const GeglRectangle *roi)
{
  return get_bounding_box (operation);
}

static void
gegl_op_class_init (GeglOpClass *klass)
{
  GeglOperationClass       *operation_class;
  GeglOperationSourceClass *source_class;

  G_OBJECT_CLASS (klass)->finalize = finalize;

  operation_class = GEGL_OPERATION_CLASS (klass);
  source_class    = GEGL_OPERATION_SOURCE_CLASS (klass);

  source_class->process             = process;
  operation_class->get_bounding_box = get_bounding_box;
  operation_class->get_cached_region = get_cached_region;
  operation_class->prepare          = prepare;

  gegl_operation_class_set_keys (operation_class,
    "name",         "gegl:v4l",
    "title",        _("Video4Linux Frame Source"),
    "categories",   "input:video",
    "description",  _("Video4Linux input, webcams framegrabbers and similar devices."),
    NULL);
}


#endif
