#include <u.h>
#include <libc.h>
#include <ctype.h>
#include <fcall.h>
#include <thread.h>
#include <9p.h>
#include <bio.h>
#include "usb.h"
#include "uvc.h"
#include "dat.h"
#include "fns.h"

char user[] = "cam";

void printVCHeader(void *vp);
void printVCInputTerminal(void *vp);
void printVCOutputTerminal(void *vp);
void printVCCameraTerminal(void *vp);
void printVCSelectorUnit(void *vp);
void printVCProcessingUnit(void *vp);
void printVCEncodingUnit(void *vp);
void printVCExtensionUnit(void *vp);
void printVSInputHeader(void *vp);
void printVSOutputHeader(void *vp);
void printVSStillFrame(void *vp);
void printVSUncompressedFormat(void *vp);
void printVSUncompressedFrame(void *vp);
void printVSColorFormat(void *vp);
void printProbeControl(void *vp);

Cam *cams;
int nunit;
VCUnit **unit;
Iface **unitif;

int debug;

int
getframedesc(Cam *c, int i, int j, Format **fp, VSUncompressedFrame **gp)
{
       Format *f;

       if(i >= c->nformat) return -1;
       f = c->format[i];
       if(f == nil) return -1;
       if(j >= f->nframe) return -1;
       if(f->frame[j] == nil) return -1;
       if(fp != nil) *fp = f;
       if(gp != nil) *gp = f->frame[j];
       return 0;
}

static void
parsevcdesc(Dev *dev, Desc *d)
{
       VCDescriptor *vdp;
       static Cam *cam;
       static Format *format;
       int i;
       Format *f;

       if(d == nil) return;
       vdp = (VCDescriptor *) &d->data;
       if(vdp->bDescriptorType != 0x24) return;
       if(Class(d->iface->csp) != CC_VIDEO) return;
       switch(Subclass(d->iface->csp)){
       case SC_VIDEOSTREAMING:
               switch(vdp->bDescriptorSubtype){
               case VS_INPUT_HEADER:
                       format = nil;
                       cam = emallocz(sizeof(Cam), 1);
                       cam->dev = dev;
                       cam->iface = d->iface;
                       cam->hdr = (VSInputHeader *) vdp;
                       cam->next = cams;
                       cams = cam;
                       break;
               case VS_FORMAT_UNCOMPRESSED:
                       if(cam == nil) return;
                       f = emallocz(sizeof(Format), 1);
                       f->desc = (void*)vdp;
                       i = f->desc->bFormatIndex;
                       if(i >= cam->nformat){
                               cam->format = realloc(cam->format, (i + 1) * sizeof(void *));
                               memset(cam->format + cam->nformat, 0, (i - cam->nformat) * sizeof(void *));
                               cam->nformat = i + 1;
                       }
                       cam->format[i] = f;
                       if(format == nil) cam->pc.bFormatIndex = i;
                       format = f;
                       break;
               case VS_FRAME_UNCOMPRESSED:
                       if(cam == nil || cam->nformat == 0) return;
                       f = format;
                       i = ((VSUncompressedFrame*)vdp)->bFrameIndex;
                       if(i >= f->nframe){
                               f->frame = realloc(f->frame, (i + 1) * sizeof(void *));
                               memset(f->frame + f->nframe, 0, (i - f->nframe) * sizeof(void *));
                               f->nframe = i + 1;
                       }
                       f->frame[i] = (void*)vdp;
                       break;
               }
               break;
       case SC_VIDEOCONTROL:
               switch(vdp->bDescriptorSubtype){
               case VC_INPUT_TERMINAL:
               case VC_OUTPUT_TERMINAL:
               case VC_SELECTOR_UNIT:
               case VC_PROCESSING_UNIT:
               case VC_ENCODING_UNIT:
               case VC_EXTENSION_UNIT:
                       i = ((VCUnit*)vdp)->bUnitID;
                       if(i >= nunit){
                               unit = realloc(unit, (i + 1) * sizeof(void *));
                               unitif = realloc(unitif, (i + 1) * sizeof(Iface *));
                               memset(unit + nunit, 0, (i - nunit) * sizeof(void *));
                               memset(unitif + nunit, 0, (i - nunit) * sizeof(void *));
                               nunit = i + 1;
                       }
                       unit[i] = (void*)vdp;
                       unitif[i] = d->iface;
                       break;
               }
               break;
       }
}

static void
createfiles(File *root, char *devname, Cam *c)
{
       char buf[512];
       File *d;

       snprint(buf, sizeof(buf), "cam%s.%d", devname, c->iface->id);
       d = createfile(root, buf, user, DMDIR|0555, c);
       c->ctlfile = createfile(d, "ctl", user, 0666, c);
       c->formatsfile = createfile(d, "formats", user, 0444, c);
       c->videofile = createfile(d, "video", user, 0444, c);
       c->framefile = createfile(d, "frame", user, 0444, c);
       c->descfile = createfile(d, "desc", user, 0444, c);
}

static char *
formatread(Cam *c)
{
       int i, j, k;
       Fmt fmt;
       Format *f;
       VSUncompressedFrame *g;
       char buf[5];

       fmtstrinit(&fmt);
       for(i = 0; i < c->nformat; i++){
               f = c->format[i];
               if(f == nil) continue;
               memcpy(buf, f->desc->guidFormat, 4);
               buf[4] = 0;
               for(j = 0; j < f->nframe; j++){
                       if((g = f->frame[j]) == nil) continue;
                       fmtprint(&fmt, "%dx%dx%d-%s ", GET2(g->wWidth), GET2(g->wHeight), f->desc->bBitsPerPixel, buf);
                       if(g->bFrameIntervalType == 0)
                               fmtprint(&fmt, "%.2f-%.2f\n", 10e6 / (u32int)GET4(g->dwFrameInterval[0]), 10e6 / (u32int)GET4(g->dwFrameInterval[1]));
                       else
                               for(k = 0; k < g->bFrameIntervalType; k++)
                                       fmtprint(&fmt, "%.2f%c", 10e6 / (u32int)GET4(g->dwFrameInterval[k]), k == g->bFrameIntervalType - 1 ? '\n' : ',');
               }
       }
       return fmtstrflush(&fmt);
}

static char *
descread(Cam *c)
{
       Fmt fmt;
       int i;
       Usbdev *ud;
       Desc *d;
       VCDescriptor *vdp;

       ud = c->dev->usb;
       fmtstrinit(&fmt);
       for(i = 0; i < nelem(ud->ddesc); i++){
               d = ud->ddesc[i];
               if(d == nil) continue;
               vdp = (VCDescriptor *) &d->data;
               if(vdp->bDescriptorType != 0x24) continue;
               if(Class(d->iface->csp) != CC_VIDEO) continue;
               printDescriptor(&fmt, d->iface, vdp);
       }
       return fmtstrflush(&fmt);
}

typedef struct ReadState ReadState;
struct ReadState {
       int len;
       char *buf;
};

static void
strread(Req *req, char *str, int len)
{
       ReadState *rs;

       if(req->fid->aux != nil){
               free(((ReadState*)req->fid->aux)->buf);
               free(req->fid->aux);
               req->fid->aux = nil;
       }
       if(str == nil)
               return;
       rs = emallocz(sizeof(ReadState), 1);
       rs->len = len < 0 ? strlen(str) : len;
       rs->buf = str;
       req->fid->aux = rs;
}

static void
fsread(Req *req)
{
       File *f;
       Cam *c;
       ReadState *rs;

       if(req->fid == nil || req->fid->file == nil || req->fid->file->aux == nil){
               respond(req, "the front fell off");
               return;
       }
       f = req->fid->file;
       c = f->aux;
       if(req->fid->aux == nil || req->ifcall.offset == 0)
               if(f == c->formatsfile)
                       strread(req, formatread(c), -1);
               else if(f == c->ctlfile)
                       strread(req, ctlread(c), -1);
               else if(f == c->descfile)
                       strread(req, descread(c), -1);
               else if(f == c->videofile || f == c->framefile){
                       videoread(req, c, 1);
                       return;
               }
       if((rs = req->fid->aux) == nil){
               respond(req, "the front fell off");
               return;
       }
       readbuf(req, rs->buf, rs->len);
       respond(req, nil);
}

static void
fswrite(Req *req)
{
       File *f;
       Cam *c;
       char *s;
       int n;

       if(req->fid == nil || req->fid->file == nil || req->fid->file->aux == nil){
err:            respond(req, "the front fell off");
               return;
       }
       f = req->fid->file;
       c = f->aux;
       if(f != c->ctlfile)
               goto err;
       for(n = 0; n < req->ifcall.count; n++)
               if(req->ifcall.data[n] == '\n')
                       break;
       s = emallocz(n+1, 0);
       memcpy(s, req->ifcall.data, n);
       s[n] = 0;
       werrstr("invalid argument");
       if(ctlwrite(c, s) < 0)
               responderror(req);
       else{
               req->ofcall.count = req->ifcall.count;
               respond(req, nil);
       }
       free(s);
}

static void
fsdestroyfid(Fid *fid)
{
       ReadState *rs;

       rs = fid->aux;
       if(rs != nil){
               free(rs->buf);
               free(rs);
       }
       if(fid->file != nil && fid->file->aux != nil && (fid->file == ((Cam*)fid->file->aux)->videofile || fid->file == ((Cam*)fid->file->aux)->framefile))
               videoclose(fid->file->aux);
}

static void
fsopen(Req *req)
{
       File *f;
       Cam *c;

       if((req->ofcall.qid.type & QTDIR) != 0){
               respond(req, nil);
               return;
       }
       if(req->fid == nil || req->fid->file == nil || req->fid->file->aux == nil){
               respond(req, "the front fell off");
               return;
       }
       f = req->fid->file;
       c = f->aux;
       if(f == c->videofile || f == c->framefile)
               if(videoopen(c, f == c->framefile) < 0){
                       responderror(req);
                       return;
               }
       respond(req, nil);
}

static void
fsflush(Req *req)
{
       if(req->oldreq->fid->file != nil && req->oldreq->fid->file->aux != nil && (((Cam*)req->oldreq->fid->file->aux)->videofile == req->oldreq->fid->file || ((Cam*)req->oldreq->fid->file->aux)->framefile == req->oldreq->fid->file))
               videoflush(req->oldreq, req->oldreq->fid->file->aux);
       respond(req, nil);
}

static void
usage(void)
{
       fprint(2, "usage: %s [-d] devid\n", argv0);
       threadexits("usage");
}

static Srv fs = {
       .open = fsopen,
       .read = fsread,
       .write = fswrite,
       .flush = fsflush,
       .destroyfid = fsdestroyfid,
};

void
threadmain(int argc, char* argv[])
{
       int i;
       Dev *d;
       Usbdev *ud;
       char buf[512];
       Cam *c;

       ARGBEGIN{
       case 'd':
               debug++;
               break;
       default:
               usage();
       }ARGEND;
       if(argc != 1)
               usage();
       d = getdev(*argv);
       if(d == nil)
               sysfatal("getdev: %r");
       ud = d->usb;
       for(i = 0; i < nelem(ud->ddesc); i++)
               parsevcdesc(d, ud->ddesc[i]);
       if(cams == nil)
               sysfatal("no input streams");
       for(c = cams; c != nil; c = c->next)
               if(c->nformat > 0){
                       c->pc.bFrameIndex = c->format[c->pc.bFormatIndex]->desc->bDefaultFrameIndex;
                       if(c->pc.bFrameIndex < c->format[c->pc.bFormatIndex]->nframe && c->format[c->pc.bFormatIndex]->frame[c->pc.bFrameIndex] != nil)
                               PUT4(c->pc.dwFrameInterval, GET4(c->format[c->pc.bFormatIndex]->frame[c->pc.bFrameIndex]->dwDefaultFrameInterval));
               }
       fs.tree = alloctree(user, "usb", DMDIR|0555, nil);
       for(c = cams; c != nil; c = c->next)
               createfiles(fs.tree->root, d->hname, c);
       snprint(buf, sizeof buf, "%d.cam", d->id);
       threadpostsharesrv(&fs, nil, "usb", buf);
       threadexits(nil);
}