#include "hls-fmp4.h"
#include "hls-param.h"
#include "mov-format.h"
#include "fmp4-writer.h"
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <errno.h>

#define N_SEGMENT (1 * 1024 * 1024)
#define N_FILESIZE (100 * 1024 * 1024) // 100M

#define VMAX(a, b) ((a) > (b) ? (a) : (b))

struct hls_fmp4_t
{
	fmp4_writer_t* mp4;
	uint8_t* ptr;
	size_t bytes;
	size_t capacity;
	size_t offset;
	size_t maxsize; // max bytes per mp4 file

	int64_t duration;	// user setting segment duration
	int64_t dts_last;	// last packet dts
	int64_t dts;		// segment first dts
	int64_t pts;		// segment first pts

	int video_track;
	int audio_only_flag;// don't have video stream in segment

	hls_fmp4_handler handler;
	void* param;
};

static int mov_buffer_read(void* param, void* data, uint64_t bytes)
{
	struct hls_fmp4_t* fmp4;
	fmp4 = (struct hls_fmp4_t*)param;
	if (fmp4->offset + bytes > fmp4->bytes)
		return -E2BIG;
	memcpy(data, fmp4->ptr + fmp4->offset, (size_t)bytes);
	return 0;
}

static int mov_buffer_write(void* param, const void* data, uint64_t bytes)
{
	void* ptr;
	size_t capacity;
	struct hls_fmp4_t* fmp4;
	fmp4 = (struct hls_fmp4_t*)param;
	if (fmp4->offset + bytes > fmp4->maxsize)
		return -E2BIG;

	if (fmp4->offset + (size_t)bytes > fmp4->capacity)
	{
		capacity = fmp4->offset + (size_t)bytes + N_SEGMENT;
		capacity = capacity > fmp4->maxsize ? fmp4->maxsize : capacity;
		ptr = realloc(fmp4->ptr, capacity);
		if (NULL == ptr)
			return -ENOMEM;
		fmp4->ptr = ptr;
		fmp4->capacity = capacity;
	}

	memcpy(fmp4->ptr + fmp4->offset, data, (size_t)bytes);
	fmp4->offset += (size_t)bytes;
	if(fmp4->offset > fmp4->bytes)
		fmp4->bytes = fmp4->offset;
	return 0;
}

static int mov_buffer_seek(void* param, int64_t offset)
{
	struct hls_fmp4_t* fmp4;
	fmp4 = (struct hls_fmp4_t*)param;
    if ((offset >= 0 ? offset : -offset) >= fmp4->maxsize)
		return -E2BIG;
    fmp4->offset = (size_t)(offset >= 0 ? offset : fmp4->maxsize+offset);
	return 0;
}

static int64_t mov_buffer_tell(void* param)
{
	return (int64_t)((struct hls_fmp4_t*)param)->offset;
}

static struct mov_buffer_t s_io = {
	mov_buffer_read,
	mov_buffer_write,
	mov_buffer_seek,
	mov_buffer_tell,
};

struct hls_fmp4_t* hls_fmp4_create(int64_t duration, hls_fmp4_handler handler, void* param)
{
	int flags;
	struct hls_fmp4_t* hls;
	hls = (struct hls_fmp4_t*)calloc(1, sizeof(*hls));
	if (NULL == hls)
		return NULL;

	hls->video_track = -1;
	hls->maxsize = N_FILESIZE;
	hls->dts = hls->pts = PTS_NO_VALUE;
	hls->dts_last = PTS_NO_VALUE;
	hls->duration = duration;
	hls->handler = handler;
	hls->param = param;

	flags = 0;
	//flags |= MOV_FLAG_FASTSTART;
	flags |= MOV_FLAG_SEGMENT;
	hls->mp4 = fmp4_writer_create(&s_io, hls, flags);
	if (NULL == hls->mp4)
	{
		free(hls);
		return NULL;
	}

	return hls;
}

void hls_fmp4_destroy(struct hls_fmp4_t* hls)
{
	if (hls->mp4)
	{
		fmp4_writer_destroy(hls->mp4);
		hls->mp4 = NULL;
	}

	if (hls->ptr)
	{
		free(hls->ptr);
		hls->ptr = NULL;
	}

	free(hls);
}

int hls_fmp4_add_audio(hls_fmp4_t* hls, uint8_t object, int channel_count, int bits_per_sample, int sample_rate, const void* extra_data, size_t extra_data_size)
{
	return fmp4_writer_add_audio(hls->mp4, object, channel_count, bits_per_sample, sample_rate, extra_data, extra_data_size);
}

int hls_fmp4_add_video(hls_fmp4_t* hls, uint8_t object, int width, int height, const void* extra_data, size_t extra_data_size)
{
	hls->video_track = fmp4_writer_add_video(hls->mp4, object, width, height, extra_data, extra_data_size);
	return hls->video_track;
}

int hls_fmp4_input(struct hls_fmp4_t* hls, int track, const void* data, size_t bytes, int64_t pts, int64_t dts, int flags)
{
	int r, segment;
	int force_new_segment;
	int64_t duration;

	assert(dts < hls->dts_last + hls->duration || PTS_NO_VALUE == hls->dts_last);

	// PTS/DTS rewind
	force_new_segment = 0;
	if (dts + hls->duration < hls->dts_last || NULL == data || 0 == bytes)
		force_new_segment = 1;

	if ((MOV_AV_FLAG_KEYFREAME & flags) && (dts - hls->dts >= hls->duration || 0 == hls->duration))
	{
		segment = 1;
	}
	else if (hls->audio_only_flag && dts - hls->dts >= hls->duration)
	{
		// audio only file
		segment = 1;
	}
	else
	{
		segment = 0;
	}

	if (PTS_NO_VALUE == hls->dts_last || segment || force_new_segment)
	{
		if (PTS_NO_VALUE != hls->dts_last)
		{
			// save and create new segment
			r = fmp4_writer_save_segment(hls->mp4);
			if (0 == r)
			{
				duration = ((force_new_segment || dts > hls->dts_last + 100) ? hls->dts_last : dts) - hls->dts;
				r = hls->handler(hls->param, hls->ptr, hls->bytes, hls->pts, hls->dts, duration);
			}
			if (0 != r) return r;
		}

		hls->pts = pts;
		hls->dts = dts;
		hls->audio_only_flag = 1;
		hls->offset = 0;
		hls->bytes = 0;
	}

	if (NULL == data || 0 == bytes)
		return 0;

	if (hls->audio_only_flag && track == hls->video_track)
		hls->audio_only_flag = 0; // clear audio only flag

	hls->dts_last = dts;
	return fmp4_writer_write(hls->mp4, track, data, bytes, pts, dts, flags);
}

int hls_fmp4_init_segment(hls_fmp4_t* hls, void* data, size_t bytes)
{
	int r;
	uint8_t* ptr;
	size_t len;
	size_t capacity;
	size_t offset;
	size_t maxsize;
	
	// save
	ptr = hls->ptr;
	len = hls->bytes;
	offset = hls->offset;
	capacity = hls->capacity;
	maxsize = hls->maxsize;

	hls->ptr = (uint8_t*)data;
	hls->bytes = 0;
	hls->offset = 0;
	hls->capacity = bytes;
	hls->maxsize = bytes;

	r = fmp4_writer_init_segment(hls->mp4);
	r = 0 ==  r ? (int)hls->bytes : -1;

	// restore
	hls->ptr = ptr;
	hls->bytes = len;
	hls->offset = offset;
	hls->capacity = capacity;
	hls->maxsize = maxsize;
	return r;
}
