// TI

#include <gnu/targets/std.h>
#include <xdc/std.h>
#include <ti/xdais/xdas.h>
#include <ti/xdais/dm/xdm.h>
#include <ti/sdo/ce/CERuntime.h>
#include <ti/sdo/ce/Engine.h>
#include <ti/sdo/ce/video/viddec.h>
#include <ti/sdo/ce/video/videnc.h>
#include <ti/sdo/ce/osal/Memory.h>


// TinyScheme
#define USE_INTERFACE 1
#include <scheme.h>
#include <scheme-private.h>

// C
#include <string.h>
#include <stdlib.h>


// ERRORS

pointer scheme_call(scheme*, pointer, pointer);




// SCHEME EXTENSIONS



// TI SCHEME EXT

// http://tiexpressdsp.com/index.php/Codec_Engine_Application_Developers_Guide

void free_engine(void *data,scheme *sc) {
    printf("free_engine(%p)\n", data);
    Engine_close(data);
}
DEFINE_TYPE(engine)

const char *ppsz_engine_error[] = {
    [Engine_EOK] = "Ok",
    [Engine_EEXIST] = "Engine name doesn't exist",
    [Engine_ENOMEM] = "Can't allocate engine memory",
    [Engine_EDSPLOAD] = "Unable to load the DSP image (used from other process?)",
    [Engine_ENOCOMM] = "Can't create communication connection to DSP",
    [Engine_ENOSERVER] = "Can't locate the server on the DSP",
    [Engine_ECOMALLOC] = "Can't allocate communication buffer",
    [Engine_ERUNTIME] = "Engine runtime failure",
    [Engine_ECODECCREATE] = "Engine codec creation failed",
    [Engine_ECODECSTART] = "Engine codec start failed",
    [Engine_EINVAL] = "Bad parameter",
    [Engine_EBADSERVER] = "Incompatible server specified",
    [Engine_ENOTAVAIL] = "Service not available",
    //[Engine_EWRONGSTATE] = "Call can't be made at this time",
    //[Engine_EINUSE] = "Call can't be made at this time because a required resource is in use",
    //[Engine_ENOTFOUND] = "Entity was not found",
};

/* An `engine' is a handle to the global Codec Engine.  Only one
   process at a time can access the DSP. */


pointer make_engine(scheme *sc, pointer args) {
    argument a[] = {{a_string}, {}};
    a_check(sc, args, a);
    char *name = a[0].v.string;

    Engine_Error err;
    Engine_Handle e = Engine_open(name, NULL, &err);
    if( err != Engine_EOK ) {
        fprintf(stderr, "Error while opening engine `%s': %s\n",
                name, ppsz_engine_error[err]);
        return sc->F;
    }
    return wrap_engine(sc, e);
}



pointer algo_info(scheme *sc, pointer args) {
    argument a[] = {{a_string}, {}};
    a_check(sc, args, a);
    Engine_AlgInfo info;
    info.algInfoSize = sizeof(info);
    int i = 0;
    // pointer rv = sc->NIL;
    while (Engine_EOK == Engine_getAlgInfo((String)a[0].v.string, 
                                           &info, i)) {
        scheme_yield(sc, wrap_string(sc, info.name));
        scheme_yield(sc, info.isLocal ? sc->T : sc->F);
        char **tt;
        for (tt = info.typeTab; *tt; tt++) {
            scheme_yield(sc, wrap_string(sc, *tt));
        }
        scheme_yield(sc, sc->NIL); // end of line
        i++;
    }
    return sc->NIL;
    // return reverse_in_place(sc, sc->NIL, rv);
}

void free_viddec(void *x, scheme *sc) { 
    fprintf(stderr, "free_viddec(%p)\n", x);
    VIDDEC_delete((VIDDEC_Handle)x);
}
void free_videnc(void *x, scheme *sc) { 
    fprintf(stderr, "free_videnc(%p)\n", x);
    VIDENC_delete((VIDENC_Handle)x);
}

    
DEFINE_TYPE(viddec)
DEFINE_TYPE(videnc)

#define XDAS_INIT(data) {memset(&data, 0, sizeof(data)); data.size = sizeof(data);}

pointer make_videnc(scheme *sc, pointer args) {
    argument a[] = {{a_engine}, {a_string},
                    {a_integer}, {a_integer},  // h w
                    {a_integer}, {a_integer},  // fr br
                    {}};
    a_check(sc, args, a);
    VIDENC_Params p; XDAS_INIT(p); // ti/xdais/dm/ividenc.h

    p.encodingPreset = XDM_DEFAULT;
    // XDM_HIGH_QUALITY 
    // XDM_HIGH_SPEED
    // XDM_USER_DEFINED
    p.rateControlPreset = IVIDEO_RATECONTROLPRESET_DEFAULT;
    // IVIDEO_LOW_DELAY;
    // IVIDEO_STORAGE
    // IVIDEO_TWOPASS
    // IVIDEO_NONE
    // IVIDEO_USER_DEFINED
    p.maxWidth  = a[2].v.integer; // 720;
    p.maxHeight = a[3].v.integer; // 576;
    p.maxFrameRate = a[4].v.integer; // 50000; // frames per 1000 sec
    p.maxBitRate = a[5].v.integer; // 1000;
    p.dataEndianness = XDM_BYTE;
    p.inputChromaFormat = XDM_YUV_422ILE; // XDM_GRAY; // XDM_YUV_420P
    p.inputContentType = IVIDEO_PROGRESSIVE; // IVIDEO_INTERLACED

    VIDENC_Handle ve = VIDENC_create((Engine_Handle)a[0].v.data,
                                     (String)a[1].v.string, &p);
    if (!ve) {
        scheme_error(sc, "Can't create VIDENC", NULL);
    }
    return wrap_videnc(sc, ve);
}

pointer make_viddec(scheme *sc, pointer args) {
    argument a[] = {{a_engine}, {a_string}, {}};
    a_check(sc, args, a);
    VIDDEC_Params p; XDAS_INIT(p); // ti/xdais/dm/ividdec.h
    p.maxWidth  = 720;
    p.maxHeight = 576;
    p.maxFrameRate = 0;
    p.maxBitRate = 0;
    p.dataEndianness = XDM_BYTE;
    p.forceChromaFormat = XDM_YUV_422ILE; // XDM_GRAY; // XDM_YUV_420P
    
    VIDDEC_Handle vd = VIDDEC_create((Engine_Handle)a[0].v.data,
                                     (String)a[1].v.string, &p);
    if (!vd) {
        scheme_error(sc, "Can't create VIDDEC", NULL);
    }
    return wrap_viddec(sc, vd);
}

void free_bufdesc(void *data, scheme *sc) {
    fprintf(stderr, "free_bufdesc(%p)\n", data);
    XDM_BufDesc *buf = (XDM_BufDesc *)data;
    int i;
    for (i=0; i<buf->numBufs; i++) {
        void *b = buf->bufs[i];
        if (b) Memory_contigFree(b, buf->bufSizes[i]);
    }
    free(buf->bufs);
    free(buf->bufSizes);
    free(data);
}
DEFINE_TYPE(bufdesc);

pointer create_bufdesc(scheme *sc, XDAS_Int32 n, XDAS_Int32 *size) {
    XDM_BufDesc *buf = (XDM_BufDesc *)malloc(sizeof(*buf));
    buf->numBufs = n;
    buf->bufs = calloc(n, sizeof(XDAS_Int8*));
    buf->bufSizes = calloc(n, sizeof(XDAS_Int32));
    int i;
    for (i=0; i<n; i++){
        buf->bufSizes[i] = size[i];
        /* The DSP needs physically contiguous pages from GPP/DSP
           shared memory. */
        if (!(buf->bufs[i] 
              = Memory_contigAlloc(size[i],Memory_DEFAULTALIGNMENT))) {
            free_bufdesc(buf, sc);
            scheme_error(sc, "Can't allocate contiguous memory.", NULL);
        }
    }
    return wrap_bufdesc(sc, buf);
}

#define LET(type, name, ai) type name = (type)ai.v.data

pointer bufdesc_saw_bang(scheme *sc, pointer args) {
    argument a[] = {{a_bufdesc}, {a_integer}, {}};
    a_check(sc, args, a);
    int fill = a[1].v.integer;
    LET(XDM_BufDesc*, bd, a[0]);
    int i,j;
    for(i=0; i<bd->numBufs; i++){
        // printf("filling %d starting %d\n", i, fill);
        for(j=0; j<bd->bufSizes[i]; j++) {
            (bd->bufs[i])[j] = fill++;
        }
    }
    return sc->T;
}

pointer bufdesc_dump(scheme *sc, pointer args) {
    argument a[] = {{a_bufdesc}, {a_string}, {}};
    a_check(sc, args, a);
    LET(XDM_BufDesc*, bd, a[0]);
    FILE *f = fopen(a[1].v.string, "a");
    if (!f) { scheme_error(sc, "Can't create file", args); }
    fwrite(bd->bufs[0], 1, bd->bufSizes[0], f);
    fclose(f);
    return sc->T;
}

void yield_io_bufdescs (scheme *sc, XDM_AlgBufInfo *i) {
    scheme_yield(sc, 
                 create_bufdesc(sc, 
                                i->minNumOutBufs, 
                                i->minOutBufSize));
    scheme_yield(sc,
                 create_bufdesc(sc,
                                i->minNumInBufs, 
                                i->minInBufSize));
}

// query viddec and create IO buffers
static void 
viddec_control(scheme *sc,
               VIDDEC_Handle vd,
               int msg,
               VIDDEC_DynamicParams *dp,
               VIDDEC_Status *s,
               const char *err) {
    if (VIDDEC_EOK != VIDDEC_control(vd, msg, dp, s)) {
        scheme_error(sc, err, NULL);
    }; 
}

pointer viddec_iobufs(scheme *sc, pointer args) {
    argument a[] = {{a_viddec}, {}};
    a_check(sc, args, a);
    VIDDEC_Handle vd = (VIDDEC_Handle)a[0].v.data;
    VIDDEC_DynamicParams dp; XDAS_INIT(dp); // ividdec.h
    VIDDEC_Status s; XDAS_INIT(s); // ividdec.h

    viddec_control(sc, vd, XDM_GETBUFINFO, &dp, &s,
                   "Failed to get buffer info");

    yield_io_bufdescs(sc, &s.bufInfo);
    viddec_control(sc, vd, XDM_GETSTATUS, &dp, &s,
                   "Failed to get decoder status.");    
    return sc->NIL;
}

static void 
videnc_control(scheme *sc,
               VIDENC_Handle ve,
               int msg,
               VIDENC_DynamicParams *dp,
               VIDENC_Status *s,
               const char *err) {
    if (VIDENC_EOK != VIDENC_control(ve, msg, dp, s)) {
        scheme_error(sc, err, NULL);
    }; 
}


pointer videnc_iobufs(scheme *sc, pointer args) {
    argument a[] = {{a_videnc}, {}};
    a_check(sc, args, a);
    VIDENC_Handle ve = (VIDENC_Handle)a[0].v.data;
    VIDENC_DynamicParams dp; XDAS_INIT(dp); // ividenc.h
    VIDENC_Status s; XDAS_INIT(s); // ividenc.h
    dp.inputWidth  = 720;
    dp.inputHeight = 576;
    dp.refFrameRate = 
        dp.targetFrameRate = 25000;
    dp.targetBitRate = 1000;
    dp.intraFrameInterval = 1; // ??
    dp.generateHeader = XDM_ENCODE_AU;
    dp.captureWidth = 0;
    dp.forceIFrame = 0;  // FIXME!
    videnc_control(sc, ve, XDM_SETPARAMS, &dp, &s,
                   "Failed to set encoder params.");
    videnc_control(sc, ve, XDM_GETBUFINFO, &dp, &s,
                   "Failed to get buffer info.");

    yield_io_bufdescs(sc, &s.bufInfo);

    videnc_control(sc, ve, XDM_GETSTATUS, &dp, &s,
                   "Failed to get encoder status");    
    return sc->NIL;
}


// see ti/include/xdais/dm/xdm.h
static pointer extended_error(scheme *sc, XDAS_Int32 e) {
    char msg[1000];
    sprintf(msg, "%08lx", e);
#define EXT_ERR(pred,x) if(pred(e)) {strcat(msg, ", "); strcat(msg, x);}
    EXT_ERR(XDM_ISAPPLIEDCONCEALMENT, "Applied bit concealment");
    EXT_ERR(XDM_ISINSUFFICIENTDATA,   "Insufficient data");
    EXT_ERR(XDM_ISCORRUPTEDDATA,      "Corrupted data");
    EXT_ERR(XDM_ISCORRUPTEDHEADER,    "Corrupted header");
    EXT_ERR(XDM_ISUNSUPPORTEDINPUT,   "Unsupported input");
    EXT_ERR(XDM_ISUNSUPPORTEDPARAM,   "Unsupported param");
    EXT_ERR(XDM_ISFATALERROR,         "Fatal error");
    return wrap_string(sc, msg);
}
pointer videnc_process(scheme *sc, pointer args) {
    argument a[] = {{a_videnc}, {a_bufdesc}, {a_bufdesc}, {}};
    a_check(sc, args, a);
    VIDENC_Handle ve = (VIDENC_Handle)a[0].v.data;
    VIDENC_InArgs in_args; XDAS_INIT(in_args);
    VIDENC_OutArgs out_args; XDAS_INIT(out_args);
    XDM_BufDesc *in  = (XDM_BufDesc *)a[1].v.data;
    XDM_BufDesc *out = (XDM_BufDesc *)a[2].v.data;
    
    if (VIDENC_EOK != VIDENC_process(ve, in, out, &in_args, &out_args)) {
        scheme_error(sc, "Video encoding failed",
                     extended_error(sc, out_args.extendedError));
    }
    scheme_yield(sc, wrap_integer(sc, out_args.bytesGenerated));
    scheme_yield(sc, wrap_integer(sc, out_args.encodedFrameType));
    scheme_yield(sc, wrap_integer(sc, out_args.inputFrameSkip));
    return sc->NIL;
}

// SCHEME TOP
scheme *sc = NULL;
void eval(char *str) { scheme_load_string(sc, str); }
void repl(void) { scheme_load_file(sc, stdin); }
void load(scheme *sc, char *filename) {
    FILE *f = fopen(filename, "r");
    scheme_load_file(sc, f);
    fclose(f);
}

pointer scm_load_ext(scheme*, pointer);
void scheme_boot(void) {

    /* init.scm */
    sc = scheme_init_new();

    defun(sc, "load-extension", scm_load_ext);


    scheme_set_output_port_file(sc, stdout);
    load(sc, "init.scm");

    /* ffi */
    defun(sc, "make-engine", make_engine);
    defun(sc, "algo-info",   algo_info);
    defun(sc, "make-videnc", make_videnc);
    defun(sc, "make-viddec", make_viddec);
    defun(sc, "viddec-iobufs", viddec_iobufs);
    defun(sc, "videnc-iobufs", videnc_iobufs);
    defun(sc, "videnc-process", videnc_process);

    defun(sc, "bufdesc-saw!", bufdesc_saw_bang);
    defun(sc, "bufdesc-dump", bufdesc_dump);

    load(sc, "test.scm");
}
void scheme_shutdown(void) { 
    scheme_deinit(sc); 
    printf("scheme_deinit() DONE\n");
}


// TEST try point
void debug() {
    printf("debug()\n");
}



int main(int argc, char **argv){
    CERuntime_init();
    scheme_boot();

    if (argc > 1) {
        if (!strcmp(argv[1], "debug")) {
            for (;;) {
                repl();
                debug();
            }
        }
        else {
            load(sc, argv[1]);
        }
    }
    else {
        repl();
    }
    scheme_shutdown();
    return 0;
}
