/*
 * MPEG4 file format support for AAC(M4A) player
 */
#include <t_services.h>
#include "ff.h"
#include "player.h"
#include <string.h>
#include <aacdec.h>
#include "mempool.h"

static AACFrameInfo *pAACInfo;

#define	HTYPE(a,b,c,d)	((a << 24)|(b << 16)|(c << 8)|d)

enum {
   TYPE_LEAF = 0,
   TYPE_NODE,
   TYPE_DATA,
   TYPE_BOX,
   TYPE_NONE,
} nodetype;

typedef struct {
  UW name;
  UW type;
  UW isize;
} t_node;

/* MPEG4 defines many node types, but we handle only few of them listed below */
const t_node node_table[] = {
 { HTYPE('h','d','l','r'), TYPE_LEAF, 0 },
 { HTYPE('i','l','s','t'), TYPE_BOX, 0 },
 { HTYPE('m','e','t','a'), TYPE_NODE, 4 },
 { HTYPE('t','r','a','k'), TYPE_BOX,  0 },
 { HTYPE('u','d','t','a'), TYPE_BOX,  0 },
 { HTYPE('m','d','i','a'), TYPE_BOX,  0 },
 { HTYPE('m','i','n','f'), TYPE_BOX,  0 },
 { HTYPE('s','t','b','l'), TYPE_BOX,  0 },
 { HTYPE('m','p','4','a'), TYPE_LEAF, 0 },
 { HTYPE('f','t','y','p'), TYPE_LEAF, 0 },
 { HTYPE('m','o','o','v'), TYPE_BOX,  0 },
 { HTYPE('m','d','a','t'), TYPE_LEAF, 0 },
 { HTYPE('m','v','h','d'), TYPE_LEAF, 0 },
 { HTYPE('f','r','e','e'), TYPE_LEAF, 0 },
 { HTYPE('s','t','s','d'), TYPE_NODE, 8 },
 { HTYPE('t','k','h','d'), TYPE_LEAF, 0 },
 { HTYPE('m','p','4','a'), TYPE_LEAF, 0 },
 { HTYPE('m','d','a','t'), TYPE_LEAF, 0 },
 { HTYPE('m','e','a','n'), TYPE_LEAF, 0 },
 { HTYPE(0xa9,'n','a','m'), TYPE_BOX, 0 },
 { HTYPE(0xa9,'A','R','T'), TYPE_BOX, 0 },
 { HTYPE(0xa9,'a','l','b'), TYPE_BOX, 0 },
 { HTYPE(0xa9,'g','e','n'), TYPE_BOX, 0 },
 { HTYPE(0xa9,'d','a','y'), TYPE_BOX, 0 },
 { HTYPE(0xa9,'t','o','o'), TYPE_BOX, 0 },
 { HTYPE('c','p','i','l'), TYPE_BOX, 0 },
 { HTYPE('t','m','p','o'), TYPE_BOX, 0 },
 { HTYPE('p','g','a','p'), TYPE_BOX, 0 },
 { HTYPE('d','i','s','k'), TYPE_BOX, 0 },
 { HTYPE('t','r','k','n'), TYPE_BOX, 0 },
 { HTYPE('-','-','-','-'), TYPE_BOX, 0 },
 { HTYPE('d','a','t','a'), TYPE_DATA, 0 },
 { 0, TYPE_NONE, 0 },
};

typedef struct {
    int hsize;
    union {
        char str[4];
        int  val;
    } htype;
} mp4header;

typedef struct {
    UB version;
    UB bits[3];
} fullinfo;

typedef struct {
    unsigned int creation_time;
    unsigned int modification_time;
    unsigned int timescale;
    unsigned int duration;
} mvhd0;

typedef struct {
    unsigned long long int creation_time;
    unsigned long long int  modification_time;
    unsigned int timescale;
    unsigned long long int  duration;
} mvhd1;

typedef union {
    mvhd0 v0;
    mvhd1 v1;
} mvheader;

typedef struct {
    UW reserved[2];
    UH channel;
    UH sample;
    UH pre_def, reserv2;
    UW sample_rate;
} __attribute__((packed)) AudioSampleEntry;

typedef struct {
    UW pre_def;
    UW handler_type;
    UW reserved[3];
} Handler;

typedef struct {
    UW type;
    UW dummy;
} Data;

inline UW ntohl(UW nval)
{
    int i;
    UW hval = 0;

    for (i = 0; i < 4; i++) {
        hval = (hval <<8) | (nval & 0xff);
        nval >>= 8;
    }
    return hval;
}

static int read_header(FIL *fp, mp4header *hp)
{
    int nb;
    int result;

    result = f_read(fp, (char *)hp, sizeof(mp4header), &nb);
    if (result != FR_OK) {
        syslog(LOG_NOTICE, "f_read failed!! (%d)", result);
        return -1;
    }
    hp->hsize = ntohl(hp->hsize);
    return (nb);
}

#define	NCOL	16
#define	NROW	10
#define	WBSIZE	(NCOL*NROW)

static char *wbuffer;

#ifdef VERBOSE
void debug_dump(FIL *fp)
{
    char *cp = wbuffer;
    int col, row, nb;

    f_read(fp, wbuffer, WBSIZE, &nb);
    for (row = 0; row < NROW; row++) {
        for (col = 0; col < NCOL; col++) {
            cons_printf(" %02x", *cp++ & 255);
        }
        cons_printf("\n");
        dly_tsk(2);
    }
}
#endif

static UW boxname;

void dump_data(Player_Status *pst, FIL *fp, long offset, int len, int level, mp4header *parent)
{
    Data hdata;
    int nb, val;
    int type, dlen;
    UB *cp, type_str[5];

    f_lseek(fp, offset);
#ifdef XDEBUG
cons_printf("offset = %x\n", offset);
#endif

    f_read(fp, &hdata, sizeof(hdata), &nb);
    type = ntohl(hdata.type);
    dlen = parent->hsize - 8 - sizeof(hdata);
    f_read(fp, wbuffer, dlen, &nb);
    wbuffer[dlen] = 0;

    switch (type) {
    case 0:
        cp = (unsigned char *)wbuffer;
        val = (cp[0] << 24) | (cp[1] << 16) | (cp[2] << 8) | cp[3];
#ifdef VERBOSE
        for (nb = 0; nb < level; nb++) {
            cons_printf("  ");
        }
        cons_printf("%08x: %d\n", offset, val);
#endif
        break;
    case 1:
        type_str[3] = boxname & 0xff;
        type_str[2] = (boxname >> 8) & 0xff;
        type_str[1] = (boxname >> 16) & 0xff;
        type_str[0] = (boxname >> 24) & 0xff;
        if (type_str[0] & 0x80)
            type_str[0] = '.';
        type_str[4] = '\0';
#ifdef VERBOSE
        for (nb = 0; nb < level; nb++) {
            cons_printf("  ");
        }
#endif
        switch (boxname) {
        case HTYPE(0xa9,'n','a','m'):
            strncpy(pst->song_name, wbuffer, MAX_SONG_NAME);
            break;
        case HTYPE(0xa9,'A','R','T'):
            strncpy(pst->performer_name, wbuffer, MAX_PERFORMER_NAME);
            break;
        case HTYPE(0xa9,'a','l','b'):
            strncpy(pst->album_name, wbuffer, MAX_ALBUM_NAME);
            break;
        }

#ifdef VERBOSE
        cons_printf("%08x (%s): %s\n", offset, type_str, wbuffer);
#endif
        break;
    }
#ifdef VERBOSE
    dly_tsk(2);
#endif
}

int dump_node(Player_Status *pst, FIL *fp, long offset, int len, int level,
mp4header *parent)
{
    mp4header *hp, hbuff, nument;
    fullinfo finfo;
    mvheader mvh;
    AudioSampleEntry audio_sample;
    int hlen, nodelen;
    UW nameval;
    int w, n, i, nb;
    const t_node *pnode;
    char *cp;
    FRESULT result;

    hp = &hbuff;
    while (len > 0) {
#ifdef DEBUG
        cons_printf("seek to %x (%x)\n", offset, len);
#endif
        result = f_lseek(fp, offset);
        if (result != FR_OK) {
            syslog(LOG_NOTICE, "f_lseek: result = %d", result);
            break;
        }
        if (read_header(fp, hp) == sizeof(mp4header)) {
            hlen = hp->hsize;
#ifdef VERBOSE
            for (w = 0; w < level; w++) {
                cons_printf("  ");
            }
            cons_printf("%08x(%08x) %c%c%c%c\n", offset, hlen,
                hp->htype.str[0], hp->htype.str[1],
                hp->htype.str[2], hp->htype.str[3]);
            dly_tsk(2);
#endif

            nameval = ntohl(hp->htype.val);
            for (pnode = node_table; pnode->name; pnode++) {
                if (pnode->name == nameval)
                    break;
            }
#ifdef DEBUG
cons_printf("type: %x, nameval: %x\n", pnode->type, pnode->name);
cons_printf("len: %d\n", len);
#endif
            switch (pnode->type) {
            case TYPE_BOX:
                if (nameval != HTYPE('-','-','-','-')) {
                    boxname = nameval;
                    (void) dump_node(pst, fp, offset + 8, hlen - 8, level + 1, hp);
                }
                break;
            case TYPE_NODE:
                nodelen = 8 + pnode->isize;
                (void) dump_node(pst, fp, offset + nodelen, hlen - nodelen, level + 1, hp);
                break;
            case TYPE_DATA:
                dump_data(pst, fp, offset + 8, hlen - 8, level + 1, hp);
                break;
            case TYPE_LEAF:
                switch (nameval) {
#ifdef VERBOSE
                case HTYPE('f','t','y','p'):
                    n = f_read(fp, wbuffer, hlen - 8, &nb);
                    cp = wbuffer;
                    for (i = 0; i < n; i++) {
                        if (i != 1) {
                            cons_printf("  %c%c%c%c\n", cp[0], cp[1], cp[2], cp[3]);
                        }
                        cp += 4;
                        dly_tsk(1);
                    }
                    break;
#endif
                case HTYPE('m','p','4','a'):
                    f_read(fp, &nument, sizeof(nument), &nb);	/* skip ref_id */
                    f_read(fp, &audio_sample, sizeof(audio_sample), &nb);
#ifdef VERBOSE
                    for (w = 0; w < level; w++) {
                        cons_printf("  ");
                    }
#endif
                    pAACInfo->nChans = ntohs(audio_sample.channel);
                    pAACInfo->sampRateOut = ntohs(audio_sample.sample);
                    pAACInfo->bitsPerSample = ntohl(audio_sample.sample_rate) >> 16;
#ifdef VERBOSE
                    cons_printf("Channel: %d, Samples: %d, Sampling rate: %d\n",
                        pAACInfo->nChans, pAACInfo->bitsPerSample,
                        pAACInfo->sampRateOut);
                    dly_tsk(1);
#endif
                    break;
                case HTYPE('m','d','a','t'):
#ifdef VERBOSE
                    debug_dump(fp);
#endif
                    return (offset + 8);
                    break;
                case HTYPE('m','v','h','d'):
                    f_read(fp, &finfo, sizeof(finfo), &nb);
cons_printf("version = %d\n", finfo.version);
                    if (finfo.version == 0) {
                        long long int ltime;

                        f_read(fp, &mvh, sizeof(mvhd0), &nb);
                        mvh.v0.timescale = ntohl(mvh.v0.timescale);
                        mvh.v0.duration = ntohl(mvh.v0.duration);
#ifdef DEBUG
printf("%x %x\n", mvh.v0.creation_time,
                mvh.v0.modification_time);
#endif
#ifdef VERBOSE
cons_printf("%d %d --> %d\n", mvh.v0.timescale, mvh.v0.duration,
                        mvh.v0.duration / mvh.v0.timescale);
#endif
                        ltime = (long long)mvh.v0.duration * 1000;
                        pst->title_length = ltime / mvh.v0.timescale;
                    } else {
                        f_read(fp, &mvh, sizeof(mvhd1), &nb);
                    }
                    break;
#ifdef VERBOSE
                case HTYPE('m','e','a','n'):
                case HTYPE('n','a','m','e'):
                    f_read(fp, wbuffer, hlen - 8, &nb);
                    wbuffer[hlen - 8] = 0;
                    for (w = 0; w < level; w++) {
                        cons_printf("  ");
                    }
                    cons_printf("  %s\n", wbuffer + 4);
                    dly_tsk(2);
                    break;
                case HTYPE('t','k','h','d'):
                    break;
#endif
                }
                break;
            }
            offset += hlen;
            len -= hlen;
        } else {
            break;
        }
    }
    return 0;
}

long mpeg4_decode(Player_Status *pst, AACFrameInfo *pinfo)
{
    FIL *fp = &pst->music_file;
    long offset;
    int len, i, n;
    char *cp;
    mp4header hbuff;
    mp4header *hp = &hbuff;

    wbuffer = jsp_malloc(WBSIZE);
    pAACInfo = pinfo;
    offset = dump_node(pst, fp, 0, fp->fsize, 0, NULL);
    f_lseek(fp, offset);
    return offset;
}
