/* Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#include "cras_types.h"
#include "cras_util.h"
#include "utlist.h"

#include <time.h>

/* Represents an armed timer.
 * Members:
 *    ts - timespec at which the timer should fire.
 *    cb - Callback to call when the timer expires.
 *    cb_data - Data passed to the callback.
 */
struct cras_timer {
	struct timespec ts;
	void (*cb)(struct cras_timer *t, void *data);
	void *cb_data;
	struct cras_timer *next, *prev;
};

/* Timer Manager, keeps a list of active timers. */
struct cras_tm {
	struct cras_timer *timers;
};

/* Local Functions. */

/* Adds ms milliseconds to ts. */
static inline void add_ms_ts(struct timespec *ts, unsigned int ms)
{
	if (ms >= 1000) {
		ts->tv_sec += ms / 1000;
		ms %= 1000;
	}
	ts->tv_nsec += ms * 1000000L;
	if (ts->tv_nsec >= 1000000000L) {
		ts->tv_sec += ts->tv_nsec / 1000000000L;
		ts->tv_nsec %= 1000000000L;
	}
}

/* Checks if timespec a is less than b. */
static inline int timespec_sooner(const struct timespec *a,
				  const struct timespec *b)
{
	return (a->tv_sec < b->tv_sec ||
		(a->tv_sec == b->tv_sec && a->tv_nsec <= b->tv_nsec));
}

/* Exported Interface. */

struct cras_timer *cras_tm_create_timer(
		struct cras_tm *tm,
		unsigned int ms,
		void (*cb)(struct cras_timer *t, void *data),
		void *cb_data)
{
	struct cras_timer *t;

	t = calloc(1, sizeof(*t));
	if (!t)
		return NULL;

	t->cb = cb;
	t->cb_data = cb_data;

	clock_gettime(CLOCK_MONOTONIC_RAW, &t->ts);
	add_ms_ts(&t->ts, ms);

	DL_APPEND(tm->timers, t);

	return t;
}

void cras_tm_cancel_timer(struct cras_tm *tm, struct cras_timer *t)
{
	DL_DELETE(tm->timers, t);
	free(t);
}

struct cras_tm *cras_tm_init()
{
	return calloc(1, sizeof(struct cras_tm));
}

void cras_tm_deinit(struct cras_tm *tm)
{
	struct cras_timer *t;

	DL_FOREACH(tm->timers, t) {
		DL_DELETE(tm->timers, t);
		free(t);
	}
	free(tm);
}

int cras_tm_get_next_timeout(const struct cras_tm *tm, struct timespec *ts)
{
	struct cras_timer *t;
	struct timespec now;
	struct timespec *min;

	if (!tm->timers)
		return 0;

	min = &tm->timers->ts;
	DL_FOREACH(tm->timers, t)
		if (timespec_sooner(&t->ts, min))
			min = &t->ts;

	clock_gettime(CLOCK_MONOTONIC_RAW, &now);

	if (timespec_sooner(min, &now)) {
		/* Timer already expired. */
		ts->tv_sec = ts->tv_nsec = 0;
		return 1;
	}

	subtract_timespecs(min, &now, ts);
	return 1;
}

void cras_tm_call_callbacks(struct cras_tm *tm)
{
	struct timespec now;
	struct cras_timer *t, *next;

	clock_gettime(CLOCK_MONOTONIC_RAW, &now);

	/* Don't use DL_FOREACH to iterate timers because in each loop the
	 * next timer pointer is stored for later access but it could be
	 * cancelled and freed in current timer's callback causing invalid
	 * memory access. */
	t = tm->timers;
	while (t) {
		next = t->next;
		if (timespec_sooner(&t->ts, &now)) {
			t->cb(t, t->cb_data);
			/* Update next timer because it could have been modified
			 * in t->cb(). */
			next = t->next;
			cras_tm_cancel_timer(tm, t);
		}
		t = next;
	}
}