/*
   videoport.c - uncompressed video
*/

#include "videoport.h"

/* buffer management calls ----------------------------------------------- */

#define SPEW(x)

/* see the comment in struct _videoport in videoport.h 
 * for info on these calls 
 */

void _videoport_open(videoport me)
{
  int i;

  me->nlocs = me->buf_fields+1;
  me->q = malloc(sizeof(videofield) * me->nlocs);
  me->vhead = 0;
  me->uhead = 0;
  me->tail = 0;

  for(i=0; i < me->nlocs; i++)
    {
      me->q[i].idx = i;
      /* no need to initialize me->q[i].info to NULL */
    }

  /* we'll use the frontier MSC to compute the MSC of each field */
  VC(me->expected_fmsc = vlGetFrontierMSC(me->svr, me->path, me->mem) );

  me->maxbuffered = -1;
}

void _videoport_close(videoport me)
{
  free(me->q);
  me->q = NULL;
}

int _videoport_nfilled(videoport me, int head, int tail)
{
  int i = tail - head;
  if (i < 0) i += me->nlocs;
  return i;
}

int _videoport_nfillable(videoport me, int head, int tail)
{
  int i = head - tail - 1;
  if (i < 0) i += me->nlocs;
  return i;
}

void _videoport_spew(videoport me)
{
  int i;
  int guard;
  char *buf1 = malloc(me->nlocs + 1);
  char *buf2 = malloc(me->nlocs + 1);
  buf1[me->nlocs] = 0;
  buf2[me->nlocs] = 0;

  guard = me->vhead - 1;
  if (guard < 0) guard += me->nlocs;

  i = me->vhead;
  while (i != me->uhead)
    {
      buf1[i] = 'v';
      buf2[i] = (me->q[i].info ? 'U' : '.');
      i++;
      if (i == me->nlocs) i=0;
    }
  while (i != me->tail)
    {
      buf1[i] = 'u';
      buf2[i] = '.';
      i++;
      if (i == me->nlocs) i=0;
    }
  while (i != guard)
    {
      buf1[i] = '.';
      buf2[i] = '.';
      i++;
      if (i == me->nlocs) i=0;
    }
  buf1[i] = 'g';
  buf2[i] = 'g';

  printf("%s vfilled=%d\n"
         "%s ufilled=%d\n", 
         buf1,
         _videoport_nfilled(me, me->vhead, me->tail),
         buf2,
         _videoport_nfilled(me, me->uhead, me->tail));

  free(buf1);
  free(buf2);
}

void _videoport_poll_vlbuffer(videoport me)
{
  USTMSCpair p;
  p.ust = -1;
  
  /* process all the VL events available right now */
  /* XXX inefficient: should limit this check to every 15ms or so */
  while (vlPending(me->svr))
    {
      VLEvent ev;
      VC(vlNextEvent(me->svr, &ev));
      
      switch (ev.reason)
        {
        case VLTransferFailed:
          error_exit("video transfer failed.\n");
          break;
        }
    }
  /* process all the data (VM) or space (MV) available now */
  for(;;) 
    {
      VLInfoPtr info;
      stamp_t info_msc;
      void *data;
      stamp_t ust;
      
      /* grab the MSC of the next field we're about to read/write. */
      info_msc = me->expected_fmsc;
      
      if (me->direction == VIDEOPORT_DIRECTION_VM)
        {
          /* get the next field from the video input */
          if (NULL == (info = vlGetNextValid(me->svr, me->buffer)))
            break; /* no more data yet */
          
          /* vlGetNextValid() makes the frontier MSC go up */
          me->expected_fmsc++;
        }
      else /* me->direction == VIDEOPORT_DIRECTION_MV */
        {
          /* get the next free space to put data for video output */
          if (NULL == (info = vlGetNextFree(me->svr, me->buffer, 
                                            me->vl_xfrsize)))
            break; /* no more space yet */
        }
      
      /* get a pointer to the actual pixels */
      VNC(data = vlGetActiveRegion(me->svr, me->buffer, info));
      
      /* get a UST/MSC pair if we haven't already */
      if (p.ust != -1)
        VC(vlGetUSTMSCPair(me->svr, me->path, me->vid, VL_ANY, me->mem, &p));
      
      /* compute the UST of this field (when it came in or went out
       * the jack of the machine).
       *
       * we want the UST of 'info'.  info_msc is the MSC of 'info.'
       * we use our UST/MSC pair to extrapolate the pair's UST to
       * info_msc. for more information, see the Lurker's Guide page:
       *   "Introduction to UST and UST/MSC"
       */
      ust = p.ust + (info_msc - p.msc)*me->ust_per_msc;
      
      /* now we have:
       *   data, a pointer to the field's pixels
       *   ust, a time when the field hit/will hit the jack of the machine
       *   me->current_field_type, which is F1 or F2
       *
       *   add this videofield to the queue at me->tail
       */
      SPEW(printf("videoport get to tail=%d\n", me->tail));
      assert(_videoport_nfillable(me,me->vhead,me->tail) > 0);
      me->q[me->tail].pixels = data;
      me->q[me->tail].ust = ust;
      me->q[me->tail].msc = info_msc;
      me->q[me->tail].fieldtype = me->current_field_type;
      me->q[me->tail].info = info;
      me->tail++;
      if (me->tail >= me->nlocs)
        me->tail -= me->nlocs;

      /* field type always alternates with VL_CAPTURE_NONINTERLEAVED */
      me->current_field_type = 
        (me->current_field_type == FIELD_F1) ? FIELD_F2 : FIELD_F1;
    }
  
  /* check for video underflow/overflow */
  {
    stamp_t fmsc;
    VC(fmsc = vlGetFrontierMSC(me->svr, me->path, me->mem));
    if (fmsc != me->expected_fmsc)
      {
        printf("video error: we dropped/padded %lld fields\n",
               fmsc-me->expected_fmsc);
      }
    me->expected_fmsc = fmsc;
  }

  /* 
   * keep track of the max # of VL fields are are tying up
   * (this is for debugging and performance monitoring)
   */
  {
    int buffered = _videoport_nfilled(me, me->vhead, me->tail);
    if (buffered > me->maxbuffered)
      me->maxbuffered = buffered;
  }
}

/* used for debugging and performance monitoring */
int videoport_max_VL_buffers_used(videoport me)
{
  int maxbuffered = me->maxbuffered;
  me->maxbuffered = -1;
  return maxbuffered;
}

int videoport_available_field_count(videoport me)
{
  _videoport_poll_vlbuffer(me);

  return _videoport_nfilled(me, me->uhead, me->tail);
}

videofield *videoport_get_one_field(videoport me)
{
  videofield *f;

  _videoport_poll_vlbuffer(me);

  if (_videoport_nfilled(me, me->uhead, me->tail) <= 0)
    return NULL;

  assert(_videoport_nfilled(me, me->vhead, me->tail) > 0);
  assert(me->q[me->vhead].info != NULL);

  SPEW(printf("app get from uh=%d\n", me->uhead));

  f = &me->q[me->uhead];
  me->uhead++;
  if (me->uhead >= me->nlocs)
    me->uhead -= me->nlocs;

  SPEW(_videoport_spew(me));

  return f;
}

void videoport_put_one_field(videoport me, videofield *field)
{
  int i;

  assert(_videoport_nfilled(me, me->vhead, me->tail) > 0);
  assert(me->q[me->vhead].info != NULL);
  assert(field);

  i = field->idx;
  assert(me->q[field->idx].info);

  /* i must be >= vhead and < uhead */
  assert(((i        -me->vhead+me->nlocs)%me->nlocs) >= 0);
  assert(((i        -me->vhead+me->nlocs)%me->nlocs) < 
         ((me->uhead-me->vhead+me->nlocs)%me->nlocs));

  SPEW(printf("app put idx=%d\n", i));

  /* first punch out the info that the user specified */
 
  me->q[field->idx].info = NULL;

  /* then see how many _real_ put's we can do */

  while (me->q[me->vhead].info == NULL &&
         me->vhead != me->tail)
    {
      if (me->direction == VIDEOPORT_DIRECTION_VM)
        {
          /* don't need this buffer any more. */
          vlPutFree(me->svr, me->buffer);
        }
      else /* me->direction == VIDEOPORT_DIRECTION_MV */
        {
          /* done filling in this buffer: send it to the video output */
          vlPutValid(me->svr, me->buffer);
          
          /* vlPutValid() makes the frontier MSC go up */
          me->expected_fmsc++;
        }
      SPEW(printf("videoport put from vh=%d\n", me->vhead));
      me->vhead++;
      if (me->vhead >= me->nlocs)
        me->vhead -= me->nlocs;
    }

  SPEW(_videoport_spew(me));
}

/* other API calls ---------------------------------------------------- */

videoport videoport_open(int direction, int buf_fields, int flags)
{
  VLControlValue val;
  videoport me = malloc(sizeof(struct _videoport));
  int rc;

  if (!me)
    {
      setoserror(VIDEOPORT_MALLOC_FAILED);
      return NULL;
    }

  me->buf_fields = buf_fields;
  me->direction = direction;

  if (me->direction == VIDEOPORT_DIRECTION_VM)
    {
      /*
       * open video input
       */
      VNC(me->svr = vlOpenVideo(""));
      VC(me->src = vlGetNode(me->svr, VL_SRC, VL_VIDEO, VL_ANY));
      VC(me->drn = vlGetNode(me->svr, VL_DRN, VL_MEM, VL_ANY));
      VC(me->path = vlCreatePath(me->svr, VL_ANY, me->src, me->drn));
      rc = vlSetupPaths(me->svr, (VLPathList)&me->path, 1, VL_LOCK,VL_LOCK);
      me->vid = me->src;
      me->mem = me->drn;
    }
  else /* me->direction == VIDEOPORT_DIRECTION_MV */
    {
      /*
       * open video output
       */
      VNC(me->svr = vlOpenVideo(""));
      VC(me->src = vlGetNode(me->svr, VL_SRC, VL_MEM, VL_ANY));
      VC(me->drn = vlGetNode(me->svr, VL_DRN, VL_VIDEO, VL_ANY));
      VC(me->path = vlCreatePath(me->svr, VL_ANY, me->src, me->drn));
      rc = vlSetupPaths(me->svr, (VLPathList)&me->path, 1, VL_LOCK,VL_LOCK);
      me->mem = me->src;
      me->vid = me->drn;
    }

  /* check if video device was busy */
  
  if (rc < 0)
    {
      if (vlErrno == VLPathInUse)
        {
          vlCloseVideo(me->svr);
          free(me);
          return VIDEOPORT_DEVICE_BUSY;
        }
      else
        error_exit("VL error (%s) for call to vlSetupPaths()",
                   vlStrError(vlErrno), #call);
    }

  /* each buffer will contain one field. */

  val.intVal = VL_CAPTURE_NONINTERLEAVED;
  VC(vlSetControl(me->svr, me->path, me->mem, VL_CAP_TYPE, &val));
  
  /* set colorspace and packing */
  
  val.intVal = VL_PACKING_YVYU_422_8;
  VC(vlSetControl (me->svr, me->path, me->mem, VL_PACKING, &val));
  me->bytes_per_pixel = 2;

  /* get size of each field in pixels */
  
  VC(vlGetControl(me->svr, me->path, me->mem, VL_SIZE, &val));
  printf("video field size is %dx%d\n", val.xyVal.x, val.xyVal.y);
  me->xsize = val.xyVal.x;
  me->ysize = val.xyVal.y;

  /* get time spacing of each MSC (in this case field) in nanoseconds */

  VC(me->ust_per_msc = vlGetUSTPerMSC(me->svr, me->path, me->mem));

  /* get size of each field in bytes */

  me->vl_xfrsize = vlGetTransferSize(me->svr, me->path);

  /* currently VL guarantees at least 1 page padding is valid */

  me->max_valid_bytes_per_field = roundup(me->vl_xfrsize, getpagesize());
  printf("video max valid bytes per field is %d\n", 
         me->max_valid_bytes_per_field);

  /* create VL buffer with buf_fields items (in this case fields) */

  VNC(me->buffer = vlCreateBuffer(me->svr, me->path, me->mem, me->buf_fields));
  printf("video buffer holds %d fields\n", me->buf_fields);
  
  if (flags & VIDEOPORT_ADVISE_NOACCESS)
    vlBufferAdvise(me->buffer, VL_BUFFER_ADVISE_NOACCESS);
  
  VC(vlRegisterBuffer(me->svr, me->path, me->mem, me->buffer));

  /* say what VL events we want */

  VC(vlSelectEvents(me->svr, me->path, 
                    VLTransferFailedMask|VLSequenceLostMask));

  /* get file descriptors which we'll use to select() on */

  me->event_fd = vlGetFD(me->svr);
  me->buffer_fd = vlBufferGetFd(me->buffer);

  /* XXX this assumes F1 dominance.  we should query the device, or set
   * the device if it is settable, to determine the field dominance.
   * the field dominance parameter is--of course--device-specific, and
   * only some devices have one (other devices assume F1 dominance).
   *
   * for more information, see the Lurker's Guide pages:
   *   "Definitions: F1/F2, Interleave, Field Dominance, and More"
   *   "Where Does Field Dominance Originate?"
   *   "Hints for Vid-to-Mem Applications"
   *   "Introduction to UST and UST/MSC"
   */
  printf("assuming F1 dominance\n");
  me->current_field_type = FIELD_F1;

  /* begin video transfer */

  VC(vlBeginTransfer(me->svr, me->path, 0, NULL));
  
  /* set up the data structures for buffer management */

  _videoport_open(me);

  /* start buffer off in a good condition */
  {
    /* 
     * VIDEOPORT_DIRECTION_VM:
     * empty the input buffer.  discard contents.
     *
     * VIDEOPORT_DIRECTION_MV:
     * pre-stuff the output buffer with some data.  doesn't matter
     * what.
     *
     * we do this to take the buffer out of overflow/underflow,
     * so that we can use UST/MSC to accurately measure the UST 
     * of the first buffer we will dequeue/enqueue later.
     *
     * we choose to dequeue/enqueue an even number of fields, so we don't
     * mess up the F1/F2 ordering.
     */
    int i;
    int items;
    if (me->direction == VIDEOPORT_DIRECTION_VM)
      items = videoport_available_field_count(me);
    else
      items = me->buf_fields;
    items = items & ~0x1;
    for(i=0; i < items; i++)
      {
        videofield *f;
        f = videoport_get_one_field(me);
        assert(f);
        videoport_put_one_field(me, f);
      }
  }

  me->fillpoint = 1;

  return me;
}

void videoport_close(videoport me)
{
  /* shut down buffer management code */
  _videoport_close(me);

  /* Stop the data transfer */
  vlEndTransfer(me->svr, me->path);
  
  /* Disassociate the ring buffer from the path */
  vlDeregisterBuffer(me->svr, me->path, me->mem, me->buffer);
  
  /* Destroy the ring buffer, free the memory it used */
  vlDestroyBuffer(me->svr, me->buffer);

  /* Destroy the path, free it's memory */
  vlDestroyPath(me->svr, me->path);
  
  /* Disconnect from the daemon */
  vlCloseVideo(me->svr);

  free(me);
}

void videoport_setfillpoint(videoport me, 
                            int fillpoint)
{
  me->fillpoint = fillpoint;
}

void videoport_setfdset(videoport me,
                        fd_set *readset,
                        fd_set *writeset,
                        fd_set *exceptset,
                        stamp_t *wakeust)
{
  _videoport_poll_vlbuffer(me); 

  if (videoport_available_field_count(me) >= me->fillpoint)
    *wakeust = 0; /* data/space available: wake up immedately */
  else
    {
      FD_SET(me->event_fd, readset);
      if (me->direction == VIDEOPORT_DIRECTION_VM)
        FD_SET(me->buffer_fd, readset);
      else
        FD_SET(me->buffer_fd, writeset);
    }
}

void videoport_checkfdset(videoport me,
                          fd_set *readset,
                          fd_set *writeset,
                          fd_set *exceptset)
{
  /* a _videoport_poll_vlbuffer() call here not strictly necessary.
   * all other videoport calls which need this info will call 
   * _videoport_poll_vlbuffer(me) themselves.  we do it anyway 
   * so that we get VL event notifications (transferfailed etc.)
   * sooner rather than later.
   */
  _videoport_poll_vlbuffer(me); 
}

void videoport_wait_for_space_or_data(videoport me)
{
  while (videoport_available_field_count(me) < me->fillpoint)
    {
      fd_set readset, writeset, exceptset;
      stamp_t wakeust;
      struct timeval tv, *tvp;
      
      FD_ZERO(&readset);
      FD_ZERO(&writeset);
      FD_ZERO(&exceptset);
      wakeust = LONGLONG_MAX;
      
      videoport_setfdset(me, &readset, &writeset, &exceptset, &wakeust);
      
      if (wakeust == LONGLONG_MAX)
        tvp = NULL;
      else
        {
          stamp_t now, time_till_wakeup;
          dmGetUST((unsigned long long *)(&now));
          time_till_wakeup = wakeust - now;
          if (time_till_wakeup < 0)
            { tv.tv_sec = 0;  tv.tv_usec = 0; }
          else
            {
              tv.tv_sec = (long)(time_till_wakeup /  1000000000LL);
              time_till_wakeup -= tv.tv_sec * 1000000000LL;
              tv.tv_usec = (long)(time_till_wakeup / 1000);
            }
          tvp = &tv;
        }
      
      OC(select(getdtablehi(), &readset, &writeset, &exceptset, tvp));
      
      videoport_checkfdset(me, &readset, &writeset, &exceptset);
    }
}


