#include <u.h>
#include <libc.h>
#include <bio.h>
#include <libsec.h>

#include "iso9660.h"

static int readisodesc(Cdimg*, Voldesc*);
static int readjolietdesc(Cdimg*, Voldesc*);

/*
* It's not strictly conforming; instead it's enough to
* get us up and running; presumably the real CD writing
* will take care of being conforming.
*
* Things not conforming include:
*      - no path table
*      - root directories are of length zero
*/
Cdimg*
createcd(char *file, Cdinfo info)
{
       int fd, xfd;
       Cdimg *cd;

       if(access(file, AEXIST) == 0){
               werrstr("file already exists");
               return nil;
       }

       if((fd = create(file, ORDWR, 0666)) < 0)
               return nil;

       cd = emalloc(sizeof *cd);
       cd->file = atom(file);

       Binit(&cd->brd, fd, OREAD);

       if((xfd = open(file, ORDWR)) < 0)
               sysfatal("can't open file again: %r");
       Binit(&cd->bwr, xfd, OWRITE);

       Crepeat(cd, 0, 16*Blocksize);
       Cputisopvd(cd, info);
       if(info.flags & CDbootable){
               cd->bootimage = info.bootimage;
               cd->flags |= CDbootable;
               Cputbootvol(cd);
       }

       if(readisodesc(cd, &cd->iso) < 0)
               assert(0);
       if(info.flags & CDplan9)
               cd->flags |= CDplan9;
       else if(info.flags & CDrockridge)
               cd->flags |= CDrockridge;
       if(info.flags & CDjoliet) {
               Cputjolietsvd(cd, info);
               if(readjolietdesc(cd, &cd->joliet) < 0)
                       assert(0);
               cd->flags |= CDjoliet;
       }
       Cputendvd(cd);

       if(info.flags & CDdump){
               cd->nulldump = Cputdumpblock(cd);
               cd->flags |= CDdump;
       }
       if(cd->flags & CDbootable){
               Cputbootcat(cd);
               Cupdatebootvol(cd);
       }

       if(info.flags & CDconform)
               cd->flags |= CDconform;

       cd->flags |= CDnew;
       cd->nextblock = Cwoffset(cd) / Blocksize;
       assert(cd->nextblock != 0);

       return cd;
}

Cdimg*
opencd(char *file, Cdinfo info)
{
       int fd, xfd;
       Cdimg *cd;
       Dir *d;

       if((fd = open(file, ORDWR)) < 0) {
               if(access(file, AEXIST) == 0)
                       return nil;
               return createcd(file, info);
       }

       if((d = dirfstat(fd)) == nil) {
               close(fd);
               return nil;
       }
       if(d->length == 0 || d->length % Blocksize) {
               werrstr("bad length %lld", d->length);
               close(fd);
               free(d);
               return nil;
       }

       cd = emalloc(sizeof *cd);
       cd->file = atom(file);
       cd->nextblock = d->length / Blocksize;
       assert(cd->nextblock != 0);
       free(d);

       Binit(&cd->brd, fd, OREAD);

       if((xfd = open(file, ORDWR)) < 0)
               sysfatal("can't open file again: %r");
       Binit(&cd->bwr, xfd, OWRITE);

       if(readisodesc(cd, &cd->iso) < 0) {
               free(cd);
               close(fd);
               close(xfd);
               return nil;
       }

       /* lowercase because of isostring */
       if(strstr(cd->iso.systemid, "iso9660") == nil
       && strstr(cd->iso.systemid, "utf8") == nil) {
               werrstr("unknown systemid %s", cd->iso.systemid);
               free(cd);
               close(fd);
               close(xfd);
               return nil;
       }

       if(strstr(cd->iso.systemid, "plan 9"))
               cd->flags |= CDplan9;
       if(strstr(cd->iso.systemid, "iso9660"))
               cd->flags |= CDconform;
       if(strstr(cd->iso.systemid, "rrip"))
               cd->flags |= CDrockridge;
       if(strstr(cd->iso.systemid, "boot"))
               cd->flags |= CDbootable;
       if(readjolietdesc(cd, &cd->joliet) == 0)
               cd->flags |= CDjoliet;
       if(hasdump(cd))
               cd->flags |= CDdump;

       return cd;
}

ulong
big(void *a, int n)
{
       uchar *p;
       ulong v;
       int i;

       p = a;
       v = 0;
       for(i=0; i<n; i++)
               v = (v<<8) | *p++;
       return v;
}

ulong
little(void *a, int n)
{
       uchar *p;
       ulong v;
       int i;

       p = a;
       v = 0;
       for(i=0; i<n; i++)
               v |= (*p++<<(i*8));
       return v;
}

void
Creadblock(Cdimg *cd, void *buf, ulong block, ulong len)
{
       assert(block != 0);     /* nothing useful there */

       Bflush(&cd->bwr);
       if(Bseek(&cd->brd, block*Blocksize, 0) != block*Blocksize)
               sysfatal("error seeking to block %lud", block);
       if(Bread(&cd->brd, buf, len) != len)
               sysfatal("error reading %lud bytes at block %lud: %r %lld", len, block, Bseek(&cd->brd, 0, 2));
}

int
parsedir(Cdimg *cd, Direc *d, uchar *buf, int len, char *(*cvtname)(uchar*, int))
{
       enum { NAMELEN = 28 };
       char name[NAMELEN];
       uchar *p;
       Cdir *c;

       memset(d, 0, sizeof *d);

       c = (Cdir*)buf;

       if(c->len > len) {
               werrstr("buffer too small");
               return -1;
       }

       if(c->namelen == 1 && c->name[0] == '\0')
               d->name = atom(".");
       else if(c->namelen == 1 && c->name[0] == '\001')
               d->name = atom("..");
       else if(cvtname)
               d->name = cvtname(c->name, c->namelen);

       d->block = little(c->dloc, 4);
       d->length = little(c->dlen, 4);

       if(c->flags & 2)
               d->mode |= DMDIR;

/*BUG: do we really need to parse the plan 9 fields? */
       /* plan 9 use fields */
       if((cd->flags & CDplan9) && cvtname == isostring
       && (c->namelen != 1 || c->name[0] > 1)) {
               p = buf+33+c->namelen;
               if((p-buf)&1)
                       p++;
               assert(p < buf+c->len);
               assert(*p < NAMELEN);
               if(*p != 0) {
                       memmove(name, p+1, *p);
                       name[*p] = '\0';
                       d->confname = d->name;
                       d->name = atom(name);
               }
               p += *p+1;
               assert(*p < NAMELEN);
               memmove(name, p+1, *p);
               name[*p] = '\0';
               d->uid = atom(name);
               p += *p+1;
               assert(*p < NAMELEN);
               memmove(name, p+1, *p);
               name[*p] = '\0';
               d->gid = atom(name);
               p += *p+1;
               if((p-buf)&1)
                       p++;
               d->mode = little(p, 4);
       }

       // BUG: rock ridge extensions
       return 0;
}

void
setroot(Cdimg *cd, ulong block, ulong dloc, ulong dlen)
{
       assert(block != 0);

       Cwseek(cd, block*Blocksize+offsetof(Cvoldesc, rootdir[0])+offsetof(Cdir, dloc[0]));
       Cputn(cd, dloc, 4);
       Cputn(cd, dlen, 4);
}

void
setvolsize(Cdimg *cd, ulong block, ulong size)
{
       assert(block != 0);

       Cwseek(cd, block*Blocksize+offsetof(Cvoldesc, volsize[0]));
       Cputn(cd, size, 4);
}

void
setpathtable(Cdimg *cd, ulong block, ulong sz, ulong lloc, ulong bloc)
{
       assert(block != 0);

       Cwseek(cd, block*Blocksize+offsetof(Cvoldesc, pathsize[0]));
       Cputn(cd, sz, 4);
       Cputnl(cd, lloc, 4);
       Cputnl(cd, 0, 4);
       Cputnm(cd, bloc, 4);
       Cputnm(cd, 0, 4);
       assert(Cwoffset(cd) == block*Blocksize+offsetof(Cvoldesc, rootdir[0]));
}


static void
parsedesc(Voldesc *v, Cvoldesc *cv, char *(*string)(uchar*, int))
{
       v->systemid = string(cv->systemid, sizeof cv->systemid);

       v->pathsize = little(cv->pathsize, 4);
       v->lpathloc = little(cv->lpathloc, 4);
       v->mpathloc = little(cv->mpathloc, 4);

       v->volumeset = string(cv->volumeset, sizeof cv->volumeset);
       v->publisher = string(cv->publisher, sizeof cv->publisher);
       v->preparer = string(cv->preparer, sizeof cv->preparer);
       v->application = string(cv->application, sizeof cv->application);

       v->abstract = string(cv->abstract, sizeof cv->abstract);
       v->biblio = string(cv->biblio, sizeof cv->biblio);
       v->notice = string(cv->notice, sizeof cv->notice);
}

static int
readisodesc(Cdimg *cd, Voldesc *v)
{
       static uchar magic[] = { 0x01, 'C', 'D', '0', '0', '1', 0x01, 0x00 };
       Cvoldesc cv;

       memset(v, 0, sizeof *v);

       Creadblock(cd, &cv, 16, sizeof cv);
       if(memcmp(cv.magic, magic, sizeof magic) != 0) {
               werrstr("bad pvd magic");
               return -1;
       }

       if(little(cv.blocksize, 2) != Blocksize) {
               werrstr("block size not %d", Blocksize);
               return -1;
       }

       cd->iso9660pvd = 16;
       parsedesc(v, &cv, isostring);

       return parsedir(cd, &v->root, cv.rootdir, sizeof cv.rootdir, isostring);
}

static int
readjolietdesc(Cdimg *cd, Voldesc *v)
{
       int i;
       static uchar magic[] = { 0x02, 'C', 'D', '0', '0', '1', 0x01, 0x00 };
       Cvoldesc cv;

       memset(v, 0, sizeof *v);

       for(i=16; i<24; i++) {
               Creadblock(cd, &cv, i, sizeof cv);
               if(memcmp(cv.magic, magic, sizeof magic) != 0)
                       continue;
               if(cv.charset[0] != 0x25 || cv.charset[1] != 0x2F
               || (cv.charset[2] != 0x40 && cv.charset[2] != 0x43 && cv.charset[2] != 0x45))
                       continue;
               break;
       }

       if(i==24) {
               werrstr("could not find Joliet SVD");
               return -1;
       }

       if(little(cv.blocksize, 2) != Blocksize) {
               werrstr("block size not %d", Blocksize);
               return -1;
       }

       cd->jolietsvd = i;
       parsedesc(v, &cv, jolietstring);

       return parsedir(cd, &v->root, cv.rootdir, sizeof cv.rootdir, jolietstring);
}

/*
* CD image buffering routines.
*/
void
Cputc(Cdimg *cd, int c)
{
       assert(Boffset(&cd->bwr) >= 16*Blocksize || c == 0);

if(Boffset(&cd->bwr) == 0x9962)
if(c >= 256) abort();
       if(Bputc(&cd->bwr, c) < 0)
               sysfatal("Bputc: %r");
       Bflush(&cd->brd);
}

void
Cputnl(Cdimg *cd, ulong val, int size)
{
       switch(size) {
       default:
               sysfatal("bad size %d in bputnl", size);
       case 2:
               Cputc(cd, val);
               Cputc(cd, val>>8);
               break;
       case 4:
               Cputc(cd, val);
               Cputc(cd, val>>8);
               Cputc(cd, val>>16);
               Cputc(cd, val>>24);
               break;
       }
}

void
Cputnm(Cdimg *cd, ulong val, int size)
{
       switch(size) {
       default:
               sysfatal("bad size %d in bputnl", size);
       case 2:
               Cputc(cd, val>>8);
               Cputc(cd, val);
               break;
       case 4:
               Cputc(cd, val>>24);
               Cputc(cd, val>>16);
               Cputc(cd, val>>8);
               Cputc(cd, val);
               break;
       }
}

void
Cputn(Cdimg *cd, long val, int size)
{
       Cputnl(cd, val, size);
       Cputnm(cd, val, size);
}

/*
* ASCII/UTF string writing
*/
void
Crepeat(Cdimg *cd, int c, int n)
{
       while(n-- > 0)
               Cputc(cd, c);
}

void
Cputs(Cdimg *cd, char *s, int size)
{
       int n;

       if(s == nil) {
               Crepeat(cd, ' ', size);
               return;
       }

       for(n=0; n<size && *s; n++)
               Cputc(cd, *s++);
       if(n<size)
               Crepeat(cd, ' ', size-n);
}

void
Cwrite(Cdimg *cd, void *buf, int n)
{
       assert(Boffset(&cd->bwr) >= 16*Blocksize);

       if(Bwrite(&cd->bwr, buf, n) != n)
               sysfatal("Bwrite: %r");
       Bflush(&cd->brd);
}

void
Cputr(Cdimg *cd, Rune r)
{
       Cputc(cd, r>>8);
       Cputc(cd, r);
}

void
Crepeatr(Cdimg *cd, Rune r, int n)
{
       int i;

       for(i=0; i<n; i++)
               Cputr(cd, r);
}

void
Cputrs(Cdimg *cd, Rune *s, int osize)
{
       int n, size;

       size = osize/2;
       if(s == nil)
               Crepeatr(cd, (Rune)' ', size);
       else {
               for(n=0; *s && n<size; n++)
                       Cputr(cd, *s++);
               if(n<size)
                       Crepeatr(cd, ' ', size-n);
       }
       if(osize&1)
               Cputc(cd, 0);   /* what else can we do? */
}

void
Cputrscvt(Cdimg *cd, char *s, int size)
{
       Rune r[256];

       strtorune(r, s);
       Cputrs(cd, strtorune(r, s), size);
}

void
Cpadblock(Cdimg *cd)
{
       int n;
       ulong nb;

       n = Blocksize - (Boffset(&cd->bwr) % Blocksize);
       if(n != Blocksize)
               Crepeat(cd, 0, n);

       nb = Boffset(&cd->bwr)/Blocksize;
       assert(nb != 0);
       if(nb > cd->nextblock)
               cd->nextblock = nb;
}

void
Cputdate(Cdimg *cd, ulong ust)
{
       Tm *tm;

       if(ust == 0) {
               Crepeat(cd, 0, 7);
               return;
       }
       tm = gmtime(ust);
       Cputc(cd, tm->year);
       Cputc(cd, tm->mon+1);
       Cputc(cd, tm->mday);
       Cputc(cd, tm->hour);
       Cputc(cd, tm->min);
       Cputc(cd, tm->sec);
       Cputc(cd, 0);
}

void
Cputdate1(Cdimg *cd, ulong ust)
{
       Tm *tm;
       char str[20];

       if(ust == 0) {
               Crepeat(cd, '0', 16);
               Cputc(cd, 0);
               return;
       }
       tm = gmtime(ust);
       sprint(str, "%.4d%.2d%.2d%.2d%.2d%.4d",
               tm->year+1900,
               tm->mon+1,
               tm->mday,
               tm->hour,
               tm->min,
               tm->sec*100);
       Cputs(cd, str, 16);
       Cputc(cd, 0);
}

void
Cwseek(Cdimg *cd, ulong offset)
{
       Bseek(&cd->bwr, offset, 0);
}

ulong
Cwoffset(Cdimg *cd)
{
       return Boffset(&cd->bwr);
}

void
Cwflush(Cdimg *cd)
{
       Bflush(&cd->bwr);
}

ulong
Croffset(Cdimg *cd)
{
       return Boffset(&cd->brd);
}

void
Crseek(Cdimg *cd, ulong offset)
{
       Bseek(&cd->brd, offset, 0);
}

int
Cgetc(Cdimg *cd)
{
       int c;

       Cwflush(cd);
       if((c = Bgetc(&cd->brd)) == Beof) {
               fprint(2, "getc at %lud\n", Croffset(cd));
               assert(0);
               //sysfatal("Bgetc: %r");
       }
       return c;
}

void
Cread(Cdimg *cd, void *buf, int n)
{
       Cwflush(cd);
       if(Bread(&cd->brd, buf, n) != n)
               sysfatal("Bread: %r");
}

char*
Crdline(Cdimg *cd, int c)
{
       Cwflush(cd);
       return Brdline(&cd->brd, c);
}

int
Clinelen(Cdimg *cd)
{
       return Blinelen(&cd->brd);
}