#include "config.h"

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <pthread.h>
#include <quicktime/quicktime.h>
#include <quicktime/colormodels.h>
#include <quicktime/lqt.h>

#include "grab-ng.h"

/* ----------------------------------------------------------------------- */

struct qt_video_priv {
    char  fcc[5];
    int   yuvsign;
    int   libencode;
    int   cmodel;
};

struct qt_audio_priv {
    char  fcc[5];
    int   libencode;
};

struct qt_handle {
    /* libquicktime handle */
    quicktime_t *fh;

    /* format */
    struct ng_video_fmt video;
    struct ng_audio_fmt audio;

    /* misc */
    int lib_video;
    int lib_audio;
    int yuvsign;
    int audio_sample;
    unsigned char **rows;
    unsigned char *data;
};

/* ----------------------------------------------------------------------- */

static void*
qt_open(char *filename, char *dummy,
	struct ng_video_fmt *video, const void *priv_video, int fps,
	struct ng_audio_fmt *audio, const void *priv_audio)
{
    const struct qt_video_priv *pvideo = priv_video;
    const struct qt_audio_priv *paudio = priv_audio;
    struct qt_handle *h;

    if (NULL == (h = malloc(sizeof(*h))))
	return NULL;

    memset(h,0,sizeof(*h));
    h->video      = *video;
    h->audio      = *audio;
    if (h->video.fmtid != VIDEO_NONE) {
	h->lib_video  = pvideo->libencode;
	h->yuvsign    = pvideo->yuvsign;
    }
    if (h->audio.fmtid != AUDIO_NONE)
	h->lib_audio  = paudio->libencode;

    if (NULL == (h->fh = quicktime_open(filename,0,1))) {
	fprintf(stderr,"quicktime_open failed (%s)\n",filename);
	goto fail;
    }
    if (h->lib_video)
	if (NULL == (h->rows = malloc(h->video.height * sizeof(char*))))
	    goto fail;
    if (h->yuvsign)
	if (NULL == (h->data = malloc(h->video.height * h->video.width * 2)))
	    goto fail;

    if (h->audio.fmtid != AUDIO_NONE) {
	quicktime_set_audio(h->fh,
			    ng_afmt_to_channels[h->audio.fmtid],
			    h->audio.rate,
			    ng_afmt_to_bits[h->audio.fmtid],
			    (char*)paudio->fcc);
	h->audio_sample = ng_afmt_to_channels[h->audio.fmtid] *
	    ng_afmt_to_bits[h->audio.fmtid] / 8;
	if (h->lib_audio) {
	    if (!quicktime_supported_audio(h->fh, 0)) {
		fprintf(stderr,"libquicktime: audio codec not supported\n");
		goto fail;
	    }
	}
    }
    if (h->video.fmtid != VIDEO_NONE) {
	quicktime_set_video(h->fh,1,h->video.width,h->video.height,
			    (float)fps/1000,(char*)pvideo->fcc);
	if (h->lib_video) {
	    quicktime_set_cmodel(h->fh,pvideo->cmodel);
	    if (!quicktime_supported_video(h->fh, 0)) {
		fprintf(stderr,"libquicktime: video codec not supported\n");
		goto fail;
	    }
	}
    }
    quicktime_set_info(h->fh, "Dumme Bemerkungen gibt's hier umsonst.");
    return h;

 fail:
    if (h->rows)
	free(h->rows);
    if (h->data)
	free(h->data);
    free(h);
    return NULL;
}

static int
qt_video(void *handle, struct ng_video_buf *buf)
{
    struct qt_handle *h = handle;
    unsigned int *src,*dest;
    int rc,i,n;

    if (h->lib_video) {
	unsigned int row,len;
	char *line;

	/* QuickTime library expects an array of pointers to image rows (RGB) */
	len = h->video.width * 3;
	for (row = 0, line = buf->data; row < h->video.height; row++, line += len)
	    h->rows[row] = line;
	rc = quicktime_encode_video(h->fh, h->rows, 0);

    } else if (h->yuvsign) {
	dest = (unsigned int *)h->data;
	src  = (unsigned int *)buf->data;
	n    = buf->size / 4;
	/* U V values are signed but Y R G B values are unsigned. */
	for (i = 0; i < n; i++) {
#if BYTE_ORDER == BIG_ENDIAN
	    *(dest++) = *(src++) ^ 0x00800080;
#else
	    *(dest++) = *(src++) ^ 0x80008000;
#endif
	}
	rc = quicktime_write_frame(h->fh, h->data, buf->size, 0);

    } else {
	rc = quicktime_write_frame(h->fh, buf->data, buf->size, 0);
    }
    return rc;
}

static int
qt_audio(void *handle, struct ng_audio_buf *buf)
{
    struct qt_handle *h = handle;
    int16_t *ch[2];
    
    if (h->lib_audio) {
	/* FIXME: works for one channel (mono) only */
	ch[0] = (int16_t*)buf->data;
	return quicktime_encode_audio(h->fh, ch, NULL,
				      buf->size / h->audio_sample);
    } else {
	return quicktime_write_audio(h->fh, buf->data,
				     buf->size / h->audio_sample, 0);
    }
}

static int
qt_close(void *handle)
{
    struct qt_handle *h = handle;

    quicktime_close(h->fh);
    if (h->rows)
	free(h->rows);
    if (h->data)
	free(h->data);
    free(h);
    return 0;
}

/* ----------------------------------------------------------------------- */

static int cmodels[] = {
    [BC_BGR888]  = VIDEO_BGR24,
    [BC_RGB888]  = VIDEO_RGB24,
    [BC_YUV422]  = VIDEO_YUYV,
    [BC_YUV422P] = VIDEO_YUV422P,
    [BC_YUV420P] = VIDEO_YUV420P,
};

static struct qt_video_priv qt_raw = {
    fcc:       QUICKTIME_RAW,
    libencode: 0,
};
static struct qt_video_priv qt_yuv2 = {
    fcc:       QUICKTIME_YUV2,
    yuvsign:   1,
    libencode: 0,
};
static struct qt_video_priv qt_yv12 = {
    fcc:       QUICKTIME_YUV420,
    libencode: 0,
};
static struct qt_video_priv qt_jpeg = {
    fcc:       QUICKTIME_JPEG,
    libencode: 0,
};

static const struct ng_format_list qt_vformats[] = {
    {
	name:  "raw",
	ext:   "mov",
	fmtid: VIDEO_RGB24,
	priv:  &qt_raw,
    },{
	name:  "yuv2",
	ext:   "mov",
	fmtid: VIDEO_YUYV,
	priv:  &qt_yuv2,
    },{
	name:  "yv12",
	ext:   "mov",
	fmtid: VIDEO_YUV420P,
	priv:  &qt_yv12,
    },{
	name:  "jpeg",
	ext:   "mov",
	fmtid: VIDEO_JPEG,
	priv:  &qt_jpeg,
    },{
	/* EOF */
    }
};

static struct qt_audio_priv qt_mono8 = {
    fcc:        QUICKTIME_RAW,
    libencode:  0,
};
static struct qt_audio_priv qt_mono16 = {
    fcc:	QUICKTIME_TWOS,
    libencode:	0,
};
static struct qt_audio_priv qt_stereo = {
    fcc:	QUICKTIME_TWOS,
    libencode:	0,
};
static const struct ng_format_list qt_aformats[] = {
    {
	name:  "mono8",
	ext:   "mov",
	fmtid: AUDIO_U8_MONO,
	priv:  &qt_mono8,
    },{
        name:  "mono16",
	ext:   "mov",
	fmtid: AUDIO_S16_BE_MONO,
	priv:  &qt_mono16,
    },{
        name:  "stereo",
	ext:   "mov",
	fmtid: AUDIO_S16_BE_STEREO,
	priv:  &qt_stereo,
    },{
	/* EOF */
    }
};

struct ng_writer qt_writer = {
    name:      "qt",
    desc:      "Apple QuickTime format",
    combined:  1,
    video:     qt_vformats,
    audio:     qt_aformats,
    wr_open:   qt_open,
    wr_video:  qt_video,
    wr_audio:  qt_audio,
    wr_close:  qt_close,
};

/* ----------------------------------------------------------------------- */

#if 0
/* debug only */
static void dump_codecs(void)
{
    lqt_codec_info_t **info;
    int i,j;

    info = lqt_query_registry(1, 1, 1, 1);
    for (i = 0; info[i] != NULL; i++) {
	fprintf(stderr,"lqt: %s codec: %s [%s]\n",
		info[i]->type == LQT_CODEC_AUDIO ? "audio" : "video",
		info[i]->name,info[i]->long_name);
	fprintf(stderr,"   encode: %s\n",
		info[i]->direction == LQT_DIRECTION_DECODE ? "no" : "yes");
	fprintf(stderr,"   decode: %s\n",
		info[i]->direction == LQT_DIRECTION_ENCODE ? "no" : "yes");
	for (j = 0; j < info[i]->num_fourccs; j++)
	    fprintf(stderr,"   fcc   : %s\n",info[i]->fourccs[j]);
	for (j = 0; j < info[i]->num_encoding_colormodels; j++)
	    fprintf(stderr,"   cmodel: %s\n",
		    lqt_get_colormodel_string(info[i]->encoding_colormodels[j]));
	fprintf(stderr,"\n");
    }
    lqt_destroy_codec_info(info);
}
#endif

static struct ng_format_list*
qt_list_add(struct ng_format_list* list,
	 char *name, char *desc, char *ext, int fmtid, void *priv)
{
    int n;

    for (n = 0; list[n].name != NULL; n++)
	/* nothing */;
    list = realloc(list,sizeof(struct ng_format_list)*(n+2));
    memset(list+n,0,sizeof(struct ng_format_list)*2);
    list[n].name  = strdup(name);
    list[n].desc  = strdup(desc);
    list[n].ext   = strdup(ext);
    list[n].fmtid = fmtid;
    list[n].priv  = priv;
    return list;
}

static struct ng_format_list* video_list(void)
{
    static int debug = 0;
    lqt_codec_info_t **info;
    struct ng_format_list *video;
    int i,j,k,skip,fmtid;
    unsigned int cmodel;
    struct qt_video_priv *vp;

    /* handle video encoders */
    video = malloc(sizeof(qt_vformats));
    memcpy(video,qt_vformats,sizeof(qt_vformats));
    info = lqt_query_registry(0, 1, 1, 0);
    for (i = 0; info[i] != NULL; i++) {
	if (debug) {
	    fprintf(stderr,"\nlqt: %s codec: %s [%s]\n",
		    info[i]->type == LQT_CODEC_AUDIO ? "audio" : "video",
		    info[i]->name,info[i]->long_name);
	    for (j = 0; j < info[i]->num_fourccs; j++)
		fprintf(stderr,"   fcc   : %s\n",info[i]->fourccs[j]);
	    for (j = 0; j < info[i]->num_encoding_colormodels; j++)
		fprintf(stderr,"   cmodel: %d [%s]\n",
			info[i]->encoding_colormodels[j],
			lqt_get_colormodel_string(info[i]->encoding_colormodels[j]));
	}

	/* sanity checks */
	if (0 == info[i]->num_fourccs) {
	    if (debug)
		fprintf(stderr,"   skipping, no fourcc\n");
	    continue;
	}
	
	/* avoid dup entries */
	skip = 0;
	for (j = 0; video[j].name != NULL; j++) {
	    const struct qt_video_priv *p = video[j].priv;
	    for (k = 0; k < info[i]->num_fourccs; k++)
		if (0 == strcmp(p->fcc,info[i]->fourccs[k]))
		    skip = 1;
	}
	if (skip) {
	    if (debug)
		fprintf(stderr,"   skipping, fourcc already in list\n");
	    continue;
	}

	/* pick colormodel */
	fmtid  = VIDEO_NONE;
	cmodel = 0;
	for (j = 0; j < info[i]->num_encoding_colormodels; j++) {
	    cmodel = info[i]->encoding_colormodels[j];
	    if (cmodel>= sizeof(cmodels)/sizeof(int))
		continue;
	    if (!cmodels[cmodel])
		continue;
	    fmtid = cmodels[cmodel];
	    break;
	}
	if (VIDEO_NONE == fmtid) {
	    if (debug)
		fprintf(stderr,"   skipping, can't handle color model\n");
	    continue;
	}

	/* all fine */
	if (debug)
	    fprintf(stderr,"   ok, using fmtid %d [%s]\n",
		    fmtid,ng_vfmt_to_desc[fmtid]);
	vp = malloc(sizeof(*vp));
	memset(vp,0,sizeof(*vp));
	strcpy(vp->fcc,info[i]->fourccs[0]);
	vp->libencode = 1;
	vp->cmodel    = cmodel;
	video = qt_list_add(video,vp->fcc,info[i]->long_name,"mov",fmtid,vp);
    }
    lqt_destroy_codec_info(info);
    return video;
}

static struct ng_format_list* audio_list(void)
{
    static int debug = 0;
    lqt_codec_info_t **info;
    struct ng_format_list *audio;
    int i,j;
    struct qt_audio_priv *ap;

    /* handle video encoders */
    audio = malloc(sizeof(qt_aformats));
    memcpy(audio,qt_aformats,sizeof(qt_aformats));
    info = lqt_query_registry(1, 0, 1, 0);
    for (i = 0; info[i] != NULL; i++) {
	if (debug) {
	    fprintf(stderr,"\nlqt: %s codec: %s [%s]\n",
		    info[i]->type == LQT_CODEC_AUDIO ? "audio" : "video",
		    info[i]->name,info[i]->long_name);
	    for (j = 0; j < info[i]->num_fourccs; j++)
		fprintf(stderr,"   fcc   : %s\n",info[i]->fourccs[j]);
	}

	/* sanity checks */
	if (0 == info[i]->num_fourccs) {
	    if (debug)
		fprintf(stderr,"   skipping, no fourcc\n");
	    continue;
	}

	/* skip uncompressed formats */
	if (0 == strcmp(info[i]->fourccs[0],QUICKTIME_RAW)  ||
	    0 == strcmp(info[i]->fourccs[0],QUICKTIME_ULAW) ||
	    0 == strcmp(info[i]->fourccs[0],QUICKTIME_IMA4) || /* ??? */
	    0 == strcmp(info[i]->fourccs[0],QUICKTIME_TWOS)) {
	    if (debug)
		fprintf(stderr,"   skipping, uncompressed\n");
	    continue;
	}

	/* all fine */
	if (debug)
	    fprintf(stderr,"   ok\n");
	ap = malloc(sizeof(*ap));
	memset(ap,0,sizeof(*ap));
	strcpy(ap->fcc,info[i]->fourccs[0]);
	ap->libencode = 1;
	audio = qt_list_add(audio,ap->fcc,info[i]->long_name,"mov",
			    AUDIO_S16_NATIVE_MONO,ap);
    }
    lqt_destroy_codec_info(info);
    return audio;
}

extern void ng_plugin_init(void);
void ng_plugin_init(void)
{
    qt_writer.video = video_list();
    qt_writer.audio = audio_list();
    ng_writer_register(NG_PLUGIN_MAGIC,__FILE__,&qt_writer);
}
