/*	$Id$ */
/*
 * Copyright (c) 2014 Kristaps Dzonsons <kristaps@kcons.eu>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */
#include <assert.h>
#include <ctype.h>
#include <math.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <gtk/gtk.h>
#include <gsl/gsl_rng.h>
#include <gsl/gsl_multifit.h>
#include <gsl/gsl_histogram.h>
#include <kplot.h>

#include "extern.h"

#define	BUFSZ 1024

/*
 * All possible input tokens, including some which are "virtual" tokens
 * in that they don't map to an exact character representation.
 */
enum	token {
	TOKEN_PAREN_OPEN,
	TOKEN_PAREN_CLOSE,
	TOKEN_ADD,
	TOKEN_SUB,
	TOKEN_MUL,
	TOKEN_DIV,
	TOKEN_EXP,
	TOKEN_END,
	TOKEN_NUMBER,
	TOKEN_SQRT,
	TOKEN_EXPF,
	TOKEN_P1,
	TOKEN_PN,
	TOKEN_N,
	TOKEN_ERROR,
	TOKEN_SKIP,
	TOKEN_POSITIVE,
	TOKEN_NEGATIVE,
	TOK__MAX
};

enum	arity {
	ARITY_NONE = 0,
	ARITY_ONE,
	ARITY_TWO
};

enum	assoc {
	ASSOC_NONE = 0,
	ASSOC_L,
	ASSOC_R,
};

struct	tok {
	char		 key; /* input identifier */
	int		 prec; /* precedence (if applicable) */
	int		 oper; /* is operator? */
	int		 func; /* is function? */
	enum assoc	 assoc; /* associativity */
	enum arity	 arity; /* n-ary-ess */
	enum htype	 map; /* map to htype (if applicable) */
};

static	const struct tok toks[TOK__MAX] = {
	{ '(',  -1, 0, 0, ASSOC_NONE, ARITY_NONE, HNODE__MAX }, /* TOKEN_PAREN_OPEN */
	{ ')',  -1, 0, 0, ASSOC_NONE, ARITY_NONE, HNODE__MAX }, /* TOKEN_PAREN_CLOSE */
	{ '+',  2, 1, 0, ASSOC_L, ARITY_TWO, HNODE_ADD }, /* TOKEN_ADD */
	{ '-',  2, 1, 0, ASSOC_L, ARITY_TWO, HNODE_SUB }, /* TOKEN_SUB */
	{ '*',  3, 1, 0, ASSOC_L, ARITY_TWO, HNODE_MUL }, /* TOKEN_MUL */
	{ '/',  3, 1, 0, ASSOC_L, ARITY_TWO, HNODE_DIV }, /* TOKEN_DIV */
	{ '^',  5, 1, 0, ASSOC_R, ARITY_TWO, HNODE_EXP }, /* TOKEN_EXP */
	{ '\0', -1, 0, 0, ASSOC_NONE, ARITY_NONE, HNODE__MAX }, /* TOKEN_END */
	{ '\0', -1, 0, 0, ASSOC_NONE, ARITY_NONE, HNODE_NUMBER }, /* TOKEN_NUMBER */
	{ '\0', -1, 0, 1, ASSOC_NONE, ARITY_NONE, HNODE_SQRT }, /* TOKEN_SQRT */
	{ '\0', -1, 0, 1, ASSOC_NONE, ARITY_NONE, HNODE_EXPF }, /* TOKEN_EXPF */
	{ 'x',  -1, 0, 0, ASSOC_NONE, ARITY_NONE, HNODE_P1 }, /* TOKEN_P1 */
	{ 'X',  -1, 0, 0, ASSOC_NONE, ARITY_NONE, HNODE_PN }, /* TOKEN_PN */
	{ 'n',  -1, 0, 0, ASSOC_NONE, ARITY_NONE, HNODE_N }, /* TOKEN_N */
	{ '\0', -1, 0, 0, ASSOC_NONE, ARITY_NONE, HNODE__MAX }, /* TOKEN_ERROR */
	{ ' ',  -1, 0, 0, ASSOC_NONE, ARITY_NONE, HNODE__MAX }, /* TOKEN_SKIP */
	{ '+',  4, 1, 0, ASSOC_R, ARITY_ONE, HNODE_POSITIVE }, /* TOKEN_POSITIVE */
	{ '-',  4, 1, 0, ASSOC_R, ARITY_ONE, HNODE_NEGATIVE }, /* TOKEN_NEGATIVE */
};

#if 0
static void
hnode_print(struct hnode **p)
{
	struct hnode	**pp;

	if (NULL == p)
		return;

	for (pp = p; NULL != *pp; pp++) {
		switch ((*pp)->type) {
		case (HNODE_VAR):
			putchar('v');
			break;
		case (HNODE_P1):
			putchar('x');
			break;
		case (HNODE_P2):
			putchar('y');
			break;
		case (HNODE_NUMBER):
			printf("%g", (*pp)->real);
			break;
		case (HNODE_SQRT):
			printf("sqrt");
			break;
		case (HNODE_ADD):
			putchar('+');
			break;
		case (HNODE_EXP):
			putchar('^');
			break;
		case (HNODE_SUB):
			putchar('-');
			break;
		case (HNODE_MUL):
			putchar('*');
			break;
		case (HNODE_DIV):
			putchar('/');
			break;
		case (HNODE_POSITIVE):
			printf("+'");
			break;
		case (HNODE_NEGATIVE):
			printf("-'");
			break;
		default:
			abort();
		}
		putchar(' ');
	}
	putchar('\n');
}
#endif

/*
 * Check if the current token (which has ARITY_ONE, we assume) is in
 * fact unary.
 * We do this by checking the previous token: if it's a binary operator
 * or an open parenthesis, then we're a unary operator.
 */
static int
check_unary(enum token lasttok)
{
	switch (lasttok) {
	case (TOK__MAX):
		/* FALLTHROUGH */
	case (TOKEN_PAREN_OPEN):
		/* FALLTHROUGH */
	case (TOKEN_ADD):
		/* FALLTHROUGH */
	case (TOKEN_SUB):
		/* FALLTHROUGH */
	case (TOKEN_MUL):
		/* FALLTHROUGH */
	case (TOKEN_DIV):
		/* FALLTHROUGH */
	case (TOKEN_EXP):
		return(1);
	default:
		break;
	}
	return(0);
}

/*
 * Check if the current token (which has ARITY_TWO, we assume) is in
 * fact binary.
 * We do this by checking the previous token: if it's an operand or a
 * close-paren (i.e., an operand), then we're a binary operator.
 */
static int
check_binary(enum token lasttok)
{
	switch (lasttok) {
	case (TOKEN_PAREN_CLOSE):
		/* FALLTHROUGH */
	case (TOKEN_NUMBER):
		/* FALLTHROUGH */
	case (TOKEN_P1):
		/* FALLTHROUGH */
	case (TOKEN_PN):
		/* FALLTHROUGH */
	case (TOKEN_N):
		return(1);
	default:
		break;
	}
	return(0);
}

/*
 * Extra a token from input.
 * If TOKEN_ERROR, something bad has happened in the attempt to do so.
 * Otherwise, this returns a valid token (possibly end of input).
 */
static enum token
tokenise(const char **v, char *buf, enum token lasttok)
{
	size_t		i, sz;

	/* Short-circuit this case. */
	if ('\0' == **v)
		return(TOKEN_END);

	/* Look for token in our predefined inputs. */
	for (i = 0; i < TOK__MAX; i++)
		if ('\0' != toks[i].key && **v == toks[i].key) {
			switch (toks[i].arity) {
			case (ARITY_NONE):
				(*v)++;
				return((enum token)i);
			case (ARITY_ONE):
				if ( ! check_unary(lasttok))
					continue;
				break;
			case (ARITY_TWO):
				if ( ! check_binary(lasttok))
					continue;
				break;
			}
			(*v)++;
			return((enum token)i);
		}

	/* See if we're a real number or identifier. */
	if (isdigit((int)**v) || '.' == **v) {
		sz = 0;
		for ( ; isdigit((int)**v) || '.' == **v; (*v)++) {
			assert(sz < BUFSZ);
			buf[sz++] = **v;
		}
		buf[sz] = '\0';
		return(TOKEN_NUMBER);
	} else if (isalpha((int)**v)) {
		sz = 0;
		for ( ; isalpha((int)**v); (*v)++) {
			assert(sz < BUFSZ);
			buf[sz++] = **v;
		}
		buf[sz] = '\0';
		if (0 == strcmp(buf, "sqrt"))
			return(TOKEN_SQRT);
		else if (0 == strcmp(buf, "exp"))
			return(TOKEN_EXPF);
	}

	/* Eh... */
	return(TOKEN_ERROR);
}

/*
 * Queue something onto the output RPN queue.
 */
static void
enqueue(struct hnode ***q, size_t *qsz, enum token tok)
{

	*q = realloc(*q, ++(*qsz) * sizeof(struct hnode *));
	(*q)[*qsz - 1] = calloc(1, sizeof(struct hnode));
	(*q)[*qsz - 1]->type = toks[tok].map;
	assert(HNODE__MAX != toks[tok].map);
}

/*
 * Make sure that all operators and functions are matched with
 * arguments.
 * The Shunting-Yard algorithm itself doesn't do this, so we need to do
 * it now.
 */
static int
check(struct hnode **p)
{
	struct hnode	**pp;
	size_t		  ssz;

	for (ssz = 0, pp = p; NULL != *pp; pp++)
		switch ((*pp)->type) {
		case (HNODE_P1):
			/* FALLTHROUGH */
		case (HNODE_PN):
			/* FALLTHROUGH */
		case (HNODE_N):
			/* FALLTHROUGH */
		case (HNODE_NUMBER):
			ssz++;
			break;
		case (HNODE_POSITIVE):
			/* FALLTHROUGH */
		case (HNODE_NEGATIVE):
			/* FALLTHROUGH */
		case (HNODE_SQRT):
			/* FALLTHROUGH */
		case (HNODE_EXPF):
			if (0 == ssz)
				return(0);
			break;
		case (HNODE_ADD):
			/* FALLTHROUGH */
		case (HNODE_SUB):
			/* FALLTHROUGH */
		case (HNODE_MUL):
			/* FALLTHROUGH */
		case (HNODE_DIV):
			/* FALLTHROUGH */
		case (HNODE_EXP):
			if (ssz < 2)
				return(0);
			ssz--;
			break;
		default:
			abort();
		}

	return(1 == ssz);
}

/*
 * Dijkstra's Shunting-Yard algorithm for converting an infix-order
 * expression to prefix order.
 * This returns a NULL-terminated list of expressions to evaluate in
 * post-fix order.
 */
struct hnode **
hnode_parse(const char **v)
{
	enum token	  tok, lasttok;
	enum token	  stack[STACKSZ];
	char		  buf[BUFSZ + 1];
	struct hnode	**q;
	int		  found;
	size_t		  i, qsz, ssz;

	q = NULL;
	qsz = ssz = 0;
	lasttok = TOK__MAX;

	while (TOKEN_END != (tok = tokenise(v, buf, lasttok))) {
		switch (tok) {
		case (TOKEN_ERROR):
			goto err;
		case (TOKEN_SKIP):
			break;
		case (TOKEN_P1):
			/* FALLTHROUGH */
		case (TOKEN_PN):
			/* FALLTHROUGH */
		case (TOKEN_N):
			enqueue(&q, &qsz, tok);
			break;
		case (TOKEN_NUMBER):
			enqueue(&q, &qsz, tok);
			q[qsz - 1]->real = atof(buf);
			break;
		case (TOKEN_SQRT):
			/* FALLTHROUGH */
		case (TOKEN_EXPF):
			/* FALLTHROUGH */
		case (TOKEN_PAREN_OPEN):
			assert(ssz < STACKSZ);
			stack[ssz++] = tok;
			break;
		case (TOKEN_PAREN_CLOSE):
			assert(ssz > 0);
			found = 0;
			do {
				if (TOKEN_PAREN_OPEN == stack[--ssz])
					found = 1;
				else
					enqueue(&q, &qsz, stack[ssz]);
			} while ( ! found && ssz > 0);
			assert(found);
			if (ssz > 0 && toks[stack[ssz - 1]].func)
				enqueue(&q, &qsz, stack[--ssz]);
			break;
		case (TOKEN_POSITIVE):
			/* FALLTHROUGH */
		case (TOKEN_NEGATIVE):
			/* FALLTHROUGH */
		case (TOKEN_SUB):
			/* FALLTHROUGH */
		case (TOKEN_MUL):
			/* FALLTHROUGH */
		case (TOKEN_DIV):
			/* FALLTHROUGH */
		case (TOKEN_EXP):
			/* FALLTHROUGH */
		case (TOKEN_ADD):
			assert(toks[tok].prec >= 0);
			assert(ASSOC_NONE != toks[tok].assoc);
			while (ssz > 0 && toks[stack[ssz - 1]].oper) {
				assert(toks[stack[ssz - 1]].prec >= 0);
				assert(ASSOC_NONE !=
					toks[stack[ssz - 1]].assoc);
				if ((ASSOC_L == toks[tok].assoc
					 && toks[tok].prec ==
					 toks[stack[ssz - 1]].prec) ||
					 (toks[tok].prec <
					  toks[stack[ssz - 1]].prec))
					enqueue(&q, &qsz, stack[--ssz]);
				else
					break;
			}
			assert(ssz < STACKSZ);
			stack[ssz++] = tok;
			break;
		default:
			abort();
		}
		if (TOKEN_SKIP != tok)
			lasttok = tok;
	}

	while (ssz > 0) {
		--ssz;
		if (TOKEN_PAREN_OPEN == stack[ssz])
			goto err;
		enqueue(&q, &qsz, stack[ssz]);
	}

	q = realloc(q, ++qsz * sizeof(struct hnode *));
	q[qsz - 1] = NULL;

	if ( ! check(q))
		goto err;

#if 0
	hnode_print(q);
#endif
	return(q);
err:
	for (i = 0; i < qsz; i++)
		free(q[i]);
	free(q);
	return(NULL);
}

struct hnode **
hnode_copy(struct hnode **p)
{
	struct hnode	**pp;
	size_t		  sz;

	for (sz = 0, pp = p; NULL != *pp; pp++)
		sz++;

	pp = calloc(sz + 1, sizeof(struct hnode *));
	for (sz = 0; NULL != *p; p++, sz++) {
		pp[sz] = calloc(1, sizeof(struct hnode));
		pp[sz]->type = (*p)->type;
		pp[sz]->real = (*p)->real;
	}

	return(pp);
}

void
hnode_free(struct hnode **p)
{
	struct hnode	**pp;

	if (NULL == p)
		return;

	for (pp = p; NULL != *pp; pp++)
		free(*pp);

	free(p);
}

/*
 * Execute a function in prefix order given variables x (given player's
 * strategy), y (other player's strategy), and var (given player's
 * morality index).
 */
double
hnode_exec(const struct hnode *const *p, double x, double X, size_t n)
{
	double		  stack[STACKSZ];
	const struct hnode *const *pp;
	size_t		  ssz;
	double		  val;

	for (ssz = 0, pp = p; NULL != *pp; pp++)
		switch ((*pp)->type) {
		case (HNODE_P1):
			assert(ssz < STACKSZ);
			stack[ssz++] = x;
			break;
		case (HNODE_PN):
			assert(ssz < STACKSZ);
			stack[ssz++] = X;
			break;
		case (HNODE_N):
			assert(ssz < STACKSZ);
			stack[ssz++] = (double)n;
			break;
		case (HNODE_NUMBER):
			assert(ssz < STACKSZ);
			stack[ssz++] = (*pp)->real;
			break;
		case (HNODE_SQRT):
			assert(ssz > 0);
			val = sqrt(stack[--ssz]);
			stack[ssz++] = val;
			break;
		case (HNODE_EXPF):
			assert(ssz > 0);
			val = exp(stack[--ssz]);
			stack[ssz++] = val;
			break;
		case (HNODE_ADD):
			assert(ssz > 1);
			val = stack[ssz - 2] + stack[ssz - 1];
			ssz -= 2;
			stack[ssz++] = val;
			break;
		case (HNODE_SUB):
			assert(ssz > 1);
			val = stack[ssz - 2] - stack[ssz - 1];
			ssz -= 2;
			stack[ssz++] = val;
			break;
		case (HNODE_MUL):
			assert(ssz > 1);
			val = stack[ssz - 2] * stack[ssz - 1];
			ssz -= 2;
			stack[ssz++] = val;
			break;
		case (HNODE_DIV):
			assert(ssz > 1);
			val = stack[ssz - 2] / stack[ssz - 1];
			ssz -= 2;
			stack[ssz++] = val;
			break;
		case (HNODE_EXP):
			assert(ssz > 1);
			val = pow(stack[ssz - 2], stack[ssz - 1]);
			ssz -= 2;
			stack[ssz++] = val;
			break;
		case (HNODE_POSITIVE):
			assert(ssz > 0);
			break;
		case (HNODE_NEGATIVE):
			assert(ssz > 0);
			val = -(stack[--ssz]);
			stack[ssz++] = val;
			break;
		default:
			abort();
		}

	assert(1 == ssz);
	return(stack[0]);
}

static void
hnode_test_expect(double x, double X,
	double n, const char *expf, double vexp)
{
	struct hnode	 **exp;
	double		   v;
	const char	  *expfp;

	expfp = expf;
	exp = hnode_parse((const char **)&expfp);
	assert(NULL != exp);
	v = hnode_exec
		((const struct hnode *const *)exp, x, X, n);
	g_debug("pi(x=%g, X=%g, n=%g) = %s = %g (want %g)",
		x, X, n, expf, v, vexp);
	hnode_free(exp);
}

void
hnode_test(void)
{
	double	x, X, n;

	x = 10.0;
	X = 20.0;
	n = 2.0;
	hnode_test_expect(x, X, n,
		"(1 - exp(-X)) - x",
		(1.0 - exp(-X)) - x);
	hnode_test_expect(x, X, n,
		"sqrt(1 / n * X) - 0.5 * x^2",
		sqrt(1.0 / n * X) - 0.5 * pow(x, 2.0));
	hnode_test_expect(x, X, n,
		"x - (X - x) * x - x^2",
		x - (X - x) * x - pow(x, 2.0));
	hnode_test_expect(x, X, n,
		"x * (1 / X) - x",
		x * (1.0 / X) - x);
}


/*
 * Copyright 2014 RWTH Aachen University. All rights reserved.
 *
 * Licensed under the RWTH LM License (the "License");
 * you may not use this file except in compliance with the License.
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#pragma once
#include <gsl/gsl_cblas.h>
#include <cassert>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <numeric>

typedef double Real;

inline void FastZero(const int size, float x[]) {
  std::fill(x, x + size, 0.0f);
}

inline void FastZero(const int size, double x[]) {
  std::fill(x, x + size, 0.0);
}

inline void FastCopy(const float source[],
                     const int size,
                     float destination[]) {
  memcpy(destination, source, size * sizeof(float));
}

inline void FastCopy(const double source[],
                     const int size,
                     double destination[]) {
  memcpy(destination, source, size * sizeof(double));
}

inline float FastMax(const float x[], const int size) {
  return *std::max_element(x, x + size);
}

inline double FastMax(const double x[], const int size) {
  return *std::max_element(x, x + size);
}

inline float FastComputeSum(const float x[], const int size) {
  return std::accumulate(x, x + size, 0.0f);
}

inline double FastComputeSum(const double x[], const int size) {
  return std::accumulate(x, x + size, 0.0);
}

inline void FastAddConstant(const float source[],
                            const int size,
                            const float value,
                            float destination[]) {
  std::transform(source,
                 source + size,
                 destination,
                 [value](const float x) { return x + value; });
}

inline void FastAddConstant(const double source[],
                            const int size,
                            const double value,
                            double destination[]) {
  std::transform(source,
                 source + size,
                 destination,
                 [value](const double x) { return x + value; });
}

inline void FastSubtractConstant(const float source[],
                                 const int size,
                                 const float value,
                                 float destination[]) {
  std::transform(source,
                 source + size,
                 destination,
                 [value](const double x) { return x - value; });
}

inline void FastSubtractConstant(const double source[],
                                 const int size,
                                 const double value,
                                 double destination[]) {
  std::transform(source,
                 source + size,
                 destination,
                 [value](const double x) { return x - value; });
}

inline void FastReverseSubtractConstant(
    const float source[],
    const int size,
    const float value,
    float destination[]) {
  std::transform(source,
                 source + size,
                 destination,
                 [value](const float x) { return value - x; });
}

inline void FastReverseSubtractConstant(
    const double source[],
    const int size,
    const double value,
    double destination[]) {
  std::transform(source,
                 source + size,
                 destination,
                 [value](const double x) { return value - x; });
}

inline void FastMultiplyByConstant(const float source[],
                                   const int size,
                                   const float value,
                                   float destination[]) {
  std::transform(source,
                 source + size,
                 destination,
                 [value](const float x) { return value * x; });
}

inline void FastMultiplyByConstant(const double source[],
                                   const int size,
                                   const double value,
                                   double destination[]) {
  std::transform(source,
                 source + size,
                 destination,
                 [value](const double x) { return value * x; });
}

inline void FastDivideByConstant(const float source[],
                                 const int size,
                                 const float value,
                                 float destination[]) {
  std::transform(source,
                 source + size,
                 destination,
                 [value](const float x) { return x / value; });
}

inline void FastDivideByConstant(const double source[],
                                 const int size,
                                 const double value,
                                 double destination[]) {
  std::transform(source,
                 source + size,
                 destination,
                 [value](const double x) { return x / value; });
}

inline void FastInvert(const float source[],
                       const int size,
                       float destination[]) {
  std::transform(source,
                 source + size,
                 destination,
                 [](const double x) { return 1.0f / x; });
}

inline void FastInvert(const double source[],
                       const int size,
                       double destination[]) {
  std::transform(source,
                 source + size,
                 destination,
                 [](const double x) { return 1.0 / x; });
}

inline void FastAdd(const float a[],
                    const int size,
                    const float b[],
                    float c[]) {
  std::transform(a,
                 a + size,
                 b,
                 c,
                 [](const float x, const float y) { return x + y; });
}

inline void FastAdd(const double a[],
                    const int size,
                    const double b[],
                    double c[]) {
  std::transform(a,
                 a + size,
                 b,
                 c,
                 [](const double x, const double y) { return x + y; });
}

inline void FastMultiplyByConstantAdd(const float alpha,
                                      const float a[],
                                      const int size,
                                      float b[]) {
  cblas_saxpy(size, alpha, a, 1, b, 1);
}

inline void FastMultiplyByConstantAdd(const double alpha,
                                      const double a[],
                                      const int size,
                                      double b[]) {
  cblas_daxpy(size, alpha, a, 1, b, 1);
}

inline void FastSub(const float a[],
                    const int size,
                    const float b[],
                    float c[]) {
  std::transform(a,
                 a + size,
                 b,
                 c,
                 [](const float x, const float y) { return x - y; });
}

inline void FastSub(const double a[],
                    const int size,
                    const double b[],
                    double c[]) {
  std::transform(a,
                 a + size,
                 b,
                 c,
                 [](const double x, const double y) { return x - y; });
}

inline void FastMultiply(const float a[],
                         const int size,
                         const float b[],
                         float c[]) {
  std::transform(a,
                 a + size,
                 b,
                 c,
                 [](const float x, const float y) { return x * y; });
}

inline void FastMultiply(const double a[],
                         const int size,
                         const double b[],
                         double c[]) {
  std::transform(a,
                 a + size,
                 b,
                 c,
                 [](const double x, const double y) { return x * y; });
}

inline void FastMultiplyAdd(const float a[],
                            const int size,
                            const float b[],
                            float c[]) {
  for (int i = 0; i < size; ++i)
    c[i] += a[i] * b[i];
}

inline void FastMultiplyAdd(const double a[],
                            const int size,
                            const double b[],
                            double c[]) {
  for (int i = 0; i < size; ++i)
    c[i] += a[i] * b[i];
}

inline void FastTanh(const float source[],
                     const int size,
                     float destination[]) {
  std::transform(source,
                 source + size,
                 destination,
                 [](const float x) { return std::tanh(x); });
}

inline void FastTanh(const double source[],
                     const int size,
                     double destination[]) {
  std::transform(source,
                 source + size,
                 destination,
                 [](const double x) { return std::tanh(x); });
}

inline void FastExponential(const float source[],
                            const int size,
                            float destination[]) {
  std::transform(source,
                 source + size,
                 destination,
                 [](const float x) { return std::exp(x); });
}

inline void FastExponential(const double source[],
                            const int size,
                            double destination[]) {
  std::transform(source,
                 source + size,
                 destination,
                 [](const double x) { return std::exp(x); });
}

inline void FastMatrixVectorMultiply(const float a[],
                                     const bool transpose_a,
                                     const int rows_a,
                                     const int columns_a,
                                     const float x[],
                                     float y[]) {
  cblas_sgemv(CblasColMajor,
              transpose_a ? CblasTrans : CblasNoTrans,
              rows_a,
              columns_a,
              1.0f,
              a,
              rows_a,
              x,
              1,
              1.0f,
              y,
              1);
}

inline void FastMatrixVectorMultiply(const double a[],
                                     const bool transpose_a,
                                     const int rows_a,
                                     const int columns_a,
                                     const double x[],
                                     double y[]) {
  cblas_dgemv(CblasColMajor,
              transpose_a ? CblasTrans : CblasNoTrans,
              rows_a,
              columns_a,
              1.0,
              a,
              rows_a,
              x,
              1,
              1.0,
              y,
              1);
}

inline void FastOuterProduct(const float alpha,
                             const float x[],
                             const int size_x,
                             const float y[],
                             const int size_y,
                             float a[]) {
  cblas_sger(CblasColMajor,
             size_x,
             size_y,
             alpha,
             x,
             1,
             y,
             1,
             a,
             size_x);
}

inline void FastOuterProduct(const double alpha,
                             const double x[],
                             const int size_x,
                             const double y[],
                             const int size_y,
                             double a[]) {
  cblas_dger(CblasColMajor,
             size_x,
             size_y,
             alpha,
             x,
             1,
             y,
             1,
             a,
             size_x);
}

inline void FastMatrixMatrixMultiply(
      const float alpha,
      const float a[],
      const bool transpose_a,
      const int rows_a,
      const int columns_a,
      const float b[],
      const bool transpose_b,
      const int columns_b,
      float c[]) {
  cblas_sgemm(CblasColMajor,
              transpose_a ? CblasTrans : CblasNoTrans,
              transpose_b ? CblasTrans : CblasNoTrans,
              rows_a,
              columns_b,
              columns_a,
              alpha,
              a,
              transpose_a ? columns_a : rows_a,
              b,
              transpose_b ? columns_b : columns_a,
              1.0f,
              c,
              rows_a);
}

inline void FastMatrixMatrixMultiply(
      const double alpha,
      const double a[],
      const bool transpose_a,
      const int rows_a,  // m
      const int columns_a,  // k
      const double b[],
      const bool transpose_b,
      const int columns_b,  // n
      double c[]) {
  cblas_dgemm(CblasColMajor,
              transpose_a ? CblasTrans : CblasNoTrans,
              transpose_b ? CblasTrans : CblasNoTrans,
              rows_a,  // m
              columns_b,  // n
              columns_a,  // k
              alpha,
              a,
              transpose_a ? columns_a : rows_a,
              b,
              transpose_b ? columns_b : columns_a,
              1.0,
              c,
              rows_a);
}

inline Real *FastMalloc(const int size) {
  Real *result = new Real[size];
  assert(result != nullptr || size == 0);
  return result;
}

inline void FastFree(Real *x) {
  delete [] x;
}


/****************************************************************
 *
 * Copyright (C) Max Planck Institute
 * for Biological Cybernetics, Tuebingen, Germany
 *
 * Author: Gabriele Lohmann, 2015
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 *****************************************************************/

/*
** trial average using spline interpolation
**
** G.Lohmann, May 2014
*/
#include <viaio/Vlib.h>
#include <viaio/VImage.h>
#include <viaio/mu.h>
#include <viaio/option.h>

#include <gsl/gsl_cblas.h>
#include <gsl/gsl_matrix.h>
#include <gsl/gsl_vector.h>
#include <gsl/gsl_spline.h>

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <math.h>

#define ABS(x) ((x) > 0 ? (x) : -(x))
#define SQR(x) ((x)*(x))

#define LEN     10000   /* buffer length        */
#define NTRIALS 10000   /* max number of trials */

typedef struct SpointStruct {
  VShort x;
  VShort y;
  VShort z;
} SPoint;

typedef struct TrialStruct {
  int   id;
  float onset;
  float duration;
  float height;
} Trial;

Trial trial[NTRIALS];
int ntrials=0;
int nevents=0;

int test_ascii(int val)
{
  if (val >= 'a' && val <= 'z') return 1;
  if (val >= 'A' && val <= 'Z') return 1;
  if (val >= '0' && val <= '9') return 1;
  if (val ==  ' ') return 1;
  if (val == '\0') return 1;
  if (val == '\n') return 1;
  if (val == '\r') return 1;
  if (val == '\t') return 1;
  if (val == '\v') return 1;
  return 0;
}

/* parse design file */
void ReadDesign(VString designfile)
{
  FILE *fp=NULL;
  int  i,j,k,id;
  char buf[LEN];
  float onset=0,duration=0,height=0;

  fp = fopen(designfile,"r");
  if (!fp) VError(" error opening design file %s",designfile);

  i = ntrials = nevents = 0;
  while (!feof(fp)) {
    for (j=0; j<LEN; j++) buf[j] = '\0';
    if (fgets(buf,LEN,fp) == NULL) break;
    if (strlen(buf) < 2) continue;
    if (buf[0] == '%' || buf[0] == '#') continue;
    if (! test_ascii((int)buf[0])) VError(" input file must be a text file");

    /* remove non-alphanumeric characters */
    for (j=0; j<strlen(buf); j++) {
      k = (int)buf[j];
      if (!isgraph(k) && buf[j] != '\n' && buf[j] != '\r' && buf[j] != '\0') {
	buf[j] = ' ';
      }
      if (buf[j] == '\v') buf[j] = ' '; /* remove tabs */
      if (buf[j] == '\t') buf[j] = ' ';
    }

    if (sscanf(buf,"%d %f %f %f",&id,&onset,&duration,&height) != 4)
      VError(" line %d: illegal input format",i+1);

    if (duration < 0.5 && duration >= -0.0001) duration = 0.5;
    trial[i].id       = id;
    trial[i].onset    = onset;
    trial[i].duration = duration;
    trial[i].height   = height;
    i++;
    if (i > NTRIALS) VError(" too many trials %d",i);

    if (id > nevents) nevents = id;
  }
  fclose(fp);

  ntrials = i;
}

int main (int argc,char *argv[])
{
  static VString  designfile = "";
  static VShort   cond_id = 1;
  static VShort   trial_id = 0;
  static VDouble  temporal_resolution = 1.0;
  static VDouble  start = 0;
  static VDouble  length = 20;
  static VOptionDescRec  options[] = {
    {"design", VStringRepn, 1, & designfile, VRequiredOpt, NULL,"Design file (ascii)" },
    {"cond", VShortRepn, 1, & cond_id, VOptionalOpt, NULL,"Id of experimental condition"},
    {"trial", VShortRepn, 1, & trial_id, VOptionalOpt, NULL,"Id of trial (starts at 0)"},
    {"resolution", VDoubleRepn, 1, & temporal_resolution, VOptionalOpt, NULL," output temporal resolution in secs"},
    {"start", VDoubleRepn, 1, & start, VOptionalOpt, NULL, "start relative to beginning of trial in secs"},
    {"length", VDoubleRepn, 1, & length, VOptionalOpt, NULL, "trial length in seconds"},
  };
  FILE *in_file=NULL,*out_file=NULL;
  VAttrList list=NULL;
  VAttrListPosn posn;
  int i,j,k,slice,row,col;
  int nslices=0,nrows=0,ncols=0,ntimesteps=0;
  double t=0;
  char *prg = GetLipsiaName("vcuttrials");
  fprintf (stderr, "%s\n", prg);

  /*  parse command line */
  VParseFilterCmd (VNumber (options),options,argc,argv,&in_file,&out_file);

  /* get image dimensions, read functional data */
  if (! (list = VReadFile (in_file, NULL))) exit (1);
  fclose(in_file);

  nslices = 0;
  VImage tmp=NULL;
  for (VFirstAttr (list, & posn); VAttrExists (& posn); VNextAttr (& posn)) {
    if (VGetAttrRepn (& posn) != VImageRepn) continue;
    VGetAttrValue (& posn, NULL,VImageRepn, & tmp);
    /* if (VPixelRepn(tmp) != VShortRepn) continue; */
    nslices++;
  }
  if (nslices < 1) VError(" no slices");

  VAttrList geoinfo = VGetGeoInfo(list);
  VImage *src = (VImage *) VCalloc(nslices,sizeof(VImage));
  i = ntimesteps = nrows = ncols = 0;
  for (VFirstAttr (list, & posn); VAttrExists (& posn); VNextAttr (& posn)) {
    if (VGetAttrRepn (& posn) != VImageRepn) continue;
    VGetAttrValue (& posn, NULL,VImageRepn, & src[i]);
    /* if (VPixelRepn(src[i]) != VShortRepn) continue; */
    if (VImageNBands(src[i]) > ntimesteps) ntimesteps = VImageNBands(src[i]);
    if (VImageNRows(src[i])  > nrows)  nrows = VImageNRows(src[i]);
    if (VImageNColumns(src[i]) > ncols) ncols = VImageNColumns(src[i]);
    i++;
  }
  fprintf(stderr," nslices: %d, nrows: %d, ncols: %d,  ntimesteps: %d\n",nslices,nrows,ncols,ntimesteps);

  /* read repetition time */
  double tr = 0;
  if (VGetAttr (VImageAttrList (src[0]), "repetition_time", NULL,
		VDoubleRepn, (VPointer) & tr) != VAttrFound) {
    VError(" attribute 'repetition_time' missing");
  }
  tr /= 1000.0;
  double experiment_duration = tr * (double)ntimesteps;

  /* read design file */
  ReadDesign(designfile);

  /* select trial id */
  int tid=0,j0=-1;
  for (j=0; j<ntrials; j++) {
    if (trial[j].id == cond_id && tid == trial_id) {
      j0 = j;
      break;
    }
    if (trial[j].id == cond_id) tid++;
  }
  if (j0 < 0) VError("trial not found");
  fprintf(stderr," trial onset: %f\n",trial[j0].onset);

  /* adjust start time */
  trial[j0].onset += start;
  if (trial[j0].onset < 0) {
    VWarning(" negative onset, reset to zero");
    trial[j0].onset = 0;
  }
  if (trial[j0].onset + length >= experiment_duration) {
    VWarning(" experiment_duration exceeded",experiment_duration);
    length = experiment_duration - trial[j0].onset-0.5;
    VWarning(" parameter '-length' set to %f\n",length);
  }

  int nt = (int)(length/temporal_resolution + 0.5);
  fprintf(stderr," number of output timesteps: %d\n",nt);

  /* ini output data structs  */
  VImage *dest = (VImage *) VCalloc(nslices, sizeof(VImage));
  VAttrList out_list = VCreateAttrList();
  if (geoinfo != NULL) VSetGeoInfo(geoinfo,out_list);
  for (slice=0; slice<nslices; slice++) {
    dest[slice] = VCreateImage(nt,nrows,ncols,VPixelRepn(src[slice]));
    VFillImage(dest[slice],VAllBands,0);
    VCopyImageAttrs (src[slice], dest[slice]);
    VAppendAttr(out_list,"image",NULL,VImageRepn,dest[slice]);
  }

  /* spline interpolation  */
  double tstep = temporal_resolution;
  double *xx = (double *) VCalloc(ntimesteps,sizeof(double));
  double *yy = (double *) VCalloc(ntimesteps,sizeof(double));

  gsl_interp_accel *acc = gsl_interp_accel_alloc ();
  gsl_spline *spline = gsl_spline_alloc (gsl_interp_cspline, ntimesteps);

  for (slice=0; slice<nslices; slice++) {
    for (row=0; row<nrows; row++) {
      for (col=0; col<ncols; col++) {

	for (k=0; k<ntimesteps; k++) {
	  xx[k] = tr*((double)k);
	  yy[k] = (double)VGetPixel(src[slice],k,row,col);
	}
	gsl_spline_init (spline, xx, yy, ntimesteps);

	k=0;
	for (t=0; t<length; t += tstep) {
	  if (k >= nt) break;
	  double xi = trial[j].onset + t;
	  double yi = 0;
	  if (xi < experiment_duration && xi >= 0) {
	    if (gsl_spline_eval_e (spline,xi,acc,&yi) == GSL_EDOM) continue;
	  }
	  VSetPixel(dest[slice],k,row,col,yi);
	  k++;
	}
      }
    }
  }

  /* write to disk */
  if (! VWriteFile (out_file, out_list)) exit (1);
  fprintf (stderr, "%s: done.\n", argv[0]);
  exit(0);
}


/*
 * CircularDB implementation for time series data.
 *
 * Copyright (c) 2007-2009 Powerset, Inc
 * Copyright (c) Dan Grillo, Manish Dubey, Dan Sully
 *
 * All rights reserved.
 */

#include "config.h"

#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <float.h>
#include <inttypes.h>
#include <math.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <time.h>
#include <unistd.h>

/* For the aggregation interface */
#include <gsl/gsl_errno.h>
#include <gsl/gsl_interp.h>
#include <gsl/gsl_sort.h>
#include <gsl/gsl_statistics.h>

#include <circulardb_interface.h>

/* Future win32 support */
#ifndef O_BINARY
#define O_BINARY 0
#endif

/* Save away errno so it doesn't get overwritten */
int cdb_error(void) {
    int cdb_error = errno;
    return cdb_error;
}

static void _print_record(FILE *fh, cdb_time_t time, double value, const char *date_format) {

    if (date_format == NULL || strcmp(date_format, "") == 0) {

        fprintf(fh, "%d %.8g\n", (int)(time), value);

    } else {

        char formatted[256];
        time_t stime = (time_t)time;

        strftime(formatted, sizeof(formatted), date_format, localtime(&stime));

        fprintf(fh, "%d [%s] %.8g\n", (int)(time), formatted, value);
    }
}

static uint64_t _physical_record_for_logical_record(cdb_header_t *header, int64_t logical_record) {

    uint64_t physical_record = 0;

    /* -ve indicates nth record from the end
       +ve indicates nth record from the beginning (zero based counting for
          this case)

       -3 = seek to 3rd record from the end
        5 = seek to 6th record from the beginning
        0 = seek to first record (start) from the beginning

       change the request for -nth record (nth record from the end)
       to a request for mth record from the beginning, where:

       m = N + n (n is -ve)

       N : total num of records. */

    if (logical_record < 0) {
        int64_t rec_num_from_start = header->num_records + logical_record;

        /* if asking for records more than there are in the db, just give as many as we can */
        logical_record = rec_num_from_start >= 0 ? rec_num_from_start : 0;
    }

    if (logical_record >= header->num_records) {
#ifdef DEBUG
        printf("Can't seek to record [%"PRIu64"] with only [%"PRIu64"] in db\n", logical_record, header->num_records);
#endif
        return 0;
    }

    /* DRK unclear behavior here. Looks like if I specify -N where N >
       num_records, I get pointed to #0, but if I specify +N where N >
       num_records, it's an error */

    /* Nth logical record (from the beginning) maps to Mth physical record
       in the file, where:

       m = (n+s) % N

       s = physical record that is 0th logical record
       N = total number of records in the db.  */

    physical_record = logical_record + header->start_record;

    if (header->num_records > 0) {
        physical_record = (physical_record % header->num_records);
    }

    return physical_record;
}

static int64_t _seek_to_logical_record(cdb_t *cdb, int64_t logical_record) {

    uint64_t physical_record = _physical_record_for_logical_record(cdb->header, logical_record);
    uint64_t offset = HEADER_SIZE + (physical_record * RECORD_SIZE);

    if (lseek(cdb->fd, offset, SEEK_SET) != offset) {
        return -1;
    }

    return physical_record;
}

static cdb_time_t _time_for_logical_record(cdb_t *cdb, int64_t logical_record) {

    cdb_record_t record[RECORD_SIZE];
    cdb_time_t time = 0;

    /* skip over any record that has NULL time or bad time values
       Such datapoints in cdb indicate a corrupted cdb. */
    while (!time || time <= 0) {

        if (_seek_to_logical_record(cdb, logical_record) < 0) {
            time = 0;
            break;
        }

        logical_record += 1;

        if (read(cdb->fd, &record, RECORD_SIZE) < 0) {
            time = 0;
            break;
        }

        time = record->time;
    }

    return time;
}

/* note - if no exact match, will return a record with a time greater than the requested value */
static int64_t _logical_record_for_time(cdb_t *cdb, cdb_time_t req_time, int64_t start_logical_record, int64_t end_logical_record) {

    bool first_time = false;
    cdb_time_t start_time, next_time, center_time;
    int64_t delta, center_logical_record, next_logical_record;

    uint64_t num_recs = cdb->header->num_records;

    /* for the very first time find start and end */
    if (start_logical_record == 0 && end_logical_record == 0) {

        start_logical_record = 0;
        end_logical_record   = num_recs - 1;

        first_time = true;
    }

    /* if no particular time was requested, just return the first one. */
    if (req_time == 0) {
        return start_logical_record;
    }

    /* if there are only 2 or 1 records in the search space, return the last record */
    if (end_logical_record - start_logical_record <= 1) {
        return end_logical_record;
    }

    start_time = _time_for_logical_record(cdb, start_logical_record);

    /* if requested time is less than start_time, start_time is the best we can do. */
    if (req_time <= start_time) {
        return start_logical_record;
    }

    /* don't go out of bounds */
    if (start_logical_record + 1 >= num_recs) {
        return start_logical_record;
    }

    next_logical_record = start_logical_record;
    next_time = start_time;

    /* if _seek_to_logical_record encounters bad data it fabricates and
       returns next good value. Try to get the *real* next rec. */
    while ((next_time - start_time) == 0) {

        next_logical_record += 1;

        next_time = _time_for_logical_record(cdb, next_logical_record);

        if (next_logical_record >= num_recs) {
            break;
        }
    }

    delta = next_time - start_time;

    /* delta = 0 means that _seek_to_logical_record fabricated data on the fly */
    if (delta == 0) {
        return start_logical_record;
    /* if we have wrapped over, or if the requested time is in the range of start and next, return start */
    } else if (delta < 0) {
        return start_logical_record;
    } else if (req_time <= next_time) {
        return next_logical_record;
    }

    /* for the very first binary search pivot point, take an aggressive guess to make search converge faster */
    if (first_time) {
        center_logical_record = (req_time - start_time) / (next_time - start_time) - 1;
    } else {
        center_logical_record = start_logical_record + (end_logical_record - start_logical_record) / 2;
    }

    if (num_recs > 0) {
        center_logical_record = (center_logical_record % num_recs);
    }

    center_time = _time_for_logical_record(cdb, center_logical_record);

    if (req_time >= center_time) {
        start_logical_record = center_logical_record;
    } else {
        end_logical_record = center_logical_record;
    }

    return _logical_record_for_time(cdb, req_time, start_logical_record, end_logical_record);
}

bool _cdb_is_writable(cdb_t *cdb) {

    /* We can't check for the O_RDONLY bit being because its defined value is zero.
     * i.e. there are no bits set to look for. We therefore assume
     * O_RDONLY if neither O_WRONLY nor O_RDWR are set. */
    if (cdb->flags & O_RDWR) {
        return true;
    }

    return false;
}

int cdb_read_header(cdb_t *cdb) {
    struct stat st;

    /* If the header has already been read from backing store do not read again */
    if (cdb->synced == true) {
        return CDB_SUCCESS;
    }

    if (cdb_open(cdb) != 0) {
        return cdb_error();
    }

    if (pread(cdb->fd, cdb->header, HEADER_SIZE, 0) != HEADER_SIZE) {
        return cdb_error();
    }

    if (strncmp(cdb->header->token, CDB_TOKEN, sizeof(CDB_TOKEN)) != 0) {
        return CDB_EBADTOK;
    }

    if (strncmp(cdb->header->version, CDB_VERSION, sizeof(CDB_VERSION)) != 0) {
        return CDB_EBADVER;
    }

    cdb->synced = true;

    /* Calculate the number of records */
    if (fstat(cdb->fd, &st) == 0) {
        cdb->header->num_records = (st.st_size - HEADER_SIZE) / RECORD_SIZE;
    } else {
        cdb->header->num_records = 0;
    }

    return CDB_SUCCESS;
}

int cdb_write_header(cdb_t *cdb) {

    if (cdb->synced) {
        return CDB_SUCCESS;
    }

    if (_cdb_is_writable(cdb) == false) {
        return CDB_ERDONLY;
    }

    cdb->synced = false;

    if (cdb_open(cdb) > 0) {
        return cdb_error();
    }

    if (pwrite(cdb->fd, cdb->header, HEADER_SIZE, 0) != HEADER_SIZE) {
        return cdb_error();
    }

    cdb->synced = true;

    return CDB_SUCCESS;
}

void cdb_print_header(cdb_t * cdb) {

    printf("version: [%s]\n", cdb->header->version);
    printf("name: [%s]\n", cdb->header->name);
    printf("desc: [%s]\n", cdb->header->desc);
    printf("units: [%s]\n", cdb->header->units);

    if (cdb->header->type == CDB_TYPE_COUNTER) {
        printf("type: [COUNTER]\n");
    }

    if (cdb->header->type == CDB_TYPE_GAUGE) {
        printf("type: [GAUGE]\n");
    }

    printf("min_value: [%g]\n", cdb->header->min_value);
    printf("max_value: [%g]\n", cdb->header->max_value);
    printf("max_records: [%"PRIu64"]\n", cdb->header->max_records);
    printf("num_records: [%"PRIu64"]\n", cdb->header->num_records);
    printf("start_record: [%"PRIu64"]\n", cdb->header->start_record);
}

int cdb_write_records(cdb_t *cdb, cdb_record_t *records, uint64_t len, uint64_t *num_recs) {

    /* read old header if it exists.
       write a header out, since the db may not have existed.
    */
    uint64_t i   = 0;
    uint64_t j   = 0;
    off_t offset = 0;
    *num_recs    = 0;

    if (cdb_read_header(cdb) != CDB_SUCCESS) {
        return cdb_error();
    }

    if (_cdb_is_writable(cdb) == false) {
        return CDB_ERDONLY;
    }

    if (cdb->header->max_records <= 0) {
        return CDB_EINVMAX;
    }

    /* Logic for writes:
    cdb is 5 records.
        try to write 7 records
        len = 7
        len + 0 > 5
        j = (7 + 0) - 5
        j = 2
        i = 7 - 2
        i = 5

        need to write j records at offset: HEADER_SIZE + (cdb->header->start_record * RECORD_SIZE)
        index into records is i

        need to write i records at offset: HEADER_SIZE + (cdb->header->num_records * RECORD_SIZE)
        index into records is 0;
    */

    /* Calculate our indicies into the records array. */
    if (len + cdb->header->num_records >= cdb->header->max_records) {
        j = (len + cdb->header->num_records) - cdb->header->max_records;
        i = (len - j);
    } else {
        i = len;
    }

    /* If we need to wrap around */
    if (j > 0) {

        offset = HEADER_SIZE + (cdb->header->start_record * RECORD_SIZE);

        cdb->header->start_record += j;
        cdb->header->start_record %= cdb->header->max_records;

        if (pwrite(cdb->fd, &records[i], (RECORD_SIZE * j), offset) != (RECORD_SIZE * j)) {
            return cdb_error();
        }

        cdb->synced = false;

        /* start_record is no longer 0, so update the header */
        if (cdb_write_header(cdb) != CDB_SUCCESS) {
            return cdb_error();
        }
    }

    /* Normal case */
    offset = HEADER_SIZE + (cdb->header->num_records * RECORD_SIZE);
    cdb->header->num_records += i;

    if (pwrite(cdb->fd, &records[0], (RECORD_SIZE * i), offset) != (RECORD_SIZE * i)) {
        return cdb_error();
    }

    *num_recs += len;

#ifdef DEBUG
    printf("write_records: wrote [%"PRIu64"] records\n", *num_recs);
#endif

    return CDB_SUCCESS;
}

bool cdb_write_record(cdb_t *cdb, cdb_time_t time, double value) {

    cdb_record_t record[RECORD_SIZE];
    uint64_t num_recs = 0;

    record->time  = time;
    record->value = value;

    if (cdb_write_records(cdb, record, 1, &num_recs) != CDB_SUCCESS) {
        return false;
    }

    return true;
}

int cdb_update_records(cdb_t *cdb, cdb_record_t *records, uint64_t len, uint64_t *num_recs) {

    int ret    = CDB_SUCCESS;
    *num_recs  = 0;
    uint64_t i = 0;

    if (cdb_read_header(cdb) != CDB_SUCCESS) {
        return cdb_error();
    }

    if (_cdb_is_writable(cdb) == false) {
        return CDB_ERDONLY;
    }

#ifdef DEBUG
    printf("in update_records with [%"PRIu64"] num_recs\n", cdb->header->num_records);
#endif

    for (i = 0; i < len; i++) {

        cdb_time_t time = records[i].time;
        cdb_time_t rtime;
        uint64_t lrec;

        lrec = _logical_record_for_time(cdb, time, 0, 0);

        if (lrec >= 1) {
            lrec -= 1;
            /* DRK things like this are extremely confusing. some logical
               functions are zero based? some are one? or you're trying to back up? */
        }

        rtime = _time_for_logical_record(cdb, lrec);

        while (rtime < time && lrec < cdb->header->num_records - 1) {

            /* DRK is this true? or is the condition (lrec - start_lrec) <
               num_recs -- seems you can't wrap here (i.e. what if the initial
               lrec was num_recs - 1) */

            lrec += 1;
            rtime = _time_for_logical_record(cdb, lrec);
        }

#ifdef DEBUG
        printf("update_records: value: lrec [%"PRIu64"] time [%d] rtime [%d]\n", lrec, (int)time, (int)rtime);
#endif

        while (time == rtime && lrec < cdb->header->num_records - 1) {

            _seek_to_logical_record(cdb, lrec);

            if (write(cdb->fd, &records[0], RECORD_SIZE) != RECORD_SIZE) {
                ret = cdb_error();
                break;
            }

            lrec += 1;

            rtime = _time_for_logical_record(cdb, lrec);
        }
    }

    if (ret == CDB_SUCCESS) {

        if (i > 0) {
            cdb->synced = false;
            *num_recs = i;
        }

        if (cdb_write_header(cdb) != CDB_SUCCESS) {
            ret = cdb_error();
        }
    }

    return ret;
}

bool cdb_update_record(cdb_t *cdb, cdb_time_t time, double value) {

    cdb_record_t record[RECORD_SIZE];
    uint64_t num_recs = 0;

    record->time  = time;
    record->value = value;

    if (cdb_update_records(cdb, record, 1, &num_recs) != 0) {
        return false;
    }

    return true;
}

int cdb_discard_records_in_time_range(cdb_t *cdb, cdb_request_t *request, uint64_t *num_recs) {

    uint64_t i = 0;
    int64_t lrec;
    off_t offset = RECORD_SIZE;
    *num_recs = 0;

    if (cdb_read_header(cdb) != CDB_SUCCESS) {
        return cdb_error();
    }

    if (_cdb_is_writable(cdb) == false) {
        return CDB_ERDONLY;
    }

    lrec = _logical_record_for_time(cdb, request->start, 0, 0);

    if (lrec >= 1) {
        lrec -= 1;
    }

    for (i = lrec; i < cdb->header->num_records; i++) {

        cdb_time_t rtime = _time_for_logical_record(cdb, i);

        if (rtime >= request->start && rtime <= request->end) {

            cdb_record_t record[RECORD_SIZE];

            record->time  = rtime;
            record->value = CDB_NAN;

            if (pwrite(cdb->fd, &record, RECORD_SIZE, offset) != RECORD_SIZE) {
                return cdb_error();
            }

            *num_recs += 1;
        }
    }

    if (*num_recs > 0) {
        cdb->synced = false;
    }

    if (cdb_write_header(cdb) != CDB_SUCCESS) {
        return cdb_error();
    }

    return CDB_SUCCESS;
}

static int _compute_scale_factor_and_num_records(cdb_t *cdb, int64_t *num_records, int32_t *factor) {

    if (cdb->header->type == CDB_TYPE_COUNTER) {
        if (*num_records != 0) {

            if (*num_records > 0) {
                *num_records += 1;
            } else {
                *num_records -= 1;
            }
        }
    }

    if (strlen(cdb->header->units) > 0) {

        int32_t multiplier = 1;
        char *frequency;

        if ((frequency = calloc(strlen(cdb->header->units), sizeof(char))) == NULL) {
            return CDB_ENOMEM;
        }

        if ((sscanf(cdb->header->units, "per %d %s", &multiplier, frequency) == 2) ||
            (sscanf(cdb->header->units, "per %s", frequency) == 1) ||
            (sscanf(cdb->header->units, "%*s per %s", frequency) == 1)) {

            if (strcmp(frequency, "min") == 0) {
                *factor = 60;
            } else if (strcmp(frequency, "hour") == 0) {
                *factor = 60 * 60;
            } else if (strcmp(frequency, "sec") == 0 || strcmp(frequency, "second") == 0) {
                *factor = 1;
            } else if (strcmp(frequency, "day") == 0) {
                *factor = 60 * 60 * 24;
            } else if (strcmp(frequency, "week") == 0) {
                *factor = 60 * 60 * 24 * 7;
            } else if (strcmp(frequency, "month") == 0) {
                *factor = 60 * 60 * 24 * 30;
            } else if (strcmp(frequency, "quarter") == 0) {
                *factor = 60 * 60 * 24 * 90;
            } else if (strcmp(frequency, "year") == 0) {
                *factor = 60 * 60 * 24 * 365;
            }

            if (*factor != 0) {
                *factor *= multiplier;
            }
        }

        free(frequency);
    }

    return CDB_SUCCESS;
}

/* Statistics code
 * Make only one call to reading for a particular time range and compute all our stats
 */
void _compute_statistics(cdb_range_t *range, uint64_t *num_recs, cdb_record_t *records) {

    uint64_t i     = 0;
    uint64_t valid = 0;
    double sum     = 0.0;
    double *values = calloc(*num_recs, sizeof(double));

    for (i = 0; i < *num_recs; i++) {

        if (!isnan(records[i].value)) {

            sum += values[valid] = records[i].value;
            valid++;
        }
    }

    range->num_recs = valid;
    range->mean     = gsl_stats_mean(values, 1, valid);
    range->max      = gsl_stats_max(values, 1, valid);
    range->min      = gsl_stats_min(values, 1, valid);
    range->sum      = sum;
    range->stddev   = gsl_stats_sd(values, 1, valid);
    range->absdev   = gsl_stats_absdev(values, 1, valid);

    /* The rest need sorted data */
    gsl_sort(values, 1, valid);

    range->median   = gsl_stats_median_from_sorted_data(values, 1, valid);
    range->pct95th  = gsl_stats_quantile_from_sorted_data(values, 1, valid, 0.95);
    range->pct75th  = gsl_stats_quantile_from_sorted_data(values, 1, valid, 0.75);
    range->pct50th  = gsl_stats_quantile_from_sorted_data(values, 1, valid, 0.50);
    range->pct25th  = gsl_stats_quantile_from_sorted_data(values, 1, valid, 0.25);

    /* MAD must come last because it alters the values array
     * http://en.wikipedia.org/wiki/Median_absolute_deviation */
    for (i = 0; i < valid; i++) {
        values[i] = fabs(values[i] - range->median);

        if (values[i] < 0.0) {
            values[i] *= -1.0;
        }
    }

    /* Final sort is required MAD */
    gsl_sort(values, 1, valid);
    range->mad = gsl_stats_median_from_sorted_data(values, 1, valid);

    free(values);
}

double cdb_get_statistic(cdb_range_t *range, cdb_statistics_enum_t type) {

    switch (type) {
        case CDB_MEDIAN:
            return range->median;
        case CDB_MAD:
            return range->mad;
        case CDB_95TH:
            return range->pct95th;
        case CDB_75TH:
            return range->pct75th;
        case CDB_50TH:
            return range->pct50th;
        case CDB_25TH:
            return range->pct25th;
        case CDB_MEAN:
            return range->mean;
        case CDB_SUM:
            return range->sum;
        case CDB_MAX:
            return range->max;
        case CDB_MIN:
            return range->min;
        case CDB_STDDEV:
            return range->stddev;
        case CDB_ABSDEV:
            return range->absdev;
        default:
            fprintf(stderr, "aggregate_using_function_for_records() function: [%d] not supported\n", type);
            return CDB_FAILURE;
    }
}

static int _cdb_read_records(cdb_t *cdb, cdb_request_t *request, uint64_t *num_recs, cdb_record_t **records) {

    int64_t first_requested_logical_record;
    int64_t last_requested_logical_record;
    uint64_t last_requested_physical_record;
    int64_t seek_physical_record;

    cdb_record_t *buffer = NULL;

    if (cdb_read_header(cdb) != CDB_SUCCESS) {
        return cdb_error();
    }

    if (request->start != 0 && request->end != 0 && request->end < request->start) {
        return CDB_ETMRANGE;
    }

    if (cdb->header == NULL || cdb->synced == false) {
        return CDB_ESANITY;
    }

    /* bail out if there are no records */
    if (cdb->header->num_records <= 0) {
        return CDB_ENORECS;
    }

    /*
      get the number of requested records:

      -ve indicates n records from the beginning
      +ve indicates n records off of the end.
      0 or undef means the whole thing.

      switch the meaning of -ve/+ve to be more array like
    */
    if (request->count != 0) {
        request->count = -request->count;
    }

#ifdef DEBUG
    printf("read_records start: [%ld]\n", request->start);
    printf("read_records end: [%ld]\n", request->end);
    printf("read_records num_requested: [%"PRIu64"]\n", request->count);
#endif

    if (request->count != 0 && request->count < 0 && request->start == 0) {
        /* if reading only few records from the end, just set -ve offset to seek to */
        first_requested_logical_record = request->count;

    } else {
        /* compute which record to start reading from the beginning, based on start time specified. */
        first_requested_logical_record = _logical_record_for_time(cdb, request->start, 0, 0);
    }

    /* if end is not defined, read all the records or only read uptill the specified record. */
    if (request->end == 0) {

        last_requested_logical_record = cdb->header->num_records - 1;

    } else {

        last_requested_logical_record = _logical_record_for_time(cdb, request->end, 0, 0);

        /* this can return something > end, check for that */
        if (_time_for_logical_record(cdb, last_requested_logical_record) > request->end) {
            last_requested_logical_record -= 1;
        }
    }

    last_requested_physical_record = (last_requested_logical_record + cdb->header->start_record) % cdb->header->num_records;

    /* After _seek_to_logical_record(), we're at the offset to read from. */
    seek_physical_record = _seek_to_logical_record(cdb, first_requested_logical_record);

    if (last_requested_physical_record >= seek_physical_record) {

        uint64_t nrec = (last_requested_physical_record - seek_physical_record + 1);
        uint64_t rlen = RECORD_SIZE * nrec;

        if ((buffer = calloc(1, rlen)) == NULL) {
            free(buffer);
            return CDB_ENOMEM;
        }

        if (read(cdb->fd, buffer, rlen) != rlen) {
            free(buffer);
            return cdb_error();
        }

        *num_recs = nrec;

    } else {

        /* We've wrapped around the end of the file */
        uint64_t nrec1 = (cdb->header->num_records - seek_physical_record);
        uint64_t nrec2 = (last_requested_physical_record + 1);

        uint64_t rlen1 = RECORD_SIZE * nrec1;
        uint64_t rlen2 = RECORD_SIZE * nrec2;

        if ((buffer = calloc(1, rlen1 + rlen2)) == NULL) {
            free(buffer);
            return CDB_ENOMEM;
        }

        /* Read at the offset set by _seek_to_logical_record() */
        if (read(cdb->fd, buffer, rlen1) != rlen1) {
            free(buffer);
            return cdb_error();
        }

        /* And then the wrap around portion past the header. */
        if (pread(cdb->fd, &buffer[nrec1], rlen2, HEADER_SIZE) != rlen2) {
            free(buffer);
            return cdb_error();
        }

        *num_recs = nrec1 + nrec2;
    }

    /* Deal with cooking the output */
    if (request->cooked) {

        bool check_min_max = true;
        int32_t factor = 0;
        uint64_t i = 0;
        uint64_t cooked_recs = 0;
        double prev_value = 0.0;
        cdb_time_t prev_date = 0;
        cdb_record_t *crecords;

        if ((crecords = calloc(*num_recs, RECORD_SIZE)) == NULL) {
            free(crecords);
            free(buffer);
            return CDB_ENOMEM;
        }

        if (_compute_scale_factor_and_num_records(cdb, &request->count, &factor)) {
            free(crecords);
            free(buffer);
            return cdb_error();
        }

        if (cdb->header->min_value == 0 && cdb->header->max_value == 0) {
            check_min_max = false;
        }

        for (i = 0; i < *num_recs; i++) {

            cdb_time_t date = buffer[i].time;
            double value    = buffer[i].value;

            if (cdb->header->type == CDB_TYPE_COUNTER) {
                double new_value = value;
                value = CDB_NAN;

                if (!isnan(prev_value) && !isnan(new_value)) {

                    double val_delta = new_value - prev_value;

                    if (val_delta >= 0) {
                        value = val_delta;
                    }
                }

                prev_value = new_value;
            }

            if (factor != 0 && cdb->header->type == CDB_TYPE_COUNTER) {

                /* Skip the first entry, since it's absolute and is needed
                 * to calculate the second */
                if (prev_date == 0) {
                    prev_date = date;
                    continue;
                }

                cdb_time_t time_delta = date - prev_date;

                if (time_delta > 0 && !isnan(value)) {
                    value = factor * (value / time_delta);
                }

                prev_date = date;
            }

            /* Check for min/max boundaries */
            /* Should this be done on write instead of read? */
            if (check_min_max && !isnan(value)) {
                if (value > cdb->header->max_value || value < cdb->header->min_value) {
                    value = CDB_NAN;
                }
            }

            /* Copy the munged data to our new array, since we might skip
             * elements. Also keep in mind mmap for the future */
            crecords[cooked_recs].time  = date;
            crecords[cooked_recs].value = value;
            cooked_recs += 1;
        }

        /* Now swap our cooked records for the buffer, so we can slice it as needed. */
        free(buffer);
        buffer = crecords;
        *num_recs = cooked_recs;
    }

    /* If we've been requested to average the records & timestamps */
    if (request->step > 1) {

        cdb_record_t *arecords;
        uint32_t step      = request->step;
        uint64_t step_recs = 0;
        uint64_t leftover  = (*num_recs % step);
        uint64_t walkend   = (*num_recs - leftover);
        uint64_t i = 0;

        if ((arecords = calloc(((*num_recs / step) + leftover), RECORD_SIZE)) == NULL) {
            free(arecords);
            free(buffer);
            return CDB_ENOMEM;
        }

        /* Walk our list of cooked records, jumping ahead by the given step.
           For each set of records within that step, we want to get the average
           for those records and place them into a new array.
         */
        for (i = 0; i < walkend; i += step) {

            uint64_t j = 0;
            double xi[step];
            double yi[step];

            for (j = 0; j < step; j++) {

                /* No NaNs on average - they cause bogus graphs. Is there a
                 * better value than 0 to use here? */
                if (isnan(buffer[i+j].value)) {
                    buffer[i+j].value = 0;
                }

                xi[j] = (double)buffer[i+j].time;
                yi[j] = buffer[i+j].value;
            }

            arecords[step_recs].time  = (cdb_time_t)gsl_stats_mean(xi, 1, step);
            arecords[step_recs].value = gsl_stats_mean(yi, 1, step);
            step_recs += 1;
        }

        /* Now collect from the last step point to the end & average. */
        if (leftover > 0) {
            uint64_t leftover_start = *num_recs - leftover;

            uint64_t j = 0;
            double xi[leftover];
            double yi[leftover];

            for (i = leftover_start; i < *num_recs; i++) {

                /* No NaNs on average - they cause bogus graphs. Is there a
                 * better value than 0 to use here? */
                if (isnan(buffer[i].value)) {
                    buffer[i].value = 0;
                }

                xi[j] = (double)buffer[i].time;
                yi[j] = buffer[i].value;
                j++;
            }

            arecords[step_recs].time  = (cdb_time_t)gsl_stats_mean(xi, 1, j);
            arecords[step_recs].value = gsl_stats_mean(yi, 1, j);
            step_recs += 1;
        }

        free(buffer);
        buffer = arecords;
        *num_recs = step_recs;
    }

    /* now pull out the number of requested records if asked */
    if (request->count != 0 && *num_recs >= abs(request->count)) {

        uint64_t start_index = 0;

        if (request->count <= 0) {
            start_index = *num_recs - abs(request->count);
        }

        *num_recs = abs(request->count);

        if ((*records  = calloc(*num_recs, RECORD_SIZE)) == NULL) {
            free(buffer);
            return CDB_ENOMEM;
        }

        memcpy(*records, &buffer[start_index], RECORD_SIZE * *num_recs);

        free(buffer);

    } else {

        *records = buffer;
    }

    return CDB_SUCCESS;
}

int cdb_read_records(cdb_t *cdb, cdb_request_t *request,
    uint64_t *num_recs, cdb_record_t **records, cdb_range_t *range) {

    int ret   = CDB_SUCCESS;

    ret = _cdb_read_records(cdb, request, num_recs, records);

    if (ret == CDB_SUCCESS) {

        if (*num_recs > 0) {
            range->start_time = request->start;
            range->end_time   = request->end;

            _compute_statistics(range, num_recs, *records);
        }
    }

    return ret;
}

void cdb_print_records(cdb_t *cdb, cdb_request_t *request, FILE *fh, const char *date_format) {

    uint64_t i = 0;
    uint64_t num_recs = 0;

    cdb_record_t *records = NULL;

    if (_cdb_read_records(cdb, request, &num_recs, &records) == CDB_SUCCESS) {

        for (i = 0; i < num_recs; i++) {

            _print_record(fh, records[i].time, records[i].value, date_format);
        }
    }

    free(records);
}

void cdb_print(cdb_t *cdb) {

    const char *date_format = "%Y-%m-%d %H:%M:%S";
    cdb_request_t request;

    request.start  = 0;
    request.end    = 0;
    request.count  = 0;
    request.step   = 0;
    request.cooked = false;

    printf("============== Header ================\n");

    if (cdb_read_header(cdb) == CDB_SUCCESS) {
        cdb_print_header(cdb);
    }

    if (cdb->header->type == CDB_TYPE_COUNTER) {

        printf("============= Raw Counter Records =============\n");
        cdb_print_records(cdb, &request, stdout, date_format);
        printf("============== End Raw Counter Records ==============\n");

        printf("============== Cooked Records ================\n");

    } else {
        printf("============== Records ================\n");
    }

    request.cooked = true;
    cdb_print_records(cdb, &request, stdout, date_format);

    printf("============== End ================\n");
}

/* Take in an array of cdbs */
int cdb_read_aggregate_records(cdb_t **cdbs, int num_cdbs, cdb_request_t *request,
    uint64_t *driver_num_recs, cdb_record_t **records, cdb_range_t *range) {

    uint64_t i = 0;
    int ret    = CDB_SUCCESS;
    cdb_record_t *driver_records = NULL;
    *driver_num_recs = 0;

    if (cdbs[0] == NULL) {
        return CDB_ESANITY;
    }

    /* The first cdb is the driver */
    ret = _cdb_read_records(cdbs[0], request, driver_num_recs, &driver_records);

    if (ret != CDB_SUCCESS) {
        fprintf(stderr, "Bailed on: %s\n", cdbs[0]->filename);
        free(driver_records);
        return ret;
    }

    if ((*records = calloc(*driver_num_recs, RECORD_SIZE)) == NULL) {
        free(driver_records);
        return CDB_ENOMEM;
    }

    if (*driver_num_recs <= 1) {
        free(driver_records);
        return CDB_EINTERPD;
    }

    double *driver_x_values   = calloc(*driver_num_recs, sizeof(double));
    double *driver_y_values   = calloc(*driver_num_recs, sizeof(double));
    double *follower_x_values = calloc(*driver_num_recs, sizeof(double));
    double *follower_y_values = calloc(*driver_num_recs, sizeof(double));

    for (i = 0; i < *driver_num_recs; i++) {
        (*records)[i].time  = driver_x_values[i] = driver_records[i].time;
        (*records)[i].value = driver_y_values[i] = driver_records[i].value;
    }

    /* initialize and allocate the gsl objects  */
    gsl_interp_accel *accel = gsl_interp_accel_alloc();
    gsl_interp *interp = gsl_interp_alloc(gsl_interp_linear, *driver_num_recs);

    /* Allows 0.0 to be returned as a valid yi */
    gsl_set_error_handler_off();

    gsl_interp_init(interp, driver_x_values, driver_y_values, *driver_num_recs);

    for (i = 1; i < num_cdbs; i++) {

        uint64_t j = 0;
        uint64_t follower_num_recs = 0;

        cdb_record_t *follower_records = NULL;

        ret = _cdb_read_records(cdbs[i], request, &follower_num_recs, &follower_records);

        /* Just bail, free all allocations below and let the error bubble up */
        if (ret == CDB_SUCCESS && follower_num_recs != 0) {

            for (j = 0; j < *driver_num_recs; j++) {

                /* Check for out of bounds */
                if (j >= follower_num_recs) {
                    break;
                }

                follower_x_values[j] = follower_records[j].time;
                follower_y_values[j] = follower_records[j].value;
            }

            for (j = 0; j < *driver_num_recs; j++) {

                double yi = gsl_interp_eval(interp, follower_x_values, follower_y_values, driver_x_values[j], accel);

                if (isnormal(yi)) {
                    (*records)[j].value += yi;
                }
            }
        }

        free(follower_records);

        if (ret != CDB_SUCCESS) {
            break;
        }
    }

    if (ret == CDB_SUCCESS && *driver_num_recs > 0) {
        /* Compute all the statistics for this range */
        range->start_time = request->start;
        range->end_time   = request->end;

        _compute_statistics(range, driver_num_recs, *records);
    }

    free(driver_x_values);
    free(driver_y_values);
    free(follower_x_values);
    free(follower_y_values);

    gsl_interp_free(interp);
    gsl_interp_accel_free(accel);
    free(driver_records);

    return ret;
}

void cdb_print_aggregate_records(cdb_t **cdbs, int32_t num_cdbs, cdb_request_t *request, FILE *fh, const char *date_format) {

    uint64_t i = 0;
    uint64_t num_recs = 0;

    cdb_record_t *records = NULL;
    cdb_range_t *range = calloc(1, sizeof(cdb_range_t));

    cdb_read_aggregate_records(cdbs, num_cdbs, request, &num_recs, &records, range);

    for (i = 0; i < num_recs; i++) {

        _print_record(fh, records[i].time, records[i].value, date_format);
    }

    free(range);
    free(records);
}

void cdb_generate_header(cdb_t *cdb, char* name, char* desc, uint64_t max_records, int32_t type,
    char* units, uint64_t min_value, uint64_t max_value) {

    if (max_records == 0) {
        max_records = CDB_DEFAULT_RECORDS;
    }

    if (type == 0) {
        cdb->header->type = CDB_DEFAULT_DATA_TYPE;
    }

    if (units == NULL || (strcmp(units, "") == 0)) {
        units = (char*)CDB_DEFAULT_DATA_UNIT;
    }

    if (desc == NULL) {
        desc = (char*)"";
    }

    memset(cdb->header->name, 0, sizeof(cdb->header->name));
    memset(cdb->header->desc, 0, sizeof(cdb->header->desc));
    memset(cdb->header->units, 0, sizeof(cdb->header->units));
    memset(cdb->header->version, 0, sizeof(cdb->header->version));
    memset(cdb->header->token, 0, sizeof(cdb->header->token));

    strncpy(cdb->header->name, name, sizeof(cdb->header->name));
    strncpy(cdb->header->desc, desc, sizeof(cdb->header->desc));
    strncpy(cdb->header->units, units, sizeof(cdb->header->units));
    strncpy(cdb->header->version, CDB_VERSION, sizeof(cdb->header->version));
    strncpy(cdb->header->token, CDB_TOKEN, sizeof(cdb->header->token));

    cdb->header->type         = type;
    cdb->header->max_records  = max_records;
    cdb->header->min_value    = min_value;
    cdb->header->max_value    = max_value;
    cdb->header->num_records  = 0;
    cdb->header->start_record = 0;
}

cdb_t* cdb_new(void) {

    cdb_t *cdb  = calloc(1, sizeof(cdb_t));
    cdb->header = calloc(1, HEADER_SIZE);

    cdb->fd = -1;
    cdb->synced = false;
    cdb->flags = -1;
    cdb->mode = -1;

    return cdb;
}

cdb_request_t cdb_new_request(void) {
    cdb_request_t request;
    memset (&request, 0, sizeof (request));
    request.start  = 0;
    request.end    = 0;
    request.count  = 0;
    request.cooked = true;
    request.step   = 0;
    return request;
}

int cdb_open(cdb_t *cdb) {

    if (cdb->fd >= 0) {
        return CDB_SUCCESS;
    }

    /* Default flags if none were set */
    if (cdb->flags == -1) {
        cdb->flags = O_RDONLY|O_BINARY;
    }

    /* A cdb can't be write only - we need to read the header */
    if (cdb->flags & O_WRONLY) {
        cdb->flags = O_RDWR;
    }

    if (cdb->mode == -1) {
        cdb->mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
    }

    cdb->fd = open(cdb->filename, cdb->flags, cdb->mode);

    if (cdb->fd < 0) {
        return cdb_error();
    }

    return CDB_SUCCESS;
}

int cdb_close(cdb_t *cdb) {

    if (cdb != NULL) {

        if (cdb->fd > 0) {
            if (close(cdb->fd) != 0) {
                return cdb_error();
            }
            cdb->fd = -1;
        }
    }

    return CDB_SUCCESS;
}

int cdb_free(cdb_t *cdb) {
    int ret = CDB_SUCCESS;

    if (cdb != NULL) {

        if (cdb->header != NULL) {
            ret = cdb_close(cdb);
            free(cdb->header);
            cdb->header = NULL;
        }

        free(cdb);
        cdb = NULL;
    }

    return ret;
}

/* -*- Mode: C; tab-width: 4 -*- */
/* vim: set tabstop=4 expandtab shiftwidth=4: */


/* ode-initval/control.c
 *
 * Copyright (C) 1996, 1997, 1998, 1999, 2000 Gerard Jungman
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or (at
 * your option) any later version.
 *
 * This program 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
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

/* Author:  G. Jungman */

#include <config.h>
#include <stdlib.h>
#include <gsl/gsl_math.h>
#include <gsl/gsl_errno.h>
#include "gsl_odeiv.h"

gsl_odeiv_control *
gsl_odeiv_control_alloc(const gsl_odeiv_control_type * T)
{
  gsl_odeiv_control * c =
    (gsl_odeiv_control *) malloc(sizeof(gsl_odeiv_control));

  if(c == 0)
    {
      GSL_ERROR_NULL ("failed to allocate space for control struct",
                      GSL_ENOMEM);
    };

  c->type = T;
  c->state = c->type->alloc();

  if (c->state == 0)
    {
      free (c);		/* exception in constructor, avoid memory leak */

      GSL_ERROR_NULL ("failed to allocate space for control state",
                      GSL_ENOMEM);
    };

  return c;
}

int
gsl_odeiv_control_init(gsl_odeiv_control * c,
                       double eps_abs, double eps_rel,
                       double a_y, double a_dydt)
{
  return c->type->init (c->state, eps_abs, eps_rel, a_y, a_dydt);
}

void
gsl_odeiv_control_free(gsl_odeiv_control * c)
{
  c->type->free(c->state);
  free(c);
}

const char *
gsl_odeiv_control_name(const gsl_odeiv_control * c)
{
  return c->type->name;
}

int
gsl_odeiv_control_hadjust (gsl_odeiv_control * c, gsl_odeiv_step * s, const double y0[], const double yerr[], const double dydt[], double * h)
{
  return c->type->hadjust(c->state, s->dimension, s->type->order(s->state),
                          y0, yerr, dydt, h);
}