/*
 * Copyright (C) 2002 Scott Smith (trckjunky@users.sourceforge.net)
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or (at
 * your option) any later version.
 *
 * This program 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
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
 * USA
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <getopt.h>

static const char RCSID[]="$Id: ifogen.c,v 1.4 2003/01/20 04:49:44 bero Exp $";

#define SCRTIME 27000000

// must be a power of 2
#define CBSIZE 16

struct circbuf {
    char buf[CBSIZE];
    int ptr;
};

struct audpts {
    int pts,sect;
};

#define MAXAUDPTS 100

struct vobuinfo {
    int sector,lastsector,scr,fsect,fnum,cellid,transpose;
    int packts[2],pts[2],dts[2],numref,numaudpts;
    int lastrefsect[3]; // why on earth do they want the LAST sector of the ref (I, P) frame?
    struct audpts audpts[MAXAUDPTS]; // should be big enough...
    char *sectdata; // so we don't have to reread it
};

struct vob_summary {
    int numvobus,maxvobus;
    long long lasttime;
    struct vobuinfo *vi;
    int *viptslookup;
};

int videoattr=0x4300;
int audioflag=-1,audiowarn=0; // 0=ac3, 2=mp1 layer 2, 4=pcm
int tvflag=-1,tvwarn=0; // 0=ntsc, 1=pal
int aspectflag=-1,aspectwarn=0; // 0=4:3, 1=16:9
int resolutionflag=-1,resolutionwarn=0; // 0=720xfull, 1=704xfull, 2=352xfull, 3=352xhalf
struct circbuf cb;

#define NUMCELLS(va) ((va)->vi[(va)->numvobus-1].cellid)

void set_ts(int *ts,int v)
{
    if( ts[0] == -1 || v < ts[0] )
        ts[0]=v;
    if( ts[1] == -1 || v > ts[1] )
        ts[1]=v;
}

unsigned char cbyte(struct circbuf *c,int o)
{
    return c->buf[(c->ptr+o)&(CBSIZE-1)];
}

void cput(struct circbuf *c,int ch)
{
    c->buf[c->ptr]=ch;
    c->ptr=(c->ptr+1)&(CBSIZE-1);
}

unsigned int buildtime(long long num,long long denom)
{
    int rate=(tvflag==1?1:3);
    double frates[4]={0,25,0,29.97};
    int hr,min,sec,fr;

    sec=num/denom;
    min=sec/60;
    hr=min/60;
    min%=60;
    sec%=60;
    hr=(hr/10)*16+hr%10;
    min=(min/10)*16+min%10;
    sec=(sec/10)*16+sec%10;
    num%=denom;
    fr=((int)(num*frates[rate]/denom));
    fr=(fr/10)*16+fr%10;
    return (hr<<24)|(min<<16)|(sec<<8)|fr|(rate<<6);
}

void write4(char *p,unsigned int v)
{
    p[0]=(v>>24)&255;
    p[1]=(v>>16)&255;
    p[2]=(v>>8)&255;
    p[3]=v&255;
}

void write2(char *p,unsigned int v)
{
    p[0]=(v>>8)&255;
    p[1]=v&255;
}

unsigned int read2(unsigned char *p)
{
    return (p[0]<<8)|p[1];
}

void updatevideoattr(char *name,int *v,int nv,int *w)
{
    if( nv == -1 )
        return;
    if( v[0]!=-1 && v[0]!=nv && w[0]==0 ) {
        fprintf(stderr,"WARN: attempt to update %s from %d to %d; skipping\n",name,v[0],nv);
        w[0]=1;
    }
    if( v[0]==-1 )
        v[0]=nv;
}

void updateframerate(int frame)
{
    int pal;
    switch(frame) {
    case 3: //   "25.0 (PAL/SECAM VIDEO / converted FILM)"
        pal=1;
        break;

    case 4: //   "30000.0/1001.0 (NTSC VIDEO)"
        pal=0;
        break;
    
    default:
        pal=-1;
        break;
    }
   updatevideoattr("pal flag",&tvflag,pal,&tvwarn);
}

void updateresolution(int h,int v)
{
    int res;

    if( h>=720 )
        res=0;
    else if ( h>=704 )
        res=1;
    else if ( v>=384 )
        res=2;
    else
        res=3;
                            
    updatevideoattr("resolution flag",&resolutionflag,res,&resolutionwarn);
}

void setattr()
{
    videoattr=0x4300;
    if( tvflag == -1 ) tvflag=0;
    if( aspectflag == -1 ) aspectflag=0;
    if( resolutionflag == -1 ) resolutionflag=0;
    if( audioflag == -1 ) audioflag=2;

    videoattr|=(tvflag*0x1000)|
        (aspectflag*0xc00)|
        (resolutionflag<<2);
    fprintf(stderr,"INFO: Generating VTS with the following video attributes:\n");
    fprintf(stderr,"INFO: TV standard: %s\n",tvflag?"PAL":"NTSC");
    fprintf(stderr,"INFO: Aspect ratio: %s\n",aspectflag?"16:9":"4:3");
    fprintf(stderr,"INFO: Resolution: %dx%d\n",
            resolutionflag?(resolutionflag==1?704:352):720,
            (resolutionflag==3?240:480)*(tvflag?6:5)/5);
    fprintf(stderr,"INFO: Audio format: %s\n",audioflag==0?"ac3":(audioflag==2?"mp2":(audioflag==4?"pcm":"-")));
}

int timeline[19]={1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,
                  20,60,120,240};

void buildvobulookup(struct vob_summary *va)
{
    int p,i;
    va->viptslookup=malloc(sizeof(int)*4*va->numvobus);
    memset(va->viptslookup,-1,sizeof(int)*4*va->numvobus);
    p=1;
    while(p<va->numvobus)
        p<<=1;
    for( i=0; i<va->numvobus; i++ )
        va->viptslookup[p+i]=va->vi[i].pts[1];
    p>>=1;
    while(p>=1) {
        for( i=0; i<p; i++ ) {
            int l=va->viptslookup[2*(p+i)];
            int h=va->viptslookup[2*(p+i)+1];
            if( l>h ) h=l;
            va->viptslookup[p+i]=h;
        }
        p>>=1;
    }
}

int findvobu(struct vob_summary *va,int pts,int roundup)
{
    int i,b;

    i=1;
    b=1;
    while( i<va->numvobus ) {
        if( pts < va->viptslookup[2*i] )
            i=i*2;
        else
            i=i*2+1;
        b=b*2;
    }
    i-=b;

    if( va->vi[i].pts[0] == pts )
        return i;
    if( pts < va->vi[i].pts[0] )
        return i-1;
    if( pts < va->vi[i].pts[1] )
        return i+roundup;
    return -1;
}

unsigned int getsect(struct vob_summary *va,int vobnum,int skip,int sectoff,unsigned notfound)
{
    int dessect;

    if( skip )
        skip=0x40000000;
    if( vobnum < 0 || vobnum >= va->numvobus )
        return notfound|skip;
    dessect=va->vi[vobnum].sector;
    if( dessect < sectoff )
        return (sectoff-dessect)|0x80000000|skip;
    else
        return (dessect-sectoff)|0x80000000|skip;
}

int readscr(const unsigned char *buf)
{
    return ((buf[0]&0x18)<<27)|
            ((buf[0]&3)<<28)|
            (buf[1]<<20)|
            ((buf[2]&0xf8)<<12)|
            ((buf[2]&3)<<13)|
            (buf[3]<<5)|
            ((buf[4]&0xf8)>>3);
}

void writescr(unsigned char *buf,unsigned int scr)
{
    buf[0]=((scr>>27)&0x18)|((scr>>28)&3)|68;
    buf[1]=scr>>20;
    buf[2]=((scr>>12)&0xf8)|((scr>>13)&3)|4;
    buf[3]=scr>>5;
    buf[4]=((scr<<3)&0xf8)|(buf[4]&7);
}

int readpts(const unsigned char *buf)
{
    int a1,a2,a3,pts;
    a1=(buf[0]&0xe)>>1;
    a2=((buf[1]<<8)|buf[2])>>1;
    a3=((buf[3]<<8)|buf[4])>>1;
    pts=(((long long)a1)<<30)|
        (a2<<15)|
        a3;
    return pts;
}

void writepts(unsigned char *buf,unsigned int pts)
{
    buf[0]=((pts>>29)&0xe)|(buf[0]&0xf1); // this preserves the PTS / DTS / PTSwDTS top bits
    write2(buf+1,(pts>>14)|1);
    write2(buf+3,(pts<<1)|1);
}

void FixVobus(char *fbase,struct vob_summary *va)
{
    int h=-1;
    int i,j,scr,fnum=-1;
    int vff,vrew;
    char *buf=0;

    for( i=0; i<va->numvobus; i++ ) {
        if( va->vi[i].fnum!=fnum ) {
            char fname[200];
            if( h >= 0 )
                close(h);
            fnum=va->vi[i].fnum;
            sprintf(fname,"%s_%d.VOB",fbase,fnum);
            h=open(fname,O_WRONLY);
            if( h < 0 ) {
                fprintf(stderr,"ERR:  Error opening %s: %s\n",fname,strerror(errno));
                exit(1);
            }
        }
        buf=va->vi[i].sectdata;

        scr=readscr(buf+4);

        buf[0x2c]=0;
        write4(buf+0x2d,va->vi[i].sector);
        write4(buf+0x39,va->vi[i].pts[0]); // vobu_s_ptm
        write4(buf+0x3d,va->vi[i].pts[1]); // vobu_e_ptm
        write4(buf+0x45,buildtime(scr-va->vi[i].transpose,90000)); // total guess

        buf[0x406]=1;
        write4(buf+0x407,scr);
        write4(buf+0x40b,va->vi[i].sector); // current lbn
        for( j=0; j<va->vi[i].numref; j++ )
            write4(buf+0x413+j*4,va->vi[i].lastrefsect[j]-va->vi[i].sector);
        write2(buf+0x41f,va->vi[i].cellid);
        buf[0x422]=1;
        write4(buf+0x423,buildtime(scr,90000)); // total guess
        write4(buf+0x433,va->vi[0].pts[0]);
        write4(buf+0x437,va->vi[va->numvobus-1].pts[1]);
        write4(buf+0x4f1,getsect(va,i+1,0,va->vi[i].sector,0xbfffffff));
        write4(buf+0x541,getsect(va,i+1,0,va->vi[i].sector,0x3fffffff));
        write4(buf+0x545,getsect(va,i-1,0,va->vi[i].sector,0x3fffffff));
        write4(buf+0x595,getsect(va,i-1,0,va->vi[i].sector,0xbfffffff));
        for( j=0; j<va->vi[i].numaudpts; j++ )
            if( va->vi[i].audpts[j].pts+90000/75 >= va->vi[i].pts[0] ) // audio chunks for mp2 are 1/75th of a second, so add that much padding
                break;
        if( j>0 && j<va->vi[i].numaudpts )
            write2(buf+0x599,va->vi[i].audpts[j-1].sect-va->vi[i].sector);
        write4(buf+0x40f,va->vi[i].lastsector-va->vi[i].sector);
        vff=i;
        vrew=i;
        for( j=0; j<19; j++ ) {
            int nff,nrew;

            nff=findvobu(va,va->vi[i].pts[0]+timeline[j]*45045,1);
            nrew=findvobu(va,va->vi[i].pts[0]-timeline[j]*45045,0);
            write4(buf+0x53d-j*4,getsect(va,nff,nff>vff+1,va->vi[i].sector,0x3fffffff));
            write4(buf+0x549+j*4,getsect(va,nrew,nrew<vrew-1,va->vi[i].sector,0x3fffffff));
            vff=nff;
            vrew=nrew;
        }

        lseek(h,va->vi[i].fsect*2048,SEEK_SET);
        write(h,buf,2048);
        if( !(i&15) ) 
            fprintf(stderr,"STAT: fixing VOBU at %dMB (%d/%d, %d%%)\r",va->vi[i].sector/512,i,va->numvobus,i*100/va->numvobus);
    }
    close(h);
    fprintf(stderr,"\n");
}

void transpose_ts(unsigned char *buf,int tsoffs)
{
    // pack scr
    if( buf[0] == 0 &&
        buf[1] == 0 &&
        buf[2] == 1 &&
        buf[3] == 0xba )
    {
        writescr(buf+4,readscr(buf+4)+tsoffs);

        // video/audio?
        // pts?
        if( buf[14] == 0 &&
            buf[15] == 0 &&
            buf[16] == 1 &&
            (buf[17]>=0xc0 && buf[17]<0xf0) &&
            (buf[21] & 128))
        {
            writepts(buf+23,readpts(buf+23)+tsoffs);
            // dts?
            if( buf[21] & 64 ) {
                writepts(buf+28,readpts(buf+28)+tsoffs);
            }
        }
    }
}

static unsigned char bigwritebuf[16*2048];
static int writebufpos=0;
static int writefile=-1;

void writeflush()
{
    if( !writebufpos ) return;
    if( write(writefile,bigwritebuf,writebufpos) != writebufpos ) {
        fprintf(stderr,"ERR:  Error writing data\n");
        exit(1);
    }
    writebufpos=0;
}

unsigned char *writegrabbuf()
{
    unsigned char *buf;
    if( writebufpos == sizeof(bigwritebuf) )
        writeflush();
    buf=bigwritebuf+writebufpos;
    writebufpos+=2048;
    return buf;
}

void writeundo()
{
    writebufpos-=2048;
}

void writeclose()
{
    writeflush();
    if( writefile!=-1 ) {
        close(writefile);
        writefile=-1;
    }
}

void writeopen(const char *newname)
{
    writefile=open(newname,O_CREAT|O_WRONLY,0666);
    if( writefile < 0 ) {
        fprintf(stderr,"ERR:  Error opening %s: %s\n",newname,strerror(errno));
        exit(1);
    }
}

void scanvideoptr(unsigned char *buf,struct vobuinfo *thisvi,int cursect,int *lastrefsect)
{
    if( buf[0]==0 &&
        buf[1]==0 &&
        buf[2]==1 ) {
        if( buf[3]==0 ) {
            int ptype=(buf[5]>>3)&7;
            if( lastrefsect[0] != -1 && thisvi->numref < 3 ) {
                thisvi->lastrefsect[thisvi->numref++]=cursect;
                lastrefsect[0]=-1;
            }
            if( ptype == 1 || ptype == 2 ) // I or P frame
                lastrefsect[0]=cursect;
        } 
        if( buf[3]==0xb3 ) {
            int hsize,vsize,aspect,frame;
            
            hsize=(buf[4]<<4)|(buf[5]>>4);
            vsize=((buf[5]<<8)&0xf00)|buf[6];
            aspect=buf[7]>>4;
            frame=buf[7]&0xf;
            
            if( aspect==2 )
                aspect=0;
            else if( aspect==3 )
                aspect=1;
            else
                aspect=-1;
            
            updateframerate(frame);
            updateresolution(hsize,vsize);
            updatevideoattr("aspect ratio",&aspectflag,aspect,&aspectwarn);
        }
    }
}

void scanvideoframe(unsigned char *buf,struct vobuinfo *thisvi,int cursect,int prevsect,int *lastrefsect)
{
    int i,f=0x17+buf[0x16],l=0x14+buf[0x12]*256+buf[0x13];
    static unsigned char tempbuf[14]={255,255,255,255, 255,255,255, 0,0,0,0, 0,0,0};

    if( l-f<8 )
        return; // what kind of video frame has less than 8 bytes anyways?
    // copy the first 7 bytes to use with the prev 7 bytes in hdr detection
    memcpy(tempbuf+7,buf+f,7);
    for( i=0; i<7; i++ )
        scanvideoptr(tempbuf+i,thisvi,prevsect,lastrefsect);

    // quickly scan all but the last 7 bytes for a hdr
    for( i=f; i<l-7; i++ ) {
        if( buf[i]==0 && buf[i+1]==0 && buf[i+2]==1 )
            scanvideoptr(buf+i,thisvi,cursect,lastrefsect);
    }

    // use the last 7 bytes in the next iteration
    memcpy(tempbuf,buf+l-7,7);
}


int FindVobus(char *fbase,int numfiles,char **fname,struct vob_summary *va)
{
    unsigned char *buf;
    FILE *h;
    int cursect=0,prevvidsect=-1,fsect=-1,fnum,outnum=0;
    int lastrefsect=-1,tsoffs=0,backoffs=0;
    int backfillpts=0;

    memset(va,0,sizeof(struct vob_summary));
    memset(&cb,0,sizeof(cb));
    for( fnum=0; fnum<numfiles; fnum++ ) {
        int hadfirstvobu=0;
        if( va->numvobus ) {
            tsoffs=va->vi[va->numvobus-1].packts[1]+90000;
        }
        fprintf(stderr,"\nSTAT: Processing %s...\n",fname[fnum]);
        h=fopen(fname[fnum],"r");
        if( !h ) {
            fprintf(stderr,"ERR:  Error opening %s: %s\n",fname[fnum],strerror(errno));
            exit(1);
        }
        while(1) {
            if( fsect == 524272 ) {
                writeclose();
                fsect=-1;
            }
            buf=writegrabbuf();
            if( 2048 != fread(buf,1,2048,h) ) {
                writeundo();
                break;
            }
            if( !hadfirstvobu && buf[0]==0 && buf[1]==0 && buf[2]==1 && buf[3]==0xba )
                backoffs=readscr(buf+4);
            transpose_ts(buf,tsoffs-backoffs);
            if( fsect == -1 ) {
                char newname[200];
                outnum++;
                fsect=0;
                sprintf(newname,"%s_%d.VOB",fbase,outnum);
                writeopen(newname);
            }
            if( buf[14] == 0 &&
                buf[15] == 0 &&
                buf[16] == 1 &&
                buf[17] == 0xbb && // system header
                buf[38] == 0 &&
                buf[39] == 0 &&
                buf[40] == 1 &&
                buf[41] == 0xbf && // 1st private2
                buf[1024] == 0 &&
                buf[1025] == 0 &&
                buf[1026] == 1 &&
                buf[1027] == 0xbf ) // 2nd private2
            {
                hadfirstvobu=1;
                va->numvobus++;
                if( va->numvobus > va->maxvobus ) {
                    if( !va->maxvobus )
                        va->maxvobus=1;
                    else
                        va->maxvobus<<=1;
                    va->vi=realloc(va->vi,va->maxvobus*sizeof(struct vobuinfo));
                }
                va->vi[va->numvobus-1].sector=cursect;
                va->vi[va->numvobus-1].fsect=fsect;
                va->vi[va->numvobus-1].fnum=outnum;
                va->vi[va->numvobus-1].cellid=fnum+1;
                va->vi[va->numvobus-1].scr=readscr(buf+4);
                va->vi[va->numvobus-1].transpose=tsoffs;
                va->vi[va->numvobus-1].pts[0]=-1;
                va->vi[va->numvobus-1].pts[1]=-1;
                va->vi[va->numvobus-1].packts[0]=-1;
                va->vi[va->numvobus-1].packts[1]=-1;
                va->vi[va->numvobus-1].numref=0;
                va->vi[va->numvobus-1].numaudpts=0;
                va->vi[va->numvobus-1].sectdata=malloc(2048);
                memcpy(va->vi[va->numvobus-1].sectdata,buf,2048);
                if( !(va->numvobus&15) )
                    fprintf(stderr,"STAT: VOBU %d at %dMB\r",va->numvobus,cursect/512);
                lastrefsect=-1;
                backfillpts=1;
            }
            if( !hadfirstvobu ) {
                fprintf(stderr,"WARN: Skipping sector, waiting for first VOBU...\n");
                writeundo();
                continue;
            }
            va->vi[va->numvobus-1].lastsector=cursect;
            if( buf[0] == 0 &&
                buf[1] == 0 &&
                buf[2] == 1 &&
                buf[3] == 0xba &&
                buf[14] == 0 &&
                buf[15] == 0 &&
                buf[16] == 1 &&
                buf[17] == 0xe0 ) { // video
                scanvideoframe(buf,&va->vi[va->numvobus-1],cursect,prevvidsect,&lastrefsect);
                if( buf[21] & 128 ) { // check whether there's a pts
                    struct vobuinfo *vi=&va->vi[va->numvobus-1];
                    int pts=readpts(buf+23);
                    // fprintf(stderr,"pts %d (%02x %02x %02x %02x %02x)\n",pts,buf[23],buf[24],buf[25],buf[26],buf[27],buf[28]);
                    set_ts(vi->pts,pts);
                    if( backfillpts && va->numvobus >= 2 ) {
                        set_ts(va->vi[va->numvobus-2].pts,pts);
                    }
                }
                backfillpts=0;
                prevvidsect=cursect;
            }
            if( buf[0] == 0 &&
                buf[1] == 0 &&
                buf[2] == 1 &&
                buf[3] == 0xba &&
                buf[14] == 0 &&
                buf[15] == 0 &&
                buf[16] == 1 &&
                buf[17] == 0xc0 &&
                buf[21] & 128 ) {
                struct vobuinfo *vi=&va->vi[va->numvobus-1];
                int pts=readpts(buf+23);
                // fprintf(stderr,"pts[%d] %d (%02x %02x %02x %02x %02x)\n",vi->numaudpts,pts,buf[23],buf[24],buf[25],buf[26],buf[27]);
                if( vi->numaudpts < MAXAUDPTS ) {
                    vi->audpts[vi->numaudpts].pts=pts;
                    vi->audpts[vi->numaudpts].sect=cursect;
                    vi->numaudpts++;
                    if( vi->numaudpts >= MAXAUDPTS ) {
                        fprintf(stderr,"WARN: Reached %d audio pts for this VOBU\n",MAXAUDPTS);
                    }
                }
            }
            if( buf[0] == 0 &&
                buf[1] == 0 &&
                buf[2] == 1 &&
                buf[3] == 0xba ) // read time from pack header
            {
                long scr,scrhi,scrext;
                long long fulltime;
                scrhi=(buf[4]&0x20)>>5;
                scr=((buf[4]&0x18)<<27)|
                    ((buf[4]&3)<<28)|
                    (buf[5]<<20)|
                    ((buf[6]&0xf8)<<12)|
                    ((buf[6]&3)<<13)|
                    (buf[7]<<5)|
                    ((buf[8]&0xf8)>>3);
                scrext=((buf[8]&3)<<7)|
                    (buf[9]>>1);
                fulltime=((long long)scrhi)<<32|((long long)scr);
                fulltime*=300;
                fulltime+=scrext;
                if( fulltime > va->lasttime )
                    va->lasttime=fulltime;
                set_ts(va->vi[va->numvobus-1].packts,fulltime/300);
            }
            cursect++;
            fsect++;
        }
        fclose(h);
    }
    writeclose();
    fprintf(stderr,"STAT: VOBUS: %d, %dMB            \n",va->numvobus,cursect/512);
    fprintf(stderr,"STAT: last time=%lld.%02lld sec\n",va->lasttime/SCRTIME,(va->lasttime%SCRTIME)/(SCRTIME/100));
    return 1;
}

int numsectTMAPT(struct vob_summary *va)
{
    int ptsspan=va->vi[va->numvobus-1].pts[1]-va->vi[0].pts[0];
    int sz=16+4*(ptsspan/90090-1);

    return (sz+2047)/2048;
}

void CreateTMAPT(FILE *h,struct vob_summary *va)
{
    int ptsspan=va->vi[va->numvobus-1].pts[1]-va->vi[0].pts[0];
    int numtmapt,i;
    char buf[16];

    numtmapt=ptsspan/90090-1;
    memset(buf,0,16);
    buf[1]=1;
    write4(buf+4,15+numtmapt*4);
    buf[11]=0xc;
    buf[12]=1;
    write2(buf+14,numtmapt);
    fwrite(buf,1,16,h);
    for( i=0; i<numtmapt; i++ ) {
        int vobu=findvobu(va,va->vi[0].pts[0]+(i+1)*90090,1);
        write4(buf,va->vi[vobu].sector);
        fwrite(buf,1,4,h);
    }
    i=(16+4*numtmapt)&2047;
    if( i ) {
        i=2048-i;
        memset(buf,0,16);
        while(i>=16) {
            fwrite(buf,1,16,h);
            i-=16;
        }
        if( i )
            fwrite(buf,1,i,h);
    }
}

int numsectVOBUAD(struct vob_summary *va)
{
    int sz=4+va->numvobus*4;

    return (sz+2047)/2048;
}

void CreateVOBUAD(FILE *h,struct vob_summary *va)
{
    int i;
    char buf[16];

    write4(buf,va->numvobus*4+3);
    fwrite(buf,1,4,h);
    for( i=0; i<va->numvobus; i++ ) {
        write4(buf,va->vi[i].sector);
        fwrite(buf,1,4,h);
    }
    i=(4+va->numvobus*4)&2047;
    if( i ) {
        i=2048-i;
        memset(buf,0,16);
        while(i>=16) {
            fwrite(buf,1,16,h);
            i-=16;
        }
        if( i )
            fwrite(buf,1,i,h);
    }
}

void WriteIFO(FILE *h,struct vob_summary *va)
{
    static char buf[2048];
    static int colors[16]={
        0x00108080,
        0x00b48080,
        0x007d8080,
        0x00eb8080,
        0x0051f05a,
        0x00902235,
        0x00286df0,
        0x00ca4892,
        0x00aaaea4,
        0x00de8948,
        0x00aeb76d,
        0x0049a6dc,
        0x00616389,
        0x0098bc51,
        0x0037b389,
        0x00718947
    };
    int i;

    // sect 0: VTS toplevel
    memset(buf,0,2048);
    memcpy(buf,"DVDVIDEO-VTS",12);
    write4(buf+12,8+2*(numsectTMAPT(va)+numsectVOBUAD(va))+va->vi[va->numvobus-1].lastsector); // total guess...
    buf[31]=3+numsectTMAPT(va)+numsectVOBUAD(va);
    buf[33]=0x11;
    write4(buf+128,0x7ff);
    buf[199]=4+numsectTMAPT(va)+numsectVOBUAD(va);
    buf[203]=1;
    buf[207]=2;
    buf[215]=3;
    buf[227]=3+numsectTMAPT(va);
    buf[231]=4+numsectTMAPT(va);
    write2(buf+512,videoattr);
    buf[515]=1;
    buf[516]=audioflag<<5;
    buf[517]=0x41;
    buf[518]=0x65;
    buf[519]=0x6e;
    fwrite(buf,1,2048,h);

    // sect 1: VTS_PTT_SRPT
    memset(buf,0,2048);
    buf[1]=1; // # of titles
    buf[11]=0xc;
    for( i=0; i<NUMCELLS(va); i++ ) {
        buf[13+i*4]=1;
        buf[15+i*4]=i+1;
    }
    buf[7]=11+i*4;
    fwrite(buf,1,2048,h);
   
    // sect 2: VTS_PGCIT
    memset(buf,0,2048);
    buf[1]=1;
    buf[6]=1;
    buf[7]=11+28*NUMCELLS(va)+((NUMCELLS(va)+1)&0xFE);
    buf[8]=0x81;
    buf[15]=0x10;
    buf[18]=NUMCELLS(va);
    buf[19]=NUMCELLS(va);
    write4(buf+20,buildtime(va->lasttime,SCRTIME));
    buf[28]=0x80;
    for( i=0; i<16; i++ )
        write4(buf+180+i*4,colors[i]);
    buf[245]=0xec;
    write2(buf+246,read2(buf+244)+0x10);
    write2(buf+248,read2(buf+246)+((NUMCELLS(va)+1)&0xFE));
    write2(buf+250,read2(buf+248)+NUMCELLS(va)*24);
    buf[255]=1;
    buf[259]=0xf;
    buf[260]=0x30;
    buf[261]=1;
    for( i=0; i<NUMCELLS(va); i++ )
        buf[16+read2(buf+246)+i]=i+1;

    for( i=0; i<NUMCELLS(va); i++ ) {
        int j,firsttime;

        j=0;
        while(j<va->numvobus && va->vi[j].cellid!=i+1)
            j++;
        buf[16+read2(buf+248)+i*24]=2|(i?8:0);
        firsttime=va->vi[j].packts[0];
        write4(buf+24+read2(buf+248)+i*24,va->vi[j].sector);
        while(j+1<va->numvobus && va->vi[j+1].cellid==i+1)
            j++;
        write4(buf+20+read2(buf+248)+i*24,buildtime(va->vi[j].packts[1]-firsttime,90000));
        write4(buf+32+read2(buf+248)+i*24,va->vi[j].sector);
        write4(buf+36+read2(buf+248)+i*24,va->vi[j].lastsector);
    }
    for( i=0; i<NUMCELLS(va); i++ ) {
        buf[17+read2(buf+250)+i*4]=i+1;
        buf[19+read2(buf+250)+i*4]=1;
    }
    fwrite(buf,1,2048,h);

    // sect 3: ??? VTS_TMAPT
    CreateTMAPT(h,va);

    // sect 4: Call address table
    memset(buf,0,2048);
    buf[1]=NUMCELLS(va);
    buf[7]=7+NUMCELLS(va)*12;
    for( i=0; i<NUMCELLS(va); i++ ) {
        int j;

        j=0;
        while(j<va->numvobus && va->vi[j].cellid!=i+1)
            j++;
        buf[9+i*12]=i+1;
        buf[10+i*12]=1;
        write4(buf+12+i*12,va->vi[j].sector);
        while(j+1<va->numvobus && va->vi[j+1].cellid==i+1)
            j++;
        write4(buf+16+i*12,va->vi[j].lastsector);
    }
    fwrite(buf,1,2048,h);

    // sect 5: VOBU address map
    CreateVOBUAD(h,va);
}

void WriteIFOs(char *fbase,struct vob_summary *va)
{
    FILE *h;
    static char buf[1000];

    sprintf(buf,"%s_0.IFO",fbase);
    h=fopen(buf,"w");
    WriteIFO(h,va);
    fclose(h);

    sprintf(buf,"%s_0.BUP",fbase);
    h=fopen(buf,"w");
    WriteIFO(h,va);
    fclose(h);
}

int main(int argc,char **argv)
{
    struct vob_summary va;
    char *fbase=0;
    static char realfbase[1000];
    int vtsnum=1;
    static struct option longopts[]={
        {"basename",1,0,'o'},
        {"resolution",1,0,'r'},
        {"tv",1,0,'t'},
        {"aspect-ratio",1,0,'a'},
        {"audio-format",1,0,'A'},
        {"vts",1,0,'v'},
        {"next-vts",0,0,'n'},
        {0,0,0,0}
    };

    while(1) {
      int c=getopt_long(argc,argv,"a:A:no:r:t:v:",longopts,NULL);
      if( c == -1 )
	break;
      switch(c) {
      case 'o': fbase=optarg; break;

      case 'r': {
          int h,v;

          if( 2 != sscanf(optarg,"%dx%d",&h,&v) ) {
              if(!strcasecmp(optarg, "pal")) {
		  h=720;
		  v=576;
	      } else if(!strcasecmp(optarg, "ntsc")) {
		  h=720;
		  v=480;
	      } else {
                  fprintf(stderr,"ERR:  Unable to parse resolution: %s\n",optarg);
                  return 1;
	      }
          }
          if( resolutionflag != -1 ) {
              fprintf(stderr,"ERR:  Already defined resolution\n");
              return 1;
          }
          updateresolution(h,v);
          break;
      }

      case 't': {
          int p=-1;
          if( !strcasecmp(optarg,"pal") )
              p=1;
          else if (!strcasecmp(optarg,"ntsc") )
              p=0;
          else {
              fprintf(stderr,"ERR:  Unrecognized TV standard: %s\n",optarg);
              return 1;
          }
          if( tvflag != -1 ) {
              fprintf(stderr,"ERR:  Already defined TV standard.\n");
              return 1;
          }
          tvflag=p;
          break;
      }

      case 'a': {
          int a=-1;
          if( !strcasecmp(optarg,"16:9") )
              a=1;
          else if (!strcasecmp(optarg,"4:3") )
              a=0;
          else {
              fprintf(stderr,"ERR:  Unrecognized aspect ratio: %s\n",optarg);
              return 1;
          }
          if( aspectflag != -1 ) {
              fprintf(stderr,"ERR:  Already defined aspect ratio.\n");
              return 1;
          }
          aspectflag=a;
          break;
      }

      case 'A': {
          int a=-1;
          if( !strcasecmp(optarg,"ac3") )
              a=0;
          else if (!strcasecmp(optarg,"mp2"))
              a=2;
          else if( !strcasecmp(optarg,"pcm"))
              a=4;
          else {
              fprintf(stderr,"ERR:  Cannot determine audio format: %s\n",optarg);
              return 1;
          }
          if( audioflag != -1 ) {
              fprintf(stderr,"ERR:  Already defined audio format.\n");
              return 1;
          }
          audioflag=a;
          break;
      }

      case 'v':
          if( 1 != sscanf(optarg,"%d",&vtsnum) ) {
              fprintf(stderr,"ERR:  Could not parse VTS number: %s\n",optarg);
              return 1;
          }
          break;

      case 'n': {
          int i;

          for( i=1; i<=99; i++ ) {
              FILE *h;
              sprintf(realfbase,"%s_%02d_0.IFO",fbase,i);
              h=fopen(realfbase,"r");
              if( !h )
                  break;
              fclose(h);
          }
          fprintf(stderr,"STAT: Picking VTS %02d\n",i);
          vtsnum=i;
          break;
      }

      }
    }
    if( optind == argc ) {
        fprintf(stderr,"syntax: ifogen -o VTSBASE VOBFILE(s)\n");
        return 1;
    }
    sprintf(realfbase,"%s_%02d",fbase,vtsnum);
    FindVobus(realfbase,argc-optind,argv+optind,&va);
    setattr();
    buildvobulookup(&va);
    WriteIFOs(realfbase,&va);
    FixVobus(realfbase,&va);
    return 0;
}
