#include "cf_internal.h"

#define CACHE_SIZE 1024

#define INDEX(i)	((i) % CACHE_SIZE)

static frame_cache_t* open_real_video_cache(cf_session_t* s)
{
	frame_cache_t* cache = calloc(1, sizeof(frame_cache_t));
	cache->wait_timer = s->proc->rtt + 2 * s->proc->rtt_val;
	cache->state = buffer_waiting;

	cache->frame_timer = 100;
	cache->size = CACHE_SIZE;
	cache->frames = calloc(cache->size, sizeof(cf_frame_t));
	return cache;
}

static inline void real_video_clean_frame(cf_session_t* session, frame_cache_t* c, cf_frame_t* frame)
{
	int i;
	if (frame->seg_number == 0)
		return;

	for (i = 0; i < frame->seg_number; ++i){
		if (frame->segments[i] != NULL){
			slab_free(session->mem, frame->segments[i]);
			frame->segments[i] = NULL;
		}
	}

	free(frame->segments);

	log_debug("buffer clean frame, frame id = %u, ts = %llu\n", frame->fid, frame->ts);

	frame->ts = 0;
	frame->frame_type = 0;
	frame->seg_number = 0;

	c->min_fid = frame->fid;
}

static void close_real_video_cache(cf_session_t* s, frame_cache_t* cache)
{
	uint32_t i;
	for (i = 0; i < cache->size; ++i)
		real_video_clean_frame(s, cache, &cache->frames[i]);

	free(cache->frames);
	free(cache);
}

static void reset_real_video_cache(cf_session_t* s, frame_cache_t* cache)
{
	uint32_t i;

	for (i = 0; i < cache->size; ++i)
		real_video_clean_frame(s, cache, &cache->frames[i]);

	cache->min_fid = 0;
	cache->max_fid = 0;
	cache->play_ts = 0;
	cache->frame_ts = 0;
	cache->max_ts = 100;
	cache->frame_timer = 100;

	cache->state = buffer_waiting;
	cache->wait_timer = SU_MAX(100, s->proc->rtt + 2 * s->proc->rtt_val);
	cache->loss_flag = 0;
}

static void real_video_evict_frame(cf_session_t* s, frame_cache_t* c, uint32_t fid)
{
	uint32_t pos, i;

	for (pos = c->max_fid + 1; pos <= fid; pos++)
		real_video_clean_frame(s, c, &c->frames[INDEX(pos)]);

	if (fid < c->min_fid + c->size)
		return;

	for (pos = c->min_fid + 1; pos < c->max_fid; ++pos){
		if (c->frames[INDEX(pos)].frame_type == 1)
			break;
	}

	for (i = c->min_fid + 1; i < pos; ++i)
		real_video_clean_frame(s, c, &c->frames[INDEX(i)]);
}

static int real_video_cache_put(cf_session_t* s, frame_cache_t* c, cf_seg_video_t* seg)
{
	cf_frame_t* frame;
	int ret = -1;
	if (seg->index >= seg->total){
		assert(0);
		return ret;
	}
	else if (seg->fid <= c->min_fid)
		return ret;

	if (seg->fid > c->max_fid){
		if (c->max_fid > 0)
			real_video_evict_frame(s, c, seg->fid);
		else if (c->min_fid == 0)
			c->min_fid = seg->fid - 1;

		if (c->max_fid >= 0 && c->max_fid < seg->fid && c->max_ts < seg->ts){
			c->frame_timer = (seg->ts - c->max_ts) / (seg->fid - c->max_fid);
			if (c->frame_timer < 20)
				c->frame_timer = 20;
			else if (c->frame_timer > 200)
				c->frame_timer = 200;
		}
		c->max_ts = seg->ts;
		c->max_fid = seg->fid;
	}

	log_debug("buffer put video frame, frame = %u, seq = %u, ts = %u\n", seg->fid, seg->seq, seg->ts);

	frame = &(c->frames[INDEX(seg->fid)]);
	frame->fid = seg->fid;
	frame->frame_type = seg->ftype;
	frame->ts = seg->ts;

	if (frame->seg_number == 0){
		frame->seg_number = seg->total;
		frame->segments = calloc(frame->seg_number, sizeof(seg));
		frame->segments[seg->index] = seg;

		ret = 0;
	}
	else{
		if (frame->segments[seg->index] == NULL){
			frame->segments[seg->index] = seg;
			ret = 0;
		}
	}

	return ret;
}

static void real_video_cache_check_playing(cf_session_t* s, frame_cache_t* c)
{
	uint64_t max_ts, min_ts;

	if (c->max_fid > c->min_fid){
		max_ts = c->frames[INDEX(c->max_fid)].ts;
		min_ts = c->frames[INDEX(c->min_fid + 1)].ts;

		if (min_ts > 0 && max_ts > min_ts + (c->wait_timer * 5 / 4) && c->max_fid >= c->min_fid + 1){
			c->state = buffer_playing;

			c->play_ts = GET_SYS_MS();
			c->frame_ts = max_ts - (c->wait_timer * 5 / 4);
		}
	}
}

static inline void real_video_cache_check_waiting(cf_session_t* s, frame_cache_t* c)
{
	if (c->max_fid <= c->min_fid){
		c->state = buffer_waiting;
		log_debug("buffer waiting ...........\n");
	}
}

static inline int real_video_cache_check_frame_full(cf_session_t* s, cf_frame_t* frame)
{
	int i;
	for (i = 0; i < frame->seg_number; ++i)
		if (frame->segments[i] == NULL)
			return -1;

	return 0;
}

static inline void real_video_cache_sync_timestamp(cf_session_t* s, frame_cache_t* c)
{
	uint64_t cur_ts = GET_SYS_MS();

	if (cur_ts > c->play_ts){
		c->frame_ts = (uint32_t)(cur_ts - c->play_ts) + c->frame_ts;
		c->play_ts = cur_ts;
	}
}

static int real_video_cache_get(cf_session_t* s, frame_cache_t* c, uint8_t* data, size_t* sizep)
{
	uint32_t pos;
	size_t size;
	int ret, i;
	cf_frame_t* frame;
	uint64_t max_ts;

	if (c->state == buffer_waiting)
		real_video_cache_check_playing(s, c);
	else
		real_video_cache_check_waiting(s, c);

	if (c->state != buffer_playing){
		size = 0;
		ret = -1;
		goto err;
	}

	real_video_cache_sync_timestamp(s, c);

	max_ts = c->frames[INDEX(c->max_fid)].ts;

	pos = INDEX(c->min_fid + 1);
	frame = &c->frames[pos];
	if (frame->ts <= c->frame_ts && real_video_cache_check_frame_full(s, frame) == 0){
		size = 0;
		for (i = 0; i < frame->seg_number; ++i){
			memcpy(data + size, frame->segments[i]->data, frame->segments[i]->data_size);
			size += frame->segments[i]->data_size;
		}

		if (frame->ts + c->wait_timer * 5 / 4 >= max_ts || c->min_fid + 1 == c->max_fid)
			c->frame_ts = frame->ts;
		else
			c->frame_ts = max_ts - c->wait_timer * 5 / 4;

		real_video_clean_frame(s, c, frame);
		ret = 0;
	}
	else{
		size = 0;
		ret = -1;
	}

err:
	*sizep = size;
	return ret;
}

static uint32_t real_video_cache_get_min_seq(cf_session_t* s, frame_cache_t* c)
{
	int i;
	cf_frame_t* frame;
	cf_seg_video_t* seg;

	frame = &c->frames[INDEX(c->min_fid)];
	for (i = 0; i < frame->seg_number; ++i){
		seg = frame->segments[i];
		if (seg != NULL)
			return seg->seq - seg->index - 1;
	}

	return 0;
}

//////////////////////////////////////////////////////////////////////////////////////////////////

typedef struct
{
	uint64_t	ts;
	int			count;
}wb_loss_t;

static inline void loss_free(skiplist_item_t key, skiplist_item_t val)
{
	free(val.ptr);
}

cf_video_real_buffer_t* create_video_real_buffer(cf_session_t* s)
{
	cf_video_real_buffer_t* r = calloc(1, sizeof(cf_video_real_buffer_t));

	r->loss = skiplist_create(id_compare, loss_free);
	r->cache = open_real_video_cache(s);
	r->cache_ts = GET_SYS_MS();

	return r;
}

void destroy_video_real_buffer(cf_session_t* s, cf_video_real_buffer_t* r)
{
	if (r == NULL)
		return;

	assert(r->cache && r->loss);
	skiplist_destroy(r->loss);
	close_real_video_cache(s, r->cache);

	free(r);
}

void reset_video_real_buffer(cf_session_t* s, cf_video_real_buffer_t* r)
{
	reset_real_video_cache(s, r->cache);
	skiplist_clear(r->loss);

	r->base_uid = 0;
	r->base_seq = 0;
	r->actived = 0;
	r->max_seq = 0;
	r->ack_ts = GET_SYS_MS();
	r->active_ts = r->ack_ts;
	r->loss_count = 0;
}

int active_video_real_buffer(cf_session_t* s, cf_video_real_buffer_t* r, uint32_t start_seq, uint16_t rate, uint32_t base_uid)
{
	if (r->actived == 1)
		return -1;

	if (start_seq > 0){
		r->base_seq = start_seq;
		r->max_seq = r->base_seq;
	}

	r->actived = 1;
	r->base_seq = start_seq;
	r->base_uid = base_uid;
	if (rate > 0)
		r->cache->frame_timer = SU_MAX(20, 1000 / rate);

	r->active_ts = GET_SYS_MS();
	return 0;
}

static void video_real_buffer_update_loss(cf_session_t* s, cf_video_real_buffer_t* r, uint32_t seq)
{
	uint32_t i;
	skiplist_item_t key, val;
	skiplist_iter_t* iter;

	key.u32 = seq;
	skiplist_remove(r->loss, key);

	for (i = r->max_seq + 1; i < seq; ++i){
		key.u32 = i;
		iter = skiplist_search(r->loss, key);
		if (iter == NULL){
			wb_loss_t* l = calloc(1, sizeof(wb_loss_t));
			l->ts = GET_SYS_MS() - s->proc->rtt;
			l->count = 0;
			val.ptr = l;

			skiplist_insert(r->loss, key, val);
		}
	}
}

static inline void video_send_segment(cf_session_t* s, cf_segment_ack_t* ack)
{
	cf_header_t header;

	CF_INIT_HEADER(header, SEG_ACK, s->rid, s->uid);

	cf_encode_msg(&s->sstrm, &header, ack);
	processor_send(s, s->proc, &s->sstrm, &s->proc->server_node);
}

static void video_real_ack(cf_session_t* s, cf_video_real_buffer_t* r, int hb)
{
	uint64_t cur_ts;
	cf_segment_ack_t ack;
	skiplist_iter_t* iter;
	skiplist_item_t key;
	wb_loss_t* l;
	uint32_t min_seq, delay;
	int max_count = 0;

	cur_ts = GET_SYS_MS();
	if (r->ack_ts + 10 < cur_ts){

		min_seq = real_video_cache_get_min_seq(s, r->cache);
		if (min_seq > r->base_seq){
			for (key.u32 = r->base_seq + 1; key.u32 <= min_seq; ++key.u32)
				skiplist_remove(r->loss, key);
			r->base_seq = min_seq;
		}

		ack.base_uid = r->base_uid;
		ack.base = r->base_seq;
		ack.loss_num = 0;
		SKIPLIST_FOREACH(r->loss, iter){
			l = (wb_loss_t*)iter->val.ptr;

			if (iter->key.u32 <= r->base_seq)
				continue;

			if (l->ts + s->proc->rtt + s->proc->rtt_val <= cur_ts && ack.loss_num < LOSS_SISE){
				ack.loss[ack.loss_num++] = iter->key.u32;
				l->ts = cur_ts;

				r->loss_count++;
				l->count++;
			}

			if (l->count > max_count)
				max_count = l->count;
		}
		if (ack.loss_num > 0 || hb == 0)
			video_send_segment(s, &ack);

		r->ack_ts = cur_ts;
	}

	/*if (r->active_ts + 10000 < cur_ts)*/{
		if (max_count > 1){
			delay = (max_count * 16 + 7) * (s->proc->rtt + s->proc->rtt_val) / 16;
			if (delay > r->cache->wait_timer)
				r->cache->wait_timer = SU_MIN(delay, 5000);
		}
		else if (skiplist_size(r->loss) > 0)
			r->cache->wait_timer = SU_MAX((s->proc->rtt + s->proc->rtt_val * 2), r->cache->wait_timer);
	}

	r->cache->wait_timer = SU_MAX(r->cache->frame_timer, r->cache->wait_timer);

}

int video_real_video_put(cf_session_t* s, cf_video_real_buffer_t* r, cf_seg_video_t* seg)
{
	uint32_t seq;
	cf_seg_video_t* tmp;

	if (r->max_seq == 0 && seg->ftype == 0)
		return -1;

	seq = seg->seq;
	if (r->actived == 0 || seg->seq <= r->base_seq || (r->max_seq > 0 && seq > r->max_seq + 2000)){
		return -1;
	}


	if (r->max_seq == 0 && seg->seq > seg->index){
		r->max_seq = seg->seq - seg->index - 1;
		r->base_seq = seg->seq - seg->index - 1;
	}

	video_real_buffer_update_loss(s, r, seq);
	tmp = calloc(1, sizeof(cf_seg_video_t));
	*tmp = *seg;
	if (real_video_cache_put(s, r->cache, tmp) != 0){
		free(tmp);
		return -1;
	}

	if (seq == r->base_seq + 1)
		r->base_seq = seq;

	r->max_seq = SU_MAX(r->max_seq, seq);

	video_real_ack(s, r, 0);

	return 0;
}

int video_real_video_get(cf_session_t* s, cf_video_real_buffer_t* r, uint8_t* data, size_t* sizep)
{
	if (r->actived == 0)
		return -1;

	return real_video_cache_get(s, r->cache, data, sizep);

}

void video_real_video_timer(cf_session_t* s, cf_video_real_buffer_t* r)
{
	video_real_ack(s, r, 1);

	if (r->cache_ts + SU_MAX(s->proc->rtt + s->proc->rtt_val, 1000) < GET_SYS_MS()){
		if (r->loss_count == 0)
			r->cache->wait_timer = SU_MAX(r->cache->wait_timer * 7 / 8, r->cache->frame_timer);
		else if (r->cache->wait_timer > 2 * (s->proc->rtt + s->proc->rtt_val))
			r->cache->wait_timer = SU_MAX(r->cache->wait_timer * 15 / 16, r->cache->frame_timer);

		r->cache_ts = GET_SYS_MS();
		r->loss_count = 0;
	}
}



