/*
 * STFU (S)ort (T)ransportable (F)ramed (U)tterances
 * Copyright (c) 2007 Anthony Minessale II <anthm@freeswitch.org>
 *
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use,
 * copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following
 * conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 *
 * THOSE WHO DISAGREE MAY CERTAINLY STFU
 */
#include "stfu.h"

#ifdef _MSC_VER
/* warning C4706: assignment within conditional expression*/
#pragma warning(disable: 4706)
#endif

struct stfu_queue {
	struct stfu_frame *array;
	struct stfu_frame int_frame;
	uint32_t array_size;
	uint32_t array_len;	
	uint32_t wr_len;
};
typedef struct stfu_queue stfu_queue_t;

struct stfu_instance {
	struct stfu_queue a_queue;
	struct stfu_queue b_queue;
	struct stfu_queue *in_queue;
	struct stfu_queue *out_queue;
    struct stfu_frame *last_frame;
	uint32_t cur_ts;
	uint32_t last_wr_ts;
	uint32_t last_rd_ts;
	uint32_t interval;
	uint32_t miss_count;
	uint32_t max_plc;
};


static stfu_status_t stfu_n_resize_aqueue(stfu_queue_t *queue, uint32_t qlen)
{
    unsigned char *m;

    if (qlen <= queue->array_size) {
        return STFU_IT_FAILED;;
    }

	m = realloc(queue->array, qlen * sizeof(struct stfu_frame));
    assert(m);
    memset(m + queue->array_size, 0, qlen * sizeof(struct stfu_frame) - queue->array_size);
    queue->array = (struct stfu_frame *) m;
	queue->array_size = qlen;
	return STFU_IT_WORKED;
}

static void stfu_n_init_aqueue(stfu_queue_t *queue, uint32_t qlen)
{
	queue->array = calloc(qlen, sizeof(struct stfu_frame));
	assert(queue->array != NULL);
	memset(queue->array, 0, sizeof(struct stfu_frame) * qlen);	
	queue->array_size = qlen;
	queue->int_frame.plc = 1;
}

void stfu_n_destroy(stfu_instance_t **i)
{
	stfu_instance_t *ii;

	if (i && *i) {
		ii = *i;
		*i = NULL;
		free(ii->a_queue.array);
		free(ii->b_queue.array);
		free(ii);
	}
}

void stfu_n_report(stfu_instance_t *i, stfu_report_t *r)
{
    assert(i);
    r->in_len = i->in_queue->array_len;
    r->in_size = i->in_queue->array_size;
    r->out_len = i->out_queue->array_len;
    r->out_size = i->out_queue->array_size;
}

stfu_status_t stfu_n_resize(stfu_instance_t *i, uint32_t qlen) 
{
    stfu_status_t s;

    if ((s = stfu_n_resize_aqueue(&i->a_queue, qlen)) == STFU_IT_WORKED) {
        s = stfu_n_resize_aqueue(&i->b_queue, qlen);
    }
    
    return s;
}

stfu_instance_t *stfu_n_init(uint32_t qlen, uint32_t max_plc)
{
	struct stfu_instance *i;

	i = malloc(sizeof(*i));
	if (!i) {
		return NULL;
	}
	memset(i, 0, sizeof(*i));
	stfu_n_init_aqueue(&i->a_queue, qlen);
	stfu_n_init_aqueue(&i->b_queue, qlen);
	i->in_queue = &i->a_queue;
	i->out_queue = &i->b_queue;

    if (max_plc) {
        i->max_plc = max_plc;
    } else {
        i->max_plc = qlen / 2;
    }

	return i;
}

void stfu_n_reset(stfu_instance_t *i)
{
	i->in_queue = &i->a_queue;
	i->out_queue = &i->b_queue;
	i->in_queue->array_len = 0;
	i->out_queue->array_len = 0;
	i->out_queue->wr_len = 0;
	i->last_frame = NULL;
	i->miss_count = 0;	
	i->last_wr_ts = 0;
	i->miss_count = 0;
	i->interval = 0;
}

static int32_t stfu_n_measure_interval(stfu_queue_t *queue)
{
	uint32_t index;
	int32_t d, most = 0, last = 0, this, track[STFU_MAX_TRACK] = {0};

	for(index = 0; index < queue->array_len; index++) {
		this = queue->array[index].ts;
		if (last) {

			if ((d = this - last) > 0 && d / 10 < STFU_MAX_TRACK) {
				track[(d/10)]++;
			}
		}

		last = this;
	}

	for(index = 0; index < STFU_MAX_TRACK; index++) {
		if (track[index] > track[most]) {
			most = index;
		}
	}

	return most * 10;
}

static int16_t stfu_n_process(stfu_instance_t *i, stfu_queue_t *queue)
{
	if (!i->interval && !(i->interval = stfu_n_measure_interval(queue))) {
		return -1;
	}

	return 0;
}

stfu_status_t stfu_n_add_data(stfu_instance_t *i, uint32_t ts, uint32_t pt, void *data, size_t datalen, int last)
{
	uint32_t index;
	stfu_frame_t *frame;
	size_t cplen = 0;

	if (last || i->in_queue->array_len == i->in_queue->array_size) {
		stfu_queue_t *other_queue;

		if (i->out_queue->wr_len < i->out_queue->array_len) {
			return STFU_IT_FAILED;
		}

		other_queue = i->in_queue;
		i->in_queue = i->out_queue;
		i->out_queue = other_queue;

		i->in_queue->array_len = 0;
		i->out_queue->wr_len = 0;
		i->last_frame = NULL;
		i->miss_count = 0;

		if (stfu_n_process(i, i->out_queue) < 0) {
            if (i->in_queue->array_len == i->in_queue->array_size && i->out_queue->array_len == i->out_queue->array_size) {
                stfu_n_resize(i, i->out_queue->array_size * 2);
            }
			//return STFU_IT_FAILED;
		}
		for(index = 0; index < i->out_queue->array_len; index++) {
			i->out_queue->array[index].was_read = 0;
		}
	}

	if (last) {
		return STFU_IM_DONE;
	}

	index = i->in_queue->array_len++;
	frame = &i->in_queue->array[index];

	if ((cplen = datalen) > sizeof(frame->data)) {
		cplen = sizeof(frame->data);
	}

    i->last_rd_ts = ts;

	memcpy(frame->data, data, cplen);
    frame->pt = pt;
	frame->ts = ts;
	frame->dlen = cplen;
	frame->was_read = 0;	

	return STFU_IT_WORKED;
}

static int stfu_n_find_frame(stfu_queue_t *queue, uint32_t ts, stfu_frame_t **r_frame, uint32_t *index)
{
    uint32_t i = 0;
    stfu_frame_t *frame = NULL;

    assert(r_frame);
    assert(index);
    
    *r_frame = NULL;

    for(i = 0; i < queue->array_len; i++) {
        frame = &queue->array[i];
        
        if (frame->ts == ts) {
            *r_frame = frame;
            *index = i;
            frame->was_read = 1;
            return 1;
        }
    }

    return 0;
}

stfu_frame_t *stfu_n_read_a_frame(stfu_instance_t *i)
{
	uint32_t index;
	stfu_frame_t *rframe = NULL;
    
	if (((i->out_queue->wr_len == i->out_queue->array_len) || !i->out_queue->array_len)) {
		return NULL;
	}

    if (i->cur_ts == 0) {
		i->cur_ts = i->out_queue->array[0].ts;
    } else {
		i->cur_ts += i->interval;
    }
    

    if (stfu_n_find_frame(i->out_queue, i->cur_ts, &rframe, &index) || stfu_n_find_frame(i->in_queue, i->cur_ts, &rframe, &index)) {
        i->last_frame = rframe;
		i->out_queue->wr_len++;
		i->last_wr_ts = rframe->ts;
		i->miss_count = 0;
    } else {
        i->last_wr_ts = i->cur_ts;
        rframe = &i->out_queue->int_frame;

        if (i->last_frame && i->last_frame != rframe) {
            rframe->dlen = i->last_frame->dlen;
            /* poor man's plc..  Copy the last frame, but we flag it so you can use a better one if you wish */
            memcpy(rframe->data, i->last_frame->data, rframe->dlen);
        }

        rframe->ts = i->cur_ts;

        if (++i->miss_count > i->max_plc) {
            i->out_queue->wr_len = i->out_queue->array_size;
            i->cur_ts = 0;
            rframe = NULL;
        }
    }

	return rframe;
}

/* For Emacs:
 * Local Variables:
 * mode:c
 * indent-tabs-mode:nil
 * tab-width:4
 * c-basic-offset:4
 * End:
 * For VIM:
 * vim:set softtabstop=4 shiftwidth=4 tabstop=4:
 */