#include<stdlib.h>
#include<libavcodec/avcodec.h>
#include<libswscale/swscale.h>
#include<libavutil/imgutils.h>
#include<ogg/ogg.h>
#include<vorbis/codec.h>

struct veepie {
	uint8_t *rgba;
	uint16_t w;
	uint16_t h;
	
	uint16_t wOld, hOld;
	
	const AVCodec *cdc;
	AVCodecContext *ctx;
	AVFrame *frame1, *frame2;
	AVPacket pkt;
	
	SwsContext *sws;
};

struct veepie *alloc_codec_vpx(int vp9) {
	struct veepie *v = calloc(1, sizeof(*v));
	v->cdc = avcodec_find_decoder_by_name(vp9 ? "vp9" : "vp8");
	if(!v->cdc) return (void*) (uintptr_t) 1;
	v->ctx = avcodec_alloc_context3(v->cdc);
	if(!v->ctx) return (void*) (uintptr_t) 2;
	if(avcodec_open2(v->ctx, v->cdc, 0) < 0) return (void*) (uintptr_t) 3;
	v->frame1 = av_frame_alloc();
	v->frame2 = av_frame_alloc();
	return v;
}

int codec_vpx_push_packet(struct veepie *v, int kf, uint8_t *data, size_t len) {//, uint64_t pts) {
	av_init_packet(&v->pkt);
	v->pkt.data = data;
	v->pkt.size = len;
	v->pkt.pts = AV_NOPTS_VALUE;
	v->pkt.dts = AV_NOPTS_VALUE;
	if(kf) v->pkt.flags |= AV_PKT_FLAG_KEY;
	avcodec_send_packet(v->ctx, &v->pkt);
	
	av_packet_unref(&v->pkt);
	
	int retframe = avcodec_receive_frame(v->ctx, v->frame1);
	if(retframe) {
		return 0;
	}
	
	v->w = v->frame1->width;
	v->h = v->frame1->height;
	
	if(v->w != v->wOld || v->h != v->hOld) {
		v->sws = sws_getContext(v->w, v->h, v->frame1->format, v->w, v->h, AV_PIX_FMT_RGBA, SWS_FAST_BILINEAR, NULL, NULL, NULL);
		av_image_fill_arrays(v->frame2->data, v->frame2->linesize, av_malloc(av_image_get_buffer_size(AV_PIX_FMT_RGBA, v->w, v->h, 4)), AV_PIX_FMT_RGBA, v->w, v->h, 4);
		
		v->wOld = v->w;
		v->hOld = v->h;
	}
	
	sws_scale(v->sws, v->frame1->data, v->frame1->linesize, 0, v->h, v->frame2->data, v->frame2->linesize);
	
	v->rgba = v->frame2->data[0];
	
	return 1;
}

struct vobie {
	float **sampleBuffer;
	uint32_t sampleCount;
	
	vorbis_info vi;
	vorbis_comment vc;
	ogg_packet op;
	
	vorbis_dsp_state dsp;
	vorbis_block vb;
};

struct vobie *alloc_codec_vorbis(uint8_t *private, size_t privateLen) {
	uint8_t numPacketsMinusOne = private[0];
	
	if(numPacketsMinusOne != 2) return NULL;
	
	size_t i = 1;
	
	size_t len0 = 0;
	while(private[i] == 0xFF) {
		len0 += 0xFF;
		i++;
	}
	len0 += private[i++];
	
	size_t len1 = 0;
	while(private[i] == 0xFF) {
		len1 += 0xFF;
		i++;
	}
	len1 += private[i++];
	
	size_t len2 = privateLen - i - len0 - len1;
	
	struct vobie *v = calloc(1, sizeof(*v));
	
	vorbis_info_init(&v->vi);
	vorbis_comment_init(&v->vc);
	
	v->op.packet = private + i;
	v->op.bytes = len0;
	v->op.b_o_s = 1;
	v->op.e_o_s = 0;
	v->op.granulepos = 0;
	v->op.packetno = 0;
	
	if(vorbis_synthesis_headerin(&v->vi, &v->vc, &v->op)) {
		return (void*) (uintptr_t) 1;
	}
	
	i += len0;
	
	v->op.packet = private + i;
	v->op.bytes = len1;
	v->op.b_o_s = 0;
	v->op.packetno++;
	
	if(vorbis_synthesis_headerin(&v->vi, &v->vc, &v->op)) {
		return (void*) (uintptr_t) 2;
	}
	
	i += len1;
	
	v->op.packet = private + i;
	v->op.bytes = len2;
	v->op.packetno++;
	
	if(vorbis_synthesis_headerin(&v->vi, &v->vc, &v->op)) {
		return (void*) (uintptr_t) 3;
	}
	
	if(vorbis_synthesis_init(&v->dsp, &v->vi)) {
		return (void*) (uintptr_t) 4;
	}
	
	if(vorbis_block_init(&v->dsp, &v->vb)) {
		return (void*) (uintptr_t) 5;
	}
	
	return v;
}

int codec_vorbis_push_packet(struct vobie *v, uint8_t *pkt, size_t len) {
	v->op.packet = pkt;
	v->op.bytes = len;
	v->op.packetno++;
	
	if(vorbis_synthesis(&v->vb, &v->op)) {
		return -1;
	}
	
	if(vorbis_synthesis_blockin(&v->dsp, &v->vb)) {
		return -2;
	}
	
	v->sampleCount = vorbis_synthesis_pcmout(&v->dsp, &v->sampleBuffer);
	
	v->op.granulepos += v->sampleCount;
	
	vorbis_synthesis_read(&v->dsp, v->sampleCount);
	
	return v->sampleCount;
}