/*-
 * Copyright 2018 UPLEX Nils Goroll Systemoptimierung
 * All rights reserved.
 *
 * Author: Nils Goroll <nils.goroll@uplex.de>
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include "config.h"

#include <limits.h>
#include <pthread.h>
#include <string.h>
#include <stdlib.h>

#include <cache/cache.h>
#include <vcl.h>

#include <sys/types.h>
#include <sys/socket.h>
#include <vsa.h>

#include "vcc_taskvar_if.h"
#include "type_magics.h"
#include "type_cp.h"
#include "compat.h"
#include "tasktop.h"

#define TASK_COMMON(ctx, t, v)				\
	do {						\
		if (ctx->method & VCL_MET_TASK_H)	\
			return (v);			\
							\
		t = VRT_priv_task(ctx, v);		\
							\
		if (t == NULL) {			\
			VRT_fail(ctx, "no priv_task");	\
			return (NULL);			\
		}					\
							\
		if (t->priv)				\
			return (t->priv);		\
	} while(0)

static const void *
state_r(VRT_CTX, void *v)
{
	struct vmod_priv *t;

	TASK_COMMON(ctx, t, v);

	return (v);
}

static void *
state_l(VRT_CTX, void *v, size_t sz)
{
	struct vmod_priv *t;

	TASK_COMMON(ctx, t, v);

	t->priv = WS_Alloc(ctx->ws, sz);
	if (t->priv == NULL) {
		VRT_fail(ctx, "out of workspace");
		return (NULL);
	}
	memcpy(t->priv, v, sz);

	return (t->priv);
}

#define tv_magic	0x1000

#define VMOD_TASKVAR_ACL_MAGIC		(tv_magic | OBJVAR_ACL_MAGIC)
#define VMOD_TASKVAR_BACKEND_MAGIC	(tv_magic | OBJVAR_BACKEND_MAGIC)
#define VMOD_TASKVAR_BLOB_MAGIC	(tv_magic | OBJVAR_BLOB_MAGIC)
#define VMOD_TASKVAR_BODY_MAGIC	(tv_magic | OBJVAR_BODY_MAGIC)
#define VMOD_TASKVAR_BOOL_MAGIC	(tv_magic | OBJVAR_BOOL_MAGIC)
#define VMOD_TASKVAR_BYTES_MAGIC	(tv_magic | OBJVAR_BYTES_MAGIC)
#define VMOD_TASKVAR_DURATION_MAGIC	(tv_magic | OBJVAR_DURATION_MAGIC)
#define VMOD_TASKVAR_HEADER_MAGIC	(tv_magic | OBJVAR_HEADER_MAGIC)
#define VMOD_TASKVAR_INT_MAGIC		(tv_magic | OBJVAR_INT_MAGIC)
#define VMOD_TASKVAR_IP_MAGIC		(tv_magic | OBJVAR_IP_MAGIC)
#define VMOD_TASKVAR_PROBE_MAGIC	(tv_magic | OBJVAR_PROBE_MAGIC)
#define VMOD_TASKVAR_REAL_MAGIC	(tv_magic | OBJVAR_REAL_MAGIC)
#define VMOD_TASKVAR_STEVEDORE_MAGIC	(tv_magic | OBJVAR_STEVEDORE_MAGIC)
#define VMOD_TASKVAR_STRING_MAGIC	(tv_magic | OBJVAR_STRING_MAGIC)
#define VMOD_TASKVAR_TIME_MAGIC	(tv_magic | OBJVAR_TIME_MAGIC)

#define VCC_TYPE(TYPE, type) var_code(vmod_, VMOD_TASKVAR_, vmod_taskvar_, TYPE, type)
#include "tbl_types.h"

VCL_VOID
vmod_string_foreach(VRT_CTX, struct VPFX(taskvar_string) *va,
    VCL_STRING s, VCL_SUB sub, VCL_STRING delim)
{
	char *p, *str, *save;
	struct vmod_taskvar_string *v =
	    state_l(ctx, va, sizeof *v);

	if (v == NULL || s == NULL)
		return;

	CHECK_OBJ(v, VMOD_TASKVAR_STRING_MAGIC);

	if (v->protected) {
		VRT_fail(ctx,
		    "attempt to set protected variable");
		return;
	}

	str = WS_Copy(ctx->ws, s, -1);
	if (str == NULL) {
		VRT_fail(ctx, ".foreach() out of workspace");
		return;
	}
	v->defined = 1;
	p = strtok_r(str, delim, &save);
	while (p != NULL) {
		v->var = p;
		VRT_call(ctx, sub);
		if (VRT_handled(ctx))
			break;
		p = strtok_r(NULL, delim, &save);
	}
	v->var = NULL;
	v->defined = 0;
}

/*
struct VPFX(taskvar_strands);

struct VARGS(strands__init) {
	char			valid_joiner;
	char			valid_prefix;
	char			valid_postfix;
	VCL_INT			cap;
	VCL_STRING		joiner;
	VCL_STRING		prefix;
	VCL_STRING		postfix;


struct strands {
        unsigned        magic;
#define STRANDS_MAGIC   0x5d5ab196
        int             n;
        const char      **p;
};

};

*/

struct VPFX(taskvar_strands) {
	unsigned			magic;
#define VMOD_TASKVAR_STRANDS_MAGIC	0x42eefabc
	unsigned			add;		// how many to add
	char				*name;
	const char			*joiner;
	const char			*prefix;
	const char			*postfix;
	unsigned			ws_cap;		// elements for heap
	pthread_mutex_t			heap_mtx;	// protects heap_pow
	unsigned			heap_pow;	// 2^n bytes on heap
};

#define STR_FMT "new %s = taskvar.strands(...): "

VCL_VOID
vmod_strands__init(VRT_CTX, struct VPFX(taskvar_strands) **vp, const char *name,
    struct VARGS(strands__init) *va)
{
	const unsigned cap_max = UINT_MAX / 4;
	struct VPFX(taskvar_strands) *v;

	AN(vp);
	AZ(*vp);

	AN(va);
	if (va->cap < 0) {
		VRT_fail(ctx, STR_FMT "cap can not be negative", name);
		return;
	}
	if (va->cap > cap_max) {
		// could limit to reasonable fraction of workspace
		VRT_fail(ctx, STR_FMT "cap = %jd too big, maximum is %u",
		    name, (intmax_t)va->cap, cap_max);
		return;
	}
	ALLOC_OBJ(v, VMOD_TASKVAR_STRANDS_MAGIC);
	if (v == NULL) {
		VRT_fail(ctx, STR_FMT "alloc failed", name);
		return;
	}
	v->add = va->valid_joiner + va->valid_prefix + va->valid_postfix;
	assert(v->add <= 3);
	REPLACE(v->name, name);
	if (va->joiner && *va->joiner)
		v->joiner = va->joiner;
	if (va->prefix && *va->prefix)
		v->prefix = va->prefix;
	if (va->postfix && *va->postfix)
		v->postfix = va->postfix;

	if (va->cap == 0)
		v->heap_pow = 6;	// 64 byte, any good?
	else {
		v->ws_cap = (unsigned)va->cap * (1 + v->add);
	}

	AZ(pthread_mutex_init(&v->heap_mtx, NULL));

	*vp = v;
}
#undef STR_FMT

VCL_VOID
vmod_strands__fini(struct VPFX(taskvar_strands) **vp)
{
	struct VPFX(taskvar_strands) *v = *vp;

	*vp = NULL;
	if (v == NULL)
		return;

	CHECK_OBJ(v, VMOD_TASKVAR_STRANDS_MAGIC);
	AZ(pthread_mutex_destroy(&v->heap_mtx));
	REPLACE(v->name, NULL);
	FREE_OBJ(v);
}

// the strands always lives on the workspace
// only the p array either lives on the workspace or heap
struct task_strands {
	unsigned			magic;
#define VMOD_TASKVAR_STRANDS_TASK_MAGIC	0xea609833
	unsigned			cap;	// == vcountof(strands.p)
	struct strands			strands;
};

static void
task_strands_heap_free(VRT_CTX, void *p)
{
	struct task_strands *ts;

	(void)ctx;
	CAST_OBJ_NOTNULL(ts, p, VMOD_TASKVAR_STRANDS_TASK_MAGIC);
	free(ts->strands.p);
}

const struct vmod_priv_methods task_strands_heap_methods[1] = {{
	.magic = VMOD_PRIV_METHODS_MAGIC,
	.type = "task_strands_heap",
	.fini = task_strands_heap_free
}};

static struct task_strands *
task_strands(VRT_CTX, struct VPFX(taskvar_strands) *v)
{
	struct vmod_priv *priv;
	struct task_strands *ts;

	priv = VRT_priv_task(ctx, v);
	if (priv == NULL)
		return NULL;

	if (priv->priv != NULL) {
		CAST_OBJ_NOTNULL(ts, priv->priv, VMOD_TASKVAR_STRANDS_TASK_MAGIC);
		CHECK_OBJ(&ts->strands, STRANDS_MAGIC);
		return (ts);
	}
	if (v->heap_pow)
		priv->methods = task_strands_heap_methods;

	WS_TASK_ALLOC_OBJ(ctx, ts, VMOD_TASKVAR_STRANDS_TASK_MAGIC);
	if (ts == NULL)
		return NULL;
	ts->strands.magic = STRANDS_MAGIC;
	priv->priv = ts;

	if (v->ws_cap) {
		ts->strands.p = WS_Alloc(ctx->ws, v->ws_cap * sizeof(*ts->strands.p));
		if (ts->strands.p == NULL)
			return (NULL);
		ts->cap = v->ws_cap;
	}

	return (ts);
}

#define STR_FMT "%s.add(...): "

VCL_VOID
vmod_strands_add(VRT_CTX, struct VPFX(taskvar_strands) *v, VCL_STRANDS s)
{
	struct task_strands *ts;
	unsigned need;

	CHECK_OBJ_NOTNULL(v, VMOD_TASKVAR_STRANDS_MAGIC);
	AN(s);
	if (s->n == 0)
		return;
	AN(s->p);

	ts = task_strands(ctx, v);
	if (ts == NULL) {
		VRT_fail(ctx, STR_FMT "failed to get PRIV_TASK and/or cap alloc", v->name);
		return;
	}

#define TS_HAVE(ts) ((ts)->cap - (ts)->strands.n)
	assert(ts->cap >= ts->strands.n);
	need = s->n + v->add;

      	if (TS_HAVE(ts) < need && v->ws_cap) {
		VRT_fail(ctx, STR_FMT "Insufficient capacity %u need %u or more",
		    v->name, ts->cap, ts->cap + need);
		return;
	}
	while (TS_HAVE(ts) < need) {
		size_t sz;

		// heap_pow is shared for the vcl, so once we determine the
		// right amount of space, we will use it for all initial
		// allocations
		AZ(pthread_mutex_lock(&v->heap_mtx));
		AN(v->heap_pow);
		sz = (size_t)1 << v->heap_pow;
		if (ts->cap == sz / sizeof(*ts->strands.p)) {
			// if same as last time, need to grow
			v->heap_pow++;
			sz <<= 1;
		}
		AZ(pthread_mutex_unlock(&v->heap_mtx));

		ts->strands.p = realloc(ts->strands.p, sz);
		if (ts->strands.p == NULL) {
			VRT_fail(ctx, STR_FMT "heap realloc failed", v->name);
			return;
		}
		ts->cap = sz / sizeof(*ts->strands.p);
		//VSLb(ctx->vsl, SLT_Debug, "realloced %zu cap=%u", sz, ts->cap);
	}
      	assert(TS_HAVE(ts) >= need);
#undef TS_HAVE

#define S_ADD(s, str) (s)->p[(s)->n++] = (str)

	struct strands *d = &ts->strands;

	if (d->n && v->joiner)
		S_ADD(d, v->joiner);
	if (v->prefix)
		S_ADD(d, v->prefix);
	for (const char **p = s->p; p < s->p + s->n; p++) {
      		if (!*p || !**p)
      			continue;
		S_ADD(d, *p);
	}
	if (v->postfix)
		S_ADD(d, v->postfix);
}

static struct task_strands *
task_strands_get(VRT_CTX, struct VPFX(taskvar_strands) *v)
{
	struct vmod_priv *priv;
	struct task_strands *ts;

	priv = VRT_priv_task_get(ctx, v);
	if (priv == NULL || priv->priv == NULL)
		return NULL;

	CAST_OBJ_NOTNULL(ts, priv->priv, VMOD_TASKVAR_STRANDS_TASK_MAGIC);
	CHECK_OBJ(&ts->strands, STRANDS_MAGIC);
	return (ts);
}

VCL_VOID
vmod_strands_clear(VRT_CTX, struct VPFX(taskvar_strands) *v)
{
	struct task_strands *ts = task_strands_get(ctx, v);
	if (ts == NULL || ts->strands.n == 0)
		return;
	ts->strands.n = 0;
}

VCL_STRANDS
vmod_strands_get(VRT_CTX, struct VPFX(taskvar_strands) *v)
{
	struct task_strands *ts = task_strands_get(ctx, v);
	if (ts == NULL || ts->strands.n == 0)
		return vrt_null_strands;
	return (&ts->strands);
}
