/*
 *  Copyright (C) 2005-2012, Jon Gettler
 *  http://www.mvpmc.org/
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 2.1 of the License, or (at your option) any later version.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <cmyth_local.h>

static void
cmyth_commbreaklist_destroy(cmyth_commbreaklist_t cbl)
{
	int i;

	cmyth_dbg(CMYTH_DBG_DEBUG, "%s\n", __FUNCTION__);
	if (!cbl) {
		return;
	}
	for (i = 0; i < cbl->commbreak_count; ++i) {
		if (cbl->commbreak_list[i]) {
			ref_release(cbl->commbreak_list[i]);
		}
		cbl->commbreak_list[i] = NULL;
	}
	if (cbl->commbreak_list) {
		free(cbl->commbreak_list);
	}
}

cmyth_commbreaklist_t
cmyth_commbreaklist_create(void)
{
	cmyth_commbreaklist_t ret;

	cmyth_dbg(CMYTH_DBG_DEBUG, "%s\n", __FUNCTION__);
	ret = ref_alloc(sizeof(*ret));
	if (!ret) {
		return(NULL);
	}
	ref_set_destroy(ret, (ref_destroy_t)cmyth_commbreaklist_destroy);

	ret->commbreak_list = NULL;
	ret->commbreak_count = 0;
	return ret;
}

void
cmyth_commbreak_destroy(cmyth_commbreak_t b)
{
	cmyth_dbg(CMYTH_DBG_DEBUG, "%s {\n", __FUNCTION__);
	if (!b) {
		cmyth_dbg(CMYTH_DBG_DEBUG, "%s }!a\n", __FUNCTION__);
		return;
	}
	cmyth_dbg(CMYTH_DBG_DEBUG, "%s }\n", __FUNCTION__);
}

cmyth_commbreak_t
cmyth_commbreak_create(void)
{
	cmyth_commbreak_t ret = ref_alloc(sizeof(*ret));

	cmyth_dbg(CMYTH_DBG_DEBUG, "%s {\n", __FUNCTION__);
	if (!ret) {
		cmyth_dbg(CMYTH_DBG_DEBUG, "%s }!\n", __FUNCTION__);
		return NULL;
	}
	ref_set_destroy(ret, (ref_destroy_t)cmyth_commbreak_destroy);

	ret->start_mark = 0;
	ret->start_offset = 0;
	ret->end_mark = 0;
	ret->end_offset = 0;

	cmyth_dbg(CMYTH_DBG_DEBUG, "%s }\n", __FUNCTION__);
	return ret;
}

cmyth_commbreaklist_t
cmyth_get_commbreaklist(cmyth_conn_t conn, cmyth_proginfo_t prog)
{
	unsigned int len = CMYTH_UTC_LEN + CMYTH_INT64_LEN + 19;
	int err;
	int count;
	char *buf;
	int r;

	cmyth_commbreaklist_t breaklist = cmyth_commbreaklist_create();

	buf = alloca(len);
	if (!buf) {
		return breaklist;
	}

	sprintf(buf,"%s %"PRIu32" %ld", "QUERY_COMMBREAK", prog->proginfo_chanId,
	        (long)cmyth_timestamp_to_unixtime(prog->proginfo_rec_start_ts));
	pthread_mutex_lock(&conn->conn_mutex);
	if ((err = cmyth_send_message(conn, buf)) < 0) {
		cmyth_dbg(CMYTH_DBG_ERROR,
			"%s: cmyth_send_message() failed (%d)\n",
			__FUNCTION__, err);
		goto out;
	}

	count = cmyth_rcv_length(conn);
	if (count < 0) {
		cmyth_dbg(CMYTH_DBG_ERROR,
			"%s: cmyth_rcv_length() failed (%d)\n",
			__FUNCTION__, count);
		goto out;
	}

	if ((r = cmyth_rcv_commbreaklist(conn, &err, breaklist, count)) < 0) {
		cmyth_dbg(CMYTH_DBG_ERROR,
			"%s: cmyth_rcv_string() failed (%d)\n",
			__FUNCTION__, r);
		goto out;
	}

	out:
	pthread_mutex_unlock(&conn->conn_mutex);
	return breaklist;
}

cmyth_commbreaklist_t
cmyth_get_cutlist(cmyth_conn_t conn, cmyth_proginfo_t prog)
{
	unsigned int len = CMYTH_UTC_LEN + CMYTH_INT64_LEN + 17;
	int err;
	int count;
	char *buf;
	int r;

	cmyth_commbreaklist_t breaklist = cmyth_commbreaklist_create();

	buf = alloca(len);
	if (!buf) {
		return breaklist;
	}

	sprintf(buf,"%s %"PRIu32" %ld", "QUERY_CUTLIST", prog->proginfo_chanId,
	        (long)cmyth_timestamp_to_unixtime(prog->proginfo_rec_start_ts));
	pthread_mutex_lock(&conn->conn_mutex);
	if ((err = cmyth_send_message(conn, buf)) < 0) {
		cmyth_dbg(CMYTH_DBG_ERROR,
			"%s: cmyth_send_message() failed (%d)\n",
			__FUNCTION__, err);
		goto out;
	}

	count = cmyth_rcv_length(conn);
	if (count < 0) {
		cmyth_dbg(CMYTH_DBG_ERROR,
			"%s: cmyth_rcv_length() failed (%d)\n",
			__FUNCTION__, count);
		goto out;
	}

	if ((r = cmyth_rcv_commbreaklist(conn, &err, breaklist, count)) < 0) {
		cmyth_dbg(CMYTH_DBG_ERROR,
			"%s: cmyth_rcv_string() failed (%d)\n",
			__FUNCTION__, r);
		goto out;
	}

	out:
	pthread_mutex_unlock(&conn->conn_mutex);
	return breaklist;
}

int cmyth_rcv_commbreaklist(cmyth_conn_t conn, int *err,
			cmyth_commbreaklist_t breaklist, int count)
{
	int consumed;
	int total = 0;
	int rows;
	int64_t mark;
	int64_t start_mark = 0;
	char *failed = NULL;
	cmyth_commbreak_t commbreak;
	uint8_t type;
	uint8_t start_type = -1;
	int started = 0;
	int i;

	if (count <= 0) {
		*err = EINVAL;
		return 0;
	}

	/*
	 * Get number of rows
	 */
	consumed = cmyth_rcv_int32(conn, err, &rows, count);
	count -= consumed;
	total += consumed;
	if (*err) {
		failed = "cmyth_rcv_long";
		goto fail;
	}

	if (rows < 0) {
		cmyth_dbg(CMYTH_DBG_DEBUG, "%s: no commercial breaks found.\n",
			__FUNCTION__);
		return 0;
	}

	for (i = 0; i < rows; i++) {
		consumed = cmyth_rcv_uint8(conn, err, &type, count);
		count -= consumed;
		total += consumed;
		if (*err) {
			failed = "cmyth_rcv_ushort";
			goto fail;
		}

		consumed = cmyth_rcv_int64(conn, err, &mark, count);
		count -= consumed;
		total += consumed;
		if (*err) {
			failed = "cmyth_rcv_long_long";
			goto fail;
		}
		if (type == CMYTH_COMMBREAK_START || type == CMYTH_CUTLIST_START) {
			start_mark = mark;
			start_type = type;
			started = 1;
		} else if (type == CMYTH_COMMBREAK_END || type == CMYTH_CUTLIST_END) {
			if (started &&
			    ((type == CMYTH_COMMBREAK_END && start_type == CMYTH_COMMBREAK_START)
			     || (type == CMYTH_CUTLIST_END && start_type == CMYTH_CUTLIST_START)))
			{
				commbreak = cmyth_commbreak_create();
				commbreak->start_mark = start_mark;
				commbreak->end_mark = mark;
				started = 0;
				breaklist->commbreak_list = realloc(breaklist->commbreak_list,
					(++breaklist->commbreak_count) * sizeof(cmyth_commbreak_t));
				breaklist->commbreak_list[breaklist->commbreak_count - 1] = commbreak;
			} else {
				cmyth_dbg(CMYTH_DBG_WARN,
					"%s: ignoring 'end' marker without a 'start' marker at %"PRId64"\n",
					__FUNCTION__, mark);
			}
		} else {
				cmyth_dbg(CMYTH_DBG_WARN,
					"%s: type (%d) is not a COMMBREAK or CUTLIST\n",
					__FUNCTION__, type);
		}
	}

	/*
	 * If the last entry is a start marker then it doesn't have an associated end marker. In this
	 * case we choose to simply ignore it. Another option is to put in a really large fake end marker
	 * but that may cause strange seek behaviour in a client application.
	 */

	return total;

	fail:
	cmyth_dbg(CMYTH_DBG_ERROR, "%s: %s() failed (%d)\n",
		__FUNCTION__, failed, *err);
	return total;
}
