/*
 * Evil evil evil hack to get VBI apps to cooperate with the vbi proxy.
 * This is based on artsdsp, which again is based on the original esddsp,
 * which esd uses to do the same for audio.
 *
 * Copyright (C) 1998 Manish Singh <yosh@gimp.org>
 * Copyright (C) 2000 Stefan Westerfeld <stefan@space.twc.de> (artsd port)
 * Copyright (C) 2004 Tom Zoerner (VBI port)
 *
 * 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., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */

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

#if defined(ENABLE_PROXY) && defined(ENABLE_V4L)

#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <string.h>
#include <stdarg.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <dlfcn.h>

#include "io.h"
#include "proxy-msg.h"
#include "proxy-client.h"

#if !defined (__NetBSD__) && !defined (__FreeBSD__) && !defined (__FreeBSD_kernel__)
#include "videodev.h"
#include "videodev2k.h"
# define BASE_VIDIOCPRIVATE      192
# define BTTV_VERSION            _IOR('v' , BASE_VIDIOCPRIVATE+6, int)
# define BTTV_VBISIZE            _IOR('v' , BASE_VIDIOCPRIVATE+8, int)
#endif


#if defined(HAVE_IOCTL_INT_INT_DOTS)
#define ioctl_request_t int
#elif defined(HAVE_IOCTL_INT_ULONG_DOTS)
#define ioctl_request_t unsigned long
#elif defined(HAVE_IOCTL_INT_ULONGINT_DOTS)
#define ioctl_request_t unsigned long int
#else
#error "unknown ioctl type (check config.h, adapt configure test)..."
#endif
#define fcntl_request_t int

/*
 * original C library functions
 */
typedef int (*orig_open_ptr) (const char *pathname, int flags, ...);
typedef int (*orig_close_ptr) (int fd);
typedef int (*orig_ioctl_ptr) (int fd, ioctl_request_t request, ...);
typedef int (*orig_fcntl_ptr) (int fd, fcntl_request_t request, ...);
typedef ssize_t(*orig_write_ptr) (int fd, const void *buf, size_t count);
typedef ssize_t(*orig_read_ptr) (int fd, void *buf, size_t count);
typedef int (*orig_munmap_ptr) (void *start, size_t length);

static orig_open_ptr orig_open;
static orig_close_ptr orig_close;
static orig_ioctl_ptr orig_ioctl;
static orig_fcntl_ptr orig_fcntl;
static orig_write_ptr orig_write;
static orig_read_ptr orig_read;

static int vbi_chains_init = 0;
static int vbi_chains_debug;
static int vbi_chains_working;
static char *vbi_chains_device;

static vbi_proxy_client *p_proxy_client;
static int vbi_buf_size;
static unsigned int vbi_seq_no;
static int vbi_fd;
static int vbi_fd_nonblocking;

#define CHECK_INIT() if(!vbi_chains_init) vbi_chains_doinit();

#define dprintf1(fmt, arg...)    do {if (vbi_chains_debug >= 1) fprintf(stderr, "proxy-chains: " fmt, ## arg);} while(0)
#define dprintf2(fmt, arg...)    do {if (vbi_chains_debug >= 2) fprintf(stderr, "proxy-chains: " fmt, ## arg);} while(0)

/*
 * Initialization - maybe this should be either be a startup only called
 * routine, or use pthread locks to prevent strange effects in multithreaded
 * use (however it seems highly unlikely that an application would create
 * multiple threads before even using one of redirected the system functions
 * once).
 */

static void vbi_chains_doinit(void)
{
   const char *env;
   char *end;

   vbi_chains_init = 1;

   /* retrieve path to VBI device (ignore if NULL) */
   vbi_chains_device = getenv("VBIPROXY_DEVICE");
   if (vbi_chains_device == NULL)
   {
      fprintf(stderr, "VBIPROXY_DEVICE environment variable not set - disabling proxy\n");
   }
   else
   {
      if (strlen(vbi_chains_device) > 0)
         dprintf1("Will redirect access to device %s\n", vbi_chains_device);
      else
         dprintf1("No device specified: will redirect access to any VBI device\n");
   }

   /* set debug flag */
   env = getenv("VBIPROXY_DEBUG");
   if (env != NULL)
   {
      vbi_chains_debug = strtol(env, &end, 0);
      if ((*env == 0) || (*end != 0))
      {
         fprintf(stderr, "VBIPROXY_DEBUG='%s': not a number - setting debug level 1\n", env);
         vbi_chains_debug = 1;
      }
   }

   /* resolve original symbols */
   orig_open = (orig_open_ptr) dlsym(RTLD_NEXT, "open");
   orig_close = (orig_close_ptr) dlsym(RTLD_NEXT, "close");
   orig_write = (orig_write_ptr) dlsym(RTLD_NEXT, "write");
   orig_read = (orig_read_ptr) dlsym(RTLD_NEXT, "read");
   orig_ioctl = (orig_ioctl_ptr) dlsym(RTLD_NEXT, "ioctl");
   orig_fcntl = (orig_fcntl_ptr) dlsym(RTLD_NEXT, "fcntl");

   /* initialize module variables */
   p_proxy_client = NULL;
   vbi_fd = -1;
   vbi_chains_working = 0;
}

/* returns 1 if the filename points to a VBI device */
static int is_vbi_device(const char *pathname)
{
   if ((pathname != NULL) && (vbi_chains_device != NULL))
   {
      if (*vbi_chains_device != 0)
         return (strcmp(pathname, vbi_chains_device) == 0);
      else
         return (strncmp(pathname, "/dev/vbi", 8) == 0) || (strncmp(pathname, "/dev/v4l/vbi", 12) == 0);
   }
   else
      return 0;
}

int open(const char *pathname, int flags, ...)
{
   va_list args;
   mode_t mode = 0;

   CHECK_INIT();

   /*
    * After the documentation, va_arg is not safe if there is no argument to
    * get "random errors will occur", so only get it in case O_CREAT is set,
    * and hope that passing 0 to the orig_open function in all other cases
    * will work.
    */
   va_start(args, flags);
   if (flags & O_CREAT)
   {
      /* The compiler will select one of these at compile-tyime if -O is used.
       * Otherwise, it may be deferred until runtime.
       */
      if (sizeof(int) >= sizeof(mode_t))
      {
         mode = va_arg(args, int);
      }
      else
      {
         mode = va_arg(args, mode_t);
      }
   }
   va_end(args);

   if (!is_vbi_device(pathname) || vbi_chains_working)
   {
      return orig_open(pathname, flags, mode);
   }
   else
   {
      unsigned int services = VBI_SLICED_VBI_625 | VBI_SLICED_VBI_525;
      vbi_raw_decoder *p_dec;
      vbi_capture *p_capt;
      const char *p_client;
      char *p_errmsg = NULL;

      dprintf1("hijacking open on %s...\n", pathname);

      if (p_proxy_client == NULL)
      {
         p_client = getenv("VBIPROXY_CLIENT");
         if (p_client == NULL)
            p_client = "vbi-chain";

         vbi_chains_working = 1;
         p_proxy_client = vbi_proxy_client_create(pathname, p_client,
                                                  VBI_PROXY_CLIENT_NO_STATUS_IND,
                                                  &p_errmsg, vbi_chains_debug);
         if (p_proxy_client != NULL)
         {
            p_capt = vbi_capture_proxy_new(p_proxy_client, 5, 0, &services, 0, &p_errmsg);
            if (p_capt != NULL)
            {
               vbi_fd = vbi_capture_fd(p_capt);

               p_dec = vbi_capture_parameters(p_capt);
               if (p_dec != NULL)
                  vbi_buf_size = (p_dec->count[0] + p_dec->count[1]) * 2048;
               else
                  vbi_buf_size = 0;     /* ERROR */

               vbi_seq_no = 0;
               vbi_fd_nonblocking = 0;
            }
            else
            {
               int save_errno = errno;

               vbi_proxy_client_destroy(p_proxy_client);
               p_proxy_client = NULL;

               errno = save_errno;
            }
         }
         vbi_chains_working = 0;

         if (p_errmsg != NULL)
         {
            dprintf1("Failed to connect to proxy: %s\n", p_errmsg);
            free(p_errmsg);
         }

         if ((vbi_fd != -1) || (errno != 2))
         {
            dprintf2("open returns %d errno=%d (%s)\n", vbi_fd, errno, strerror(errno));
            return vbi_fd;
         }
         else
         {
            dprintf1("proxy not running - trying the actual device...\n");

            return orig_open(pathname, flags, mode);
         }
      }
      else
      {
         errno = EBUSY;
         return -1;
      }
   }
}

int ioctl(int fd, ioctl_request_t request, ...)
{
   /*
    * FreeBSD needs ioctl with varargs. However I have no idea how to "forward"
    * the variable args ioctl to the orig_ioctl routine. So I expect the ioctl
    * to have exactly one pointer-like parameter and forward that, hoping that
    * it works
    */
   va_list args;
   void *argp;
   va_start(args, request);
   argp = va_arg(args, void *);
   va_end(args);

   CHECK_INIT();

   if ((fd != vbi_fd) || vbi_chains_working)
   {
      return orig_ioctl(fd, request, argp);
   }
   else if (vbi_fd == -1)
   {
      errno = EBADF;
      return -1;
   }
   else
   {
      /*int *arg = (int *) argp; */
      dprintf1("hijacking ioctl (%d : %x - %p)\n",
               (int) fd, (int) request, (void *) ((argp != NULL) ? argp : "(NULL)"));

      switch (request)
      {
         /* ----------------------------- V4L2 -------------------------------- */

         case VIDIOC_QUERYCAP:
         {
            struct v4l2_capability *p_cap = argp;
            memset(p_cap, 0, sizeof(p_cap[0]));
            strcpy((char *) p_cap->driver, "VBI Proxy");
            strcpy((char *) p_cap->card, "unknown");
            strcpy((char *) p_cap->bus_info, "");
            p_cap->version = VBIPROXY_VERSION;
            p_cap->capabilities = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_VBI_CAPTURE;
            return 0;
         }

         case VIDIOC_G_FMT:
         {
            struct v4l2_format *p_fmt = argp;
            vbi_capture *p_capt;
            vbi_raw_decoder *p_dec;

            if (p_fmt->type == V4L2_BUF_TYPE_VBI_CAPTURE)
            {
               p_capt = vbi_proxy_client_get_capture_if(p_proxy_client);
               p_dec = vbi_capture_parameters(p_capt);
               memset(p_fmt, 0, sizeof(p_fmt[0]));
               p_fmt->type = V4L2_BUF_TYPE_VBI_CAPTURE;
               p_fmt->fmt.vbi.sampling_rate = p_dec->sampling_rate;
               p_fmt->fmt.vbi.samples_per_line = p_dec->bytes_per_line;
               p_fmt->fmt.vbi.offset = p_dec->offset;
               p_fmt->fmt.vbi.sample_format = V4L2_PIX_FMT_GREY;
               p_fmt->fmt.vbi.start[0] = p_dec->start[0];
               p_fmt->fmt.vbi.count[0] = p_dec->count[0];
               p_fmt->fmt.vbi.start[1] = p_dec->start[1];
               p_fmt->fmt.vbi.count[1] = p_dec->count[1];
               p_fmt->fmt.vbi.flags = 0;
            }
            return 0;
         }
         case VIDIOC_S_FMT:
         case VIDIOC_TRY_FMT:
         {
            /* XXX TODO: zvbi API does not allow to change params for raw capture */
#if 0
            struct v4l2_format *p_fmt = argp;
            char *p_errmsg = NULL;
            unsigned int services = VBI_SLICED_VBI_625 | VBI_SLICED_VBI_525;
            int res;
            res = vbi_proxy_client_update_services(p_proxy_client, TRUE, TRUE, &services, 0, &p_errmsg);
            if (p_errmsg != NULL)
            {
               dprintf1("Failed to update services: %s\n", p_errmsg);
               free(p_errmsg);
            }
            return res;
#else
            errno = EINVAL;
            return -1;
#endif
         }

         case VIDIOC_G_PRIORITY:
         case VIDIOC_S_PRIORITY:
         {
            enum v4l2_priority * prio = (enum v4l2_priority *) ((long) argp);
            vbi_channel_profile chn_profile;

            memset(&chn_profile, 0, sizeof(chn_profile));
            chn_profile.is_valid = TRUE;
            chn_profile.min_duration = 1;
            chn_profile.exp_duration = 1;
            return vbi_proxy_client_channel_request(p_proxy_client, *prio, &chn_profile);
         }


         /* ----------------------------- V4L1 -------------------------------- */

         case VIDIOCSVBIFMT:
            errno = EINVAL;
            return -1;

         case VIDIOCGVBIFMT:
         {
            vbi_capture *p_capt;
            vbi_raw_decoder *p_dec;
            struct vbi_format fmt;

            p_capt = vbi_proxy_client_get_capture_if(p_proxy_client);
            p_dec = vbi_capture_parameters(p_capt);
            memset(&fmt, 0, sizeof(fmt));
            fmt.sampling_rate = p_dec->sampling_rate;
            fmt.samples_per_line = p_dec->bytes_per_line;
            fmt.sample_format = VIDEO_PALETTE_RAW;
            fmt.start[0] = p_dec->start[0];
            fmt.count[0] = p_dec->count[0];
            fmt.start[1] = p_dec->start[1];
            fmt.count[1] = p_dec->count[1];
            fmt.flags = 0;
            memcpy(argp, &fmt, sizeof(fmt));
            return 0;
         }

         case BTTV_VERSION:
            dprintf1("ioctl BTTV_VERSION\n");
            return ((7 << 16) | (100 << 8));

         case BTTV_VBISIZE:
         {
            vbi_capture *p_capt;
            vbi_raw_decoder *p_dec;
            int size;

            p_capt = vbi_proxy_client_get_capture_if(p_proxy_client);
            p_dec = vbi_capture_parameters(p_capt);
            size = (p_dec->count[0] + p_dec->count[1]) * p_dec->bytes_per_line;
            dprintf1("ioctl BTTV_VBISIZE: %d\n", size);
            return size;
         }

         default:
            /* pass the ioctl to daemon via RPC
            ** (note not all requests are allowed, but this is checked internally) */
            return vbi_proxy_client_device_ioctl(p_proxy_client, request, argp);
      }
   }
}

int fcntl(int fd, fcntl_request_t request, ...)
{
   va_list args;
   void *argp;
   va_start(args, request);
   argp = va_arg(args, void *);
   va_end(args);

   CHECK_INIT();

   if ((fd != vbi_fd) || vbi_chains_working)
   {
      return orig_fcntl(fd, request, argp);
   }
   else if (vbi_fd == -1)
   {
      errno = EBADF;
      return -1;
   }
   else
   {
      dprintf2("hijacking fcntl (%d : %x - %p)\n",
               (int) fd, (int) request, (void *) ((argp != NULL) ? argp : "(no 3rd arg)"));
      if (request == F_SETFL)
      {
         vbi_fd_nonblocking = (((long) argp & O_NONBLOCK) != 0);
         dprintf1("Setting NONBLOCK mode flag: %d\n", vbi_fd_nonblocking);
         return 0;
      }
      else if (request == F_GETFL)
      {
         return (orig_fcntl(fd, request, argp) & ~O_NONBLOCK) | (vbi_fd_nonblocking ? O_NONBLOCK : 0);
      }
      else
         return orig_fcntl(fd, request, argp);
   }
}


int close(int fd)
{
   CHECK_INIT();

   if ((fd != vbi_fd) || vbi_chains_working)
   {
      return orig_close(fd);
   }
   else if (vbi_fd == -1)
   {
      errno = EBADF;
      return -1;
   }
   else
   {
      dprintf1("close...\n");
      vbi_chains_working = 1;

      vbi_proxy_client_destroy(p_proxy_client);
      p_proxy_client = NULL;
      vbi_fd = -1;

      vbi_chains_working = 0;
      return 0;
   }
}

ssize_t write(int fd, const void *buf, size_t count)
{
   CHECK_INIT();

   if ((fd != vbi_fd) || vbi_chains_working)
   {
      return orig_write(fd, buf, count);
   }
   else if (vbi_fd == -1)
   {
      errno = EBADF;
      return -1;
   }
   else
   {
      dprintf1("write() called for VBI - ignored\n");
      /* write access to VBI device is useless */
      /* indicate nothing's been written */
      return 0;
   }
}

ssize_t read(int fd, void *buf, size_t count)
{
   CHECK_INIT();

   if ((fd != vbi_fd) || vbi_chains_working)
   {
      return orig_read(fd, buf, count);
   }
   else if (vbi_fd == -1)
   {
      errno = EBADF;
      return -1;
   }
   else
   {
      vbi_capture *p_capt;
      vbi_capture_buffer *p_capt_buf;
      struct timeval timeout;
      double timestamp;
      int result;

      dprintf2("read %d bytes buf=0x%lX\n", (int) count, (long) buf);
      vbi_chains_working = 1;

      p_capt = vbi_proxy_client_get_capture_if(p_proxy_client);
      timeout.tv_sec = (vbi_fd_nonblocking ? 0 : 60 * 60 * 24);
      timeout.tv_usec = 0;

      if (count >= (size_t) vbi_buf_size)
      {
         /* buffer is large enough -> capture directly into the user buffer */
         result = vbi_capture_read_raw(p_capt, buf, &timestamp, &timeout);
         if (result > 0)
         {
            /* stamp frame sequence number into last 4 byes of each frame's data */
            *(unsigned int *) ((long) buf + count - 4) = vbi_seq_no++;

            result = vbi_buf_size;
         }
         else if (result == 0)
         {
            result = -1;
            errno = EAGAIN;
         }
      }
      else
      {
         /* buffer not large enough -> copy manually */
         result = vbi_capture_pull_raw(p_capt, &p_capt_buf, &timeout);
         if (result > 0)
         {
            /* copy requested portion into user buffer (rest of frame's data is discarded) */
            if (count > (size_t) p_capt_buf->size)
               count = p_capt_buf->size;
            memcpy(buf, p_capt_buf->data, count);

            *(unsigned int *) ((long) buf + count - 4) = vbi_seq_no++;

            result = count;
         }
         else if (result == 0)
         {
            result = -1;
            errno = EAGAIN;
         }
      }
      vbi_chains_working = 0;
      dprintf2("read returns %d (of %d)\n", (int) result, vbi_buf_size);

      return (ssize_t) result;
   }
}

#endif /* !ENABLE_PROXY || !ENABLE_V4L */


syntax highlighted by Code2HTML, v. 0.9.1