#include "pch.h"
#include "engine.h"

enum
{
    AVIF_HASINDEX        = 0x000010,
    AVIF_MUSTUSEINDEX    = 0x000020,
    AVIF_ISINTERLEAVED   = 0x000100,
    AVIF_TRUSTCKTYPE     = 0x000800,
    AVIF_WASCAPTUREFILE  = 0x010000,
    AVIF_COPYRIGHTED     = 0x020000
};

#define AVI_4CC(s) (*(const uint *)s)

struct avifileheader
{
    uint microsecsperframe;
    uint maxbytespersec;
    uint padding;
    uint flags;
    uint totalframes, initialframes;
    uint streams;
    uint buffersize;
    uint width, height;
    uint reserved[4];
};

struct avistreamheader
{
    uint fcctype, fcchandler;
    uint flags;
    ushort priority, language;
    uint initialframes;
    uint scale, rate;
    uint start, length;
    uint buffersize;
    uint quality;
    uint samplesize;
    ushort left, top, right, bottom;
};

struct avivideoformat
{
    uint headersize;
    uint width, height;
    ushort planes, bitcount;
    uint compression;
    uint imagesize;
    uint xres, yres;
    uint colorsused, colorsrequired;
};

struct avivideoproperties
{
    uint vidformat, vidstandard;
    uint vertrefresh;
    uint htotal, vtotal;
    uint frameaspect, framewidth, frameheight;
    uint fieldsperframe;
};

struct avivideofield
{
    uint compressheight, compresswidth;
    uint validheight, validwidth;
    uint validxoffset, validyoffset;
    uint videoxoffset, videoyoffset;
};

struct aviwriter
{
    enum
    {
        MAX_CHUNK_DEPTH = 16
    };

    FILE *file;
    uint chunkoffsets[MAX_CHUNK_DEPTH];
    int chunkdepth;
    uint fileheaderoffset, videoheaderoffset;
    int videow, videoh, videofps, videoframes;

    aviwriter() : file(NULL) {}

    void startchunk(const char *fcc)
    {
        fwrite(fcc, 1, 4, file);
        const uint size = 0;
        fwrite(&size, 1, 4, file);
        chunkoffsets[++chunkdepth] = ftell(file);
    }
    
    void listchunk(const char *fcc, const char *lfcc)
    {
        startchunk(fcc);
        fwrite(lfcc, 1, 4, file);
    }

    void endchunk()
    {
        if(chunkdepth<0) return;
        uint size = ftell(file) - chunkoffsets[chunkdepth];
        fseek(file, chunkoffsets[chunkdepth] - 4, SEEK_SET);
        size = SDL_SwapLE32(size);
        fwrite(&size, 1, 4, file);
        fseek(file, 0, SEEK_END);
        --chunkdepth;
    }

    bool open(const char *fname, int w, int h, int fps)
    {
        if(file) return false;
        file = fopen(path(fname, true), "wb");
        if(!file) return false;

        chunkdepth = -1;
        videow = w;
        videoh = h;
        videofps = fps;
        videoframes = 0;

        listchunk("RIFF", "AVI ");

        listchunk("LIST", "hdrl");
        startchunk("avih");
        fileheaderoffset = ftell(file);
        avifileheader fhdr;
        memset(&fhdr, 0, sizeof(fhdr));
        fhdr.microsecsperframe = SDL_SwapLE32(1000000 / videofps);
        fhdr.streams = SDL_SwapLE32(1);
        fhdr.width = SDL_SwapLE32(videow);
        fhdr.height = SDL_SwapLE32(videoh);
        fwrite(&fhdr, 1, sizeof(fhdr), file);
        endchunk(); // avih

        uint framesize = videow*videoh + 2*(videow/2)*(videoh/2);

        listchunk("LIST", "strl");
        startchunk("strh");
        videoheaderoffset = ftell(file);
        avistreamheader vhdr;
        memset(&vhdr, 0, sizeof(vhdr));
        vhdr.fcctype = AVI_4CC("vids");
        vhdr.fcchandler = AVI_4CC("I420");
        vhdr.scale = SDL_SwapLE32(1);
        vhdr.rate = SDL_SwapLE32(videofps);
        vhdr.buffersize = SDL_SwapLE32(framesize);
        vhdr.right = SDL_SwapLE16(videow);
        vhdr.bottom = SDL_SwapLE16(videoh);
        fwrite(&vhdr, 1, sizeof(vhdr), file);
        endchunk(); // strh

        startchunk("strf");
        avivideoformat vfmt;
        memset(&vfmt, 0, sizeof(vfmt));
        vfmt.headersize = SDL_SwapLE32(sizeof(vfmt));
        vfmt.width = SDL_SwapLE32(videow);
        vfmt.height = SDL_SwapLE32(videoh);
        vfmt.planes = SDL_SwapLE16(3);
        vfmt.bitcount = SDL_SwapLE16(12);
        vfmt.compression = AVI_4CC("I420");
        vfmt.imagesize = SDL_SwapLE32(framesize);
        fwrite(&vfmt, 1, sizeof(vfmt), file);
        endchunk(); // strf

        startchunk("vprp");
        avivideoproperties vprop;
        memset(&vprop, 0, sizeof(vprop));
        vprop.vertrefresh = SDL_SwapLE32(videofps);
        vprop.htotal = SDL_SwapLE32(videow);
        vprop.vtotal = SDL_SwapLE32(videoh);
        int aw = videow, ah = videoh;
        while(!(aw%5) && !(ah%5)) { aw /= 5; ah /= 5; }
        while(!(aw%3) && !(ah%3)) { aw /= 3; ah /= 3; }
        while(!(aw%2) && !(ah%2)) { aw /= 2; ah /= 2; }
        vprop.frameaspect = SDL_SwapLE32((aw<<16)|ah);
        vprop.framewidth = SDL_SwapLE32(videow);
        vprop.frameheight = SDL_SwapLE32(videoh);
        vprop.fieldsperframe = SDL_SwapLE32(1);
        fwrite(&vprop, 1, sizeof(vprop), file);

        avivideofield vfield;
        memset(&vfield, 0, sizeof(vfield));    
        vfield.compressheight = SDL_SwapLE32(videoh);
        vfield.compresswidth = SDL_SwapLE32(videow);
        vfield.validheight = SDL_SwapLE32(videoh);
        vfield.validwidth = SDL_SwapLE32(videow);
        fwrite(&vfield, 1, sizeof(vfield), file);
        endchunk(); // vprp

        endchunk(); // LIST strl
        endchunk(); // LIST hdrl

        listchunk("LIST", "movi");

        return true;
    } 
    
    void close()
    {
        if(!file) return;
        endchunk(); // LIST movi
        endchunk(); // RIFF AVI
        fclose(file);
    }
};

void foo()
{
    aviwriter blah;
    blah.open("foo.avi", 640, 480, 30);
    blah.close();
}
COMMAND(foo, "");


