/*
   videoport.h - uncompressed video
*/

#include <dmedia/vl.h>
#include <sys/dmcommon.h>

#define max(a,b) ( ((a)>(b)) ? (a) : (b))
#define min(a,b) ( ((a)<(b)) ? (a) : (b))
#define roundup(x, y) ((((x)+((y)-1))/(y))*(y))

/* field types.
 * for precise definitions, please see the Lurker's Guide page:
 *   "Definitions: F1/F2, Interleave, Field Dominance, and More"
 */
#define FIELD_F1 1
#define FIELD_F2 2

typedef struct videofield
{
  /* for app use: */

  void *pixels;
  stamp_t ust;
  stamp_t msc;
  int fieldtype; /* (FIELD_...) */

  /* for internal use: */

  VLInfoPtr info;
  int idx;
  
} videofield;

#define VIDEOPORT_DIRECTION_VM 0 /* vid to mem, input, capture, record */
#define VIDEOPORT_DIRECTION_MV 1 /* mem to vid, output, laydown, playback */

struct _videoport;

typedef struct _videoport *videoport;

/* error tokens */
#define VIDEOPORT_SUCCESS 0
#define VIDEOPORT_DEVICE_BUSY 1 /* some resource in use; can't open */
#define VIDEOPORT_MALLOC_FAILED 2

/* flags argument to videoport_open() */
#define VIDEOPORT_ADVISE_NOACCESS 1

/*
 * opens video, set to full-size, full-rate VL_PACKING_YVYU_422_8 fields.
 *
 * direction: pass in VIDEOPORT_DIRECTION_*
 * 
 * buf_fields: capacity, in fields, of video buffer.  buf_fields & ~0x1 
 *   needs to be enough fields to ride over the worst case delay between
 *   calls to videoport_open(), videoport_wait_for_space_or_data(),
 *   videoport_checkfdset() or videoport_setfdset().  
 *   for info on this delay see the lurker page:
 *     "Seizing Higher Scheduling Priority"
 *
 * flags:
 *   use VIDEOPORT_ADVISE_NOACCESS if you won't be touching buffer with CPU
 *
 * videoport_open() mallocs and returns a _videoport for you, or NULL on error
 * videoport_open() sets oserror() to a VIDEOPORT_ token on error
 * videoport_close() frees the _videoport for you.
 *
 * XXX videoport assumes F1 dominance.  see XXX in videoport.c for more info.
 */
videoport videoport_open(int direction, int buf_fields, int flags);
void videoport_close(videoport me);

/*
 * VIDEOPORT_DIRECTION_VM: returns number of fields of data available now
 * VIDEOPORT_DIRECTION_MV: returns number of fields of space available now
 */
int videoport_available_field_count(videoport me);

/*
 * VIDEOPORT_DIRECTION_VM:
 *   call videoport_get_one_field() to get some data (a videofield *)
 *   read videofield->ust, videofield->msc and videofield->fieldtype
 *   read the data out of videofield->pixels
 *   call videoport_put_one_field() to release the space
 * VIDEOPORT_DIRECTION_MV:
 *   call videoport_get_one_field() to get some space (a videofield *)
 *   read videofield->ust, videofield->msc and videofield->fieldtype
 *   write data into videofield->pixels
 *   call videoport_put_one_field() to release the data
 *
 * videoport_get_one_field()
 *   returns NULL if there's no data or space to get.
 *   returns a videofield * with the pixel pointer, ust, msc, fieldtype.
 *   do not free the videofield * returned.
 *   the UST and MSC are accurate assuming no overflow or underflow.
 *   the field type is always accurate.
 *   first field after videoport_open() will be an F1 field
 *     XXX assumes F1 dominance.  see XXX in videoport.c for more info.
 *   field type will always alternate between F1 and F2.
 *   field->pixels contains video data at VL_OFFSET of (0,0):
 *     see "Hints for Vid-to-Mem Applications"
 * videoport_put_one_field()
 *   pass in the videofield * which you want to put.
 */

videofield *videoport_get_one_field(videoport me);
void        videoport_put_one_field(videoport me, videofield *field);

/*
 * sets the fillpoint, in fields. see:
 * - videoport_setfdset() and
 * - videoport_wait_for_space_or_data().
 */
void videoport_setfillpoint(videoport me, 
                            int fillpoint);


/*
 * you must call videoport_setfdset() before the select() in your main loop.
 * it will fill in the fds that it wants to select on.  if it needs to
 * wake up at a UST which is less than *wakeust, it will modify *wakeust
 * to that UST.
 *
 * you must call videoport_checkfdset() after the select() in your main loop,
 * so that the videoport can react to new data and space that has become
 * available.
 *
 * a videoport has a fillpoint value in fields (default 1 field).
 * you can set the fillpoint with videoport_setfillpoint().
 * your select will unblock, at the latest, when:
 *
 *   videoport_available_field_count(p) >= fillpoint
 *
 * your select() may unblock earlier due to videoport's internals,
 * so when you unblock, you must call videoport_available_field_count()
 * or check the return value of videoport_get_*() to make sure there 
 * is data/space.
 *
 * videoport_setfdset() will not cause your select() to hard spin if there
 * are < fillpoint of fields available.  videoport_setfdset()
 * will cause your select() to hard spin if there >= fillpoint fields
 * available.
 */
void videoport_setfdset(videoport me,
                        fd_set *readset,
                        fd_set *writeset,
                        fd_set *exceptset,
                        stamp_t *wakeust);

void videoport_checkfdset(videoport me,
                          fd_set *readset,
                          fd_set *writeset,
                          fd_set *exceptset);

/*
 * if you have no select loop and you just want to block until at
 * least the fillpoint of space or data becomes available, call this.
 * you can set the fillpoint (default 1 field) with videoport_setfillpoint().
 * videoport_wait_for_space_or_data() will call videoport_setfdset(), 
 * select(), and videoport_checkfdset() as many times are are necessary
 * to get the fillpoint worth of data/space (including zero times).
 */
void videoport_wait_for_space_or_data(videoport me);

/* struct _videoport ---------------------------------------------------- */

/* totally non-encapsulated and proud of it! */

typedef struct _videoport 
{
  /* === public interface -- read these freely */

  /* these were passed into videoport_open() */
  int direction; /* VIDEOPORT_DIRECTION_... */
  int buf_fields;
  /* time spacing of fields */
  double ust_per_msc;
  /* image size */
  int xsize, ysize;
  /* bytes per pix: fixed for now */
  int bytes_per_pixel;
  /* maximum number of valid bytes in a videofield:  this includes
   * bytes_per_pixel*xsize*ysize plus any extra padding the VL includes.
   */
  int max_valid_bytes_per_field;

  /* === sorta public interface -- you shouldn't read these unless you have
   * to make some backdoor VL call.
   */
  VLServer svr;
  VLPath path;
  VLNode drn, src, mem, vid;
  VLBuffer buffer;

  /* === private interface -- you'll get in trouble if you read these
   * without reading the source code.  use the videoport_ functions instead.
   */
  int event_fd;
  int buffer_fd;
  int fillpoint; /* in fields */
  stamp_t expected_fmsc;
  int current_field_type; /* NOT the same as returned by videoport_get_*() */
  int vl_xfrsize; /* VL transfersize to pass to vlGetNextFree() */
  videofield bogusfield; /* used by unvideoport.c */
  int maxbuffered;

  /*
   * with wonderful VLbuffers, we can only free the oldest valid data (VM)
   * or make the oldest free space valid (MV).  that is:
   * we can only vlPutFree() the oldest data we vlGetNextValid()ed (VM), and
   * we can only vlPutValid() the oldest data we vlGetNextFree()d (MV).
   * the fields which we have "got" but not yet "put" are in the 
   * "user region."  uncompressed video disk programs which use
   * asynchronous I/O read or write slices of the user region simultaneously
   * on different file descriptors.  these I/Os often complete in a different
   * order than the order in which they were issued.  therefore, these
   * programs want to "put" items out of the user region in a different 
   * order in which they "got" them into the user region.
   *
   * the videoport functions sit on top of the VL get/put calls and present
   * an improved buffering interface whose "put" calls take an argument
   * saying which field you want to "put."  there are two user regions:
   * a contiguous "lib region" containing the fields which videoport
   * got from VL but has not yet put to VL, and a possibly non-contiguous 
   * "app region" containing the fields which the app got from videoport
   * but has not yet put to videoport.
   *
   * underneath the covers, videoport uses the following private data 
   * structures to maintain these regions.
   *
   * q is a simple ring buffer with guard location.
   *  - q has nlocs locations but holds at most nlocs-1 items.
   *  - so nlocs-1 == me->buf_fields
   *  - [uv]head and tail are always >=0 and <nlocs
   *  - filled = (tail - [uv]head) mod nlocs;
   *  - fillable = ([uv]head - tail - 1) mod nlocs;
   *  - [uv]head == tail                implies filled==0 or empty
   *  - [uv]head == (tail+1) mod nlocs  implies fillable==0 or full
   *  
   * tail is the next place videoport will get a buffer from VL
   * uhead is the next place the app will get a buffer from videoport
   * vhead is the next place videoport will put a buffer to VL
   *
   * (vhead <= uhead <= tail) mod nlocs
   *
   * if (i >= vhead and i < tail) mod nlocs, then
   *   {
   *     q[i] is in the lib region.
   *     if (q[i].info != NULL)
   *       q[i] is in the app region (the app is still using q[i])
   *     else
   *       app has done a put on q[i] but videoport cannot put it to VL yet
   *
   *     an invariant maintained everywhere except in the middle of
   *     _videoport_put() is that q[vhead].info != NULL.
   *   }
   * else
   *   q[i] is not in the lib region, so is total garbage
   *   (might as well be NULL)
   */
  int nlocs;
  videofield *q;
  int vhead;
  int uhead;
  int tail;
  
} _videoport;

/* error handling -------------------------------------------------------- */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>
#include <unistd.h>
#include <stdarg.h>
#include <assert.h>
#include <sys/uio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <ctype.h>
#include <inttypes.h>
#include <aio.h>
#include <errno.h>
#include <malloc.h>

/*
 * you will of course want to substitute better error handling.
 */

void error_exit(char *format, ...);
void perror_exit(char *format, ...);
void error(char *format, ...);
void perror2(char *format, ...);

/*
 * VNC -- "VL NULL Check".  Wraps a VL call that returns NULL
 * on error.
 */
#define VNC(call) \
{ \
  if ( (call) == NULL ) \
    error_exit("VL error (%s) for call \"%s\"",  \
               vlStrError(vlErrno), #call); \
}

/*
 * VC - "VL Check".  Wraps a VL call that returns <0 on error.
 */
#define VC(call) \
{ \
  if ( (call) < 0 ) \
    error_exit("VL error (%s) for call \"%s\"",  \
               vlStrError(vlErrno), #call); \
}

/*
** OC -- "OS Check".  Wraps a Unix call that returns negative on error.
**                    Prints and translates oserror()
*/
#define OC(call) \
{ \
  if ( (call) < 0 ) \
    perror_exit(#call); \
}

/*
** ONC -- "OS Null Check".  Wraps a Unix call that returns NULL on error.
**                          Prints and translates oserror()
*/
#define ONC(call) \
{ \
  if ( (call) == NULL ) \
    perror_exit(#call); \
}

/*
** OSIGC -- "OS SIG_ERR Check".  Wraps a Unix call that returns SIG_ERR 
**                               on error.
*/
#define OSIGC(call) \
{ \
  if ( (call) == SIG_ERR ) \
    perror_exit(#call); \
}

/*
 * VPNC -- "videoport NULL Check".  Wraps a videoport call that returns NULL
 *                                  on error.  Prints oserror() numerically.
 */
#define VPNC(call) \
{ \
  if ( (call) == NULL ) \
    error_exit("videoport error %d for call \"%s\"",  \
               oserror(), #call); \
}

/*
** NC -- "NULL Check".  Wraps a statement to check for NULL.  No error print.
*/
#define NC(call) \
{ \
  if ( (call) == NULL ) \
    error_exit("\"%s\"", #call); \
}


/* utility ------------------------------------------------------------ */

typedef struct _diskperformance
{
  stamp_t oldestust;
  off64_t oldestcompleted;
  stamp_t lastreport;
} _diskperformance;

typedef _diskperformance *diskperformance;

diskperformance diskperformance_open(void);
void diskperformance_close(diskperformance me);
void diskperformance_datapoint(diskperformance me, off64_t bytescompleted,
                               videoport port);



