#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "playvideo.h"
#include "avi.h"
#include "avi_file.h"

int ReadChunkHead(FILE* f, uint32* ID, uint32* size)
{
    if (!fread(ID,sizeof(uint32),1,f)) return(FALSE);
    if (!fread(size,sizeof(uint32),1,f)) return(FALSE);
    return(TRUE);
}

int handle_hdrl(avih, v_strh, v_strf, a_strh, a_strf, f, chunksize, hasaudio, hasvideo)
    struct RIFF_avih **avih;
    struct RIFF_strh **v_strh;
    struct RIFF_strf_vids **v_strf;
    struct RIFF_strh **a_strh;
    struct RIFF_strf_auds **a_strf;
    FILE *f;
    int chunksize;
    int *hasaudio, *hasvideo;
{
    int filepos;
    int i=0;
    int cs, tag;
    
    filepos=ftell(f);
    
    while (i<chunksize) {
        ReadChunkHead(f, &tag, &cs);
        switch(tag) {
            case avihtag:
                *avih=(struct RIFF_avih *)malloc(cs+4);
                fseek(f,-4,SEEK_CUR);
                fread(*avih,cs+4,1,f);
                break;
            case LISTtag:
                fread(&tag,sizeof(uint32),1,f);
                switch(tag) {
                    case strltag: {
                        uint32 j,k, fcc;
                        int l=0, v=0, a=0;
                        while(l<cs) {
                            ReadChunkHead(f, &j, &k);
                            l+=k;
                            switch(j) {
                                case strhtag:
                                    fread(&fcc,4,1,f);
                                    fseek(f,-8,SEEK_CUR);
                                    switch(fcc) {
                                        case vidstag: 
                                            a=0; 
                                            v=1; 
                                            *hasvideo=1;
                                            *v_strh=(struct RIFF_strh *)malloc(k+4);
                                            fread(*v_strh,k+4,1,f);
                                            break;
                                        case audstag: 
                                            a=1; 
                                            v=0;
                                            *hasaudio=1;
                                            *a_strh=(struct RIFF_strh *)malloc(k+4);
                                            fread(*a_strh,k+4,1,f);
                                            break;
                                        default: a=0; v=0; fseek(f,k+4,SEEK_CUR); break;
                                    }
                                    break;
                                case strftag:
                                    if(a) {
                                        *a_strf=(struct RIFF_strf_auds *)malloc(k+4);
                                        fseek(f,-4,SEEK_CUR);
                                        fread(*a_strf,k+4,1,f);
                                        a=0;
                                    } else if(v) {
                                        *v_strf=(struct RIFF_strf_vids *)malloc(k+4);
                                        fseek(f,-4,SEEK_CUR);
                                        fread(*v_strf,k+4,1,f);                                                                                                 v=0;
                                    } else fseek(f,k,SEEK_CUR);
                                    break;
                                default:
                                    fseek(f,k,SEEK_CUR);
                            }
                        }
                    }
                    break;
                }
        }
        i+=8+cs;
        fseek(f,filepos+i,SEEK_SET);
    }
    return 0;
}

int avi_read_vframe(VIDEO_FILE *vf, unsigned char *buf, int frame, int *len) {
    AVI_FILE *p;
    p=vf->extra;
    *len=p->vindex[frame].len;
    fseek(p->f,p->vindex[frame].pos+8,SEEK_SET);
    fread(buf,*len,1,p->f);
    return 0;
}

int avi_read_aframe(VIDEO_FILE *vf, unsigned char *buf, int frame) {
    AVI_FILE *p;
    p=vf->extra;
    if(frame!=p->last_aframe) {
        long long s;

        s=(long long)frame*p->a_strf->av_bps*vf->us_frame/1000000;
        p->afr=0;
        while((p->afr<vf->aframes) && (s>=p->aindex[p->afr].offset+p->aindex[p->afr].len))p->afr++;
        p->aleft=s-p->aindex[p->afr].offset;
    }

    if((int)p->aindex[p->afr].len-p->aleft>=vf->asize) {
        fseek(p->f,p->aindex[p->afr].pos+p->aleft+8,SEEK_SET);
        fread(buf,vf->asize,1,p->f);
        p->aleft+=vf->asize;
        if(p->aleft==p->aindex[p->afr].len) {
            p->aleft=0;
            p->afr++;
        }
    } else {
        fseek(p->f,p->aindex[p->afr].pos+p->aleft+8,SEEK_SET);
        fread(buf,p->aindex[p->afr].len-p->aleft,1,p->f);
        fseek(p->f,p->aindex[p->afr+1].pos+8,SEEK_SET);
        fread(buf+p->aindex[p->afr].len-p->aleft,vf->asize+p->aleft-p->aindex[p->afr].len,1,p->f);
        p->aleft=vf->asize+p->aleft-p->aindex[p->afr].len;
        p->afr++;
    }
    p->last_aframe=frame+1;
    return vf->asize;
}

int avi_open_file(char *filename, VIDEO_FILE *vf, int verbose) {
    char st[5];
    int tag, chunksize;
    unsigned int filepos;
    int movi=0, movi_pos, hdrl=0, hdrl_pos, idx=0, idx_pos;
    int ai,vi;
    AVI_FILE *p;

    p=(AVI_FILE *)malloc(sizeof(AVI_FILE));
    vf->extra=p;
    vf->hasaudio=0;
    vf->hasvideo=0;

    if (!(p->f=fopen(filename,"rb"))) {
	fprintf(stderr,"\n\n *** Error opening file %s. Program aborted!\n", filename);
	return(1);
    }
    
    strncpy(vf->filename,filename,253);
        
    fseek(p->f, 0, SEEK_END);
    p->filesize = ftell(p->f);
    fseek(p->f, 0, SEEK_SET);

    if(verbose)fprintf(stderr,"Contents of file %s (%i bytes):\n\n",
	   filename,p->filesize);

    ReadChunkHead(p->f, &tag, &chunksize);
    
    if(tag!=RIFFtag) {
        fprintf(stderr, "Not a RIFF file.\n");
        exit(1);
    }
    
    fread(&tag,sizeof(uint32),1,p->f);
    FOURCC2Str(tag,st);
    
    if(verbose)fprintf(stderr,"Riff of type %s   size %u bytes.\n",st,chunksize);

    filepos=12;

    p->avih=NULL;
    p->a_strf=NULL;
    p->v_strf=NULL;
    p->a_strh=NULL;
    p->v_strh=NULL;
    p->aindex=NULL;
    p->vindex=NULL;
    vf->palette=NULL;
    
    for (; filepos < p->filesize;) {
        ReadChunkHead(p->f, &tag, &chunksize);
        FOURCC2Str(tag,st);
        switch(tag) {
            case LISTtag:
                fread(&tag,sizeof(uint32),1,p->f);
                FOURCC2Str(tag,st);
                
                if(verbose)fprintf(stderr,"LIST %s   size %u bytes.\n",st,chunksize);

                switch(tag) {
                    case movitag:
                        if(!movi) {
                            movi++;
                            movi_pos=filepos;
                        }
                    break;
                    case hdrltag:
                        if(!hdrl) {
                            hdrl++;
                            hdrl_pos=filepos;
                            handle_hdrl(&p->avih, &p->v_strh, &p->v_strf, &p->a_strh, 
                                        &p->a_strf, p->f, chunksize, &vf->hasaudio, &vf->hasvideo);
                        }
                }
                break;
            case idx1tag:
                if(verbose)fprintf(stderr,"idx1 chunk, %u elements.\n",chunksize/16);
                if(!idx) {
                    int i;
                    int extpos=-1;

                    idx_pos=filepos;                   
                    idx++;
                    vi=0;
                    ai=0;
                    i=0;
                    if(vf->hasvideo)p->vindex=(vid_idx *)malloc(sizeof(vid_idx)*p->avih->frames);
                    if(vf->hasaudio)p->aindex=(aud_idx *)malloc(sizeof(aud_idx)*p->avih->frames);
                    while(i<chunksize/16) {
                        uint32 ct;
                        fread(&ct,4,1,p->f);
                        switch(ct) {
                            case _00dbtag:
                            case _00ivtag:
                            case _00dctag:
                                if(vf->hasvideo && (vi<p->avih->frames)) {
                                    fread(&p->vindex[vi],12,1,p->f);
                                    if(extpos==-1){
                                        if(p->vindex[vi].pos<movi_pos) { 
                                            extpos= movi_pos+8;
                                        } else {
                                            extpos= 0;
                                        }
                                    }
                                    p->vindex[vi].pos+=extpos;
                                    vi++;
                                }
                                break;
                            case _01wbtag:
                                if(vf->hasaudio && (ai<p->avih->frames)) {
                                    fread(&p->aindex[ai],12,1,p->f);
                                    if(ai) {
                                        p->aindex[ai].offset=p->aindex[ai-1].offset+p->aindex[ai-1].len; 
                                    } else p->aindex[ai].offset=0;
                                    if(extpos==-1){                                        
                                        if(p->aindex[vi].pos<movi_pos) { 
                                            extpos= movi_pos+8;
                                        } else {
                                            extpos= 0;
                                        }
                                    }
                                    p->aindex[ai].pos+=extpos;
                                    ai++;
                                }
                                break;
                            default:
                                fseek(p->f,12,SEEK_CUR);
                                break;
                        }
                        i++;
                    }
                }
                break;
            default:
                if(verbose)fprintf(stderr,"Unexpected tag %s   size %u bytes.\n",st,chunksize);
        }
        filepos+= 8 + chunksize;
        fseek(p->f,filepos,SEEK_SET);
    }
    
    if(!movi) {
        fprintf(stderr,"No movi LIST.\n");
        exit(1);
    }
    
    if(!idx) {        
        int i;
        int size;

        if(verbose)fprintf(stderr,"no idx1. Building index from movi LIST.\n");

        fseek(p->f,movi_pos+4,SEEK_SET);
        fread(&size,4,1,p->f);
        fseek(p->f,movi_pos+12,SEEK_SET);
        filepos=movi_pos+12;
        vi=0;
        ai=0;
        i=0;
        if(vf->hasvideo)p->vindex=(vid_idx *)malloc(sizeof(vid_idx)*p->avih->frames);
        if(vf->hasaudio)p->aindex=(aud_idx *)malloc(sizeof(aud_idx)*p->avih->frames);
        while((filepos<movi_pos+size) && (filepos<p->filesize)) {
            uint32 ct;
            int len;
            fread(&ct,4,1,p->f);
            fread(&len,4,1,p->f);
            len=((len-1)|1)+1;
            if(filepos+len+8<p->filesize)
                switch(ct) {
                    case _00dbtag:
                    case _00ivtag:
                    case _00dctag:
                        if(vf->hasvideo && (vi<p->avih->frames)) {
                            p->vindex[vi].len=len;
                            p->vindex[vi].pos=filepos;
                            vi++;
                        }
                        break;
                    case _01wbtag:
                        if(vf->hasaudio && (ai<p->avih->frames)) {
                            if(ai) {
                                p->aindex[ai].offset=p->aindex[ai-1].offset+p->aindex[ai-1].len; 
                            } else p->aindex[ai].offset=0;
                            p->aindex[ai].len=len;
                            p->aindex[ai].pos=filepos;
                            ai++;
                        }
                        break;
                    case LISTtag:
                        len=4;
                        break;
                }
            filepos+=8+len;
            fseek(p->f,len,SEEK_CUR);
        }
    }
    if(p->avih) {
        vf->us_frame=p->avih->us_frame;
    }
    if(vf->hasaudio && ai) {
        vf->achannels=p->a_strf->channels;
        vf->arate=p->a_strf->rate;
        vf->aformat=p->a_strf->size;
        if(vf->aformat==4)vf->hasaudio=0;
        vf->aframes=ai;
        p->last_aframe=-2;
        vf->asize=(long long)p->a_strf->av_bps*vf->us_frame/1000000;
    }
    if(vf->hasvideo && vi) {
        vf->vframes=vi;
        vf->width=p->v_strf->width;
        vf->height=p->v_strf->height;
        vf->compression[0]=p->v_strf->compression[0];
        vf->compression[1]=p->v_strf->compression[1];
        vf->compression[2]=p->v_strf->compression[2];
        vf->compression[3]=p->v_strf->compression[3];
        vf->bitspp=p->v_strf->bit_cnt;
        vf->colors=p->v_strf->num_colors;
        if((vf->colors>0) && (vf->colors<257)) {
            int i, s;
            char *q=(char *)p->v_strf;

            vf->palette=(uint32 *)malloc(1024);
            s=*(int *)q;
            q+=44;
            for(i=0;(i<vf->colors) && (i*4+44<=s); i++) {
                vf->palette[i]=*(uint32 *)(q+4*i);
            }
        } else vf->palette=NULL;
    }
    return 0;
}

int avi_check_file(char *filename) {
    int c1,c2,c3;
    FILE *f;
    
    f=fopen(filename,"rb");
    if(!f) return 0;
    fread(&c1,4,1,f);
    fread(&c2,4,1,f);
    fread(&c3,4,1,f);
    fclose(f);
    if((c1==RIFFtag)&&(c3==AVItag))return 1;
    return 0;
}

int avi_close_file(VIDEO_FILE *vf) {
    AVI_FILE *p;
    p=vf->extra;

    if(vf->palette)free(vf->palette);
    if(p->vindex)free(p->vindex);
    if(p->aindex)free(p->aindex);
    if(p->v_strf)free(p->v_strf);
    if(p->a_strf)free(p->a_strf);
    if(p->v_strh)free(p->v_strh);
    if(p->a_strh)free(p->a_strh);
    if(p->avih)free(p->avih);
    fclose(p->f);
    free(p);
}
