/*-
 * Copyright 2023 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.
 *
 * RVB: Re Vfp Buffer
 *
 * stay sane managing pointers on buffers
 *
 *
 * +---------------- (p)ointer: allocation start
 * |   +------------ (r)ead pointer
 * |   |   +-------- (w)rite pointer - new data here
 * |   |   |     +-- (l)imit: byte after buffer
 * p   r   w     l
 * v   v   v     v
 * ~~~~~~~~          returned from pull
 * ====xxxx------
 */

#include "config.h"

struct rvb {
	unsigned	magic;
#define RVB_MAGIC	0x1f6f0031
	unsigned	flags;
#define RVB_F_WR	1	// is writable
#define RVB_F_MALLOC	(1<<1)
#define RVB_F_STABLE	(1<<2)	// remaining across calls like VDP_NULL
#define RVB_F_END	(1<<3)
	union {
		struct {
			const char	*p;
			const char	*r;
			const char	*w;
			const char	*l;
		} ro;
		struct {
			char		*p;
			const char	*r;
			char		*w;
			const char	*l;
		} wr;
	} u;
};

static inline void
rvb_assert(const struct rvb *b)
{
	CHECK_OBJ_NOTNULL(b, RVB_MAGIC);
	assert(b->u.ro.p && b->u.ro.l);
	assert(b->u.ro.r >= b->u.ro.p);
	assert(b->u.ro.r <= b->u.ro.w);
	assert(b->u.ro.w >= b->u.ro.r);
	assert(b->u.ro.w <= b->u.ro.l);
}

static inline enum vfp_status
rvb_vfp_status(const struct rvb *b)
{
	rvb_assert(b);
	return ((b->flags & RVB_F_END) ? VFP_END : VFP_OK);
}

#define RVB_INIT(ROWR, b, p, len) do {					\
		AN(b);							\
		AN(p);							\
									\
		assert(!(b->u.ROWR.p || b->u.ROWR.r ||			\
		   b->u.ROWR.w || b->u.ROWR.l));			\
		assert(!(b->magic || b->flags));			\
									\
		b->magic = RVB_MAGIC;					\
		b->u.ROWR.p = p;					\
		b->u.ROWR.r = p;					\
		b->u.ROWR.w = p;					\
		b->u.ROWR.l = b->u.ROWR.r + len;			\
	} while(0)

// init for available data
static inline void
rvb_init_ro(struct rvb *b, const void *p, size_t len)
{
	RVB_INIT(ro, b, p, len);
	b->u.ro.w = b->u.ro.l;
}

static inline void
rvb_init_wr(struct rvb *b, void *p, size_t len)
{
	RVB_INIT(wr, b, p, len);
	b->flags |= RVB_F_WR;
}

static inline void
rvb_reset(struct rvb *b)
{

	rvb_assert(b);
	b->u.ro.r = b->u.ro.p;
	b->u.ro.w = b->u.ro.p;
}

static inline int
rvb_is(const struct rvb *b)
{
	return (b != NULL && b->magic != 0);
}

static void
rvb_fini(struct rvb *b)
{

	rvb_assert(b);
	if (b->flags & RVB_F_MALLOC)
		free(b->u.wr.p);
	memset(b, 0, sizeof *b);
}

static inline const char *
rvb_r(const struct rvb *b)
{
	return (b->u.ro.r);

}

/* read length available */
static inline size_t
rvb_rl(const struct rvb *b)
{
	assert(b->u.ro.w >= b->u.ro.r);
	return (size_t)(b->u.ro.w - b->u.ro.r);

}

/* write length available */
static inline size_t
rvb_wl(const struct rvb *b)
{
	assert(b->flags & RVB_F_WR);
	assert(b->u.wr.l >= b->u.wr.w);
	return (size_t)(b->u.wr.l - b->u.wr.w);
}

static inline char *
rvb_w(const struct rvb *b)
{
	assert(b->flags & RVB_F_WR);
	return (b->u.wr.w);
}

/* total size */
static inline size_t
rvb_size(const struct rvb *b)
{
	assert(b->u.ro.l >= b->u.ro.p);
	return (size_t)(b->u.ro.l - b->u.ro.p);
}

static void
rvb_grow(struct rvb *b, size_t l)
{
	ssize_t r_off, w_off;

	rvb_assert(b);
	assert(b->flags & RVB_F_MALLOC);
	assert(b->flags & RVB_F_WR);
	if (l == 0)
		l = rvb_size(b) << 1;
	else
		l += rvb_size(b);

	// prepare for relocation
	r_off = b->u.ro.r - b->u.ro.p;
	w_off = b->u.wr.w - b->u.wr.p;
	assert(r_off >= 0);
	assert(w_off >= 0);

	b->u.wr.p = realloc(b->u.wr.p, l);
	AN(b->u.ro.p);
	b->u.ro.l = b->u.ro.p + l;
	b->u.ro.r = b->u.ro.p + r_off;
	b->u.wr.w = b->u.wr.p + w_off;
}

static void
rvb_alloc(struct rvb *b, size_t l)
{
	memset(b, 0, sizeof *b);
	rvb_init_wr(b, malloc(l), l);
	memset(b->u.wr.p, 0, l);
	b->flags |= RVB_F_MALLOC;
}

static inline int
rvb_cangrow(const struct rvb *b)
{
	return (b->flags & RVB_F_MALLOC);
}


// mark read up to ->u.ro.w
static inline void
rvb_consume_all(struct rvb *b)
{
	rvb_assert(b);

	b->u.ro.r = b->u.ro.w;
}

static inline void
rvb_consume(struct rvb *b, size_t n)
{
	rvb_assert(b);
	b->u.ro.r += n;
	assert(b->u.ro.r <= b->u.ro.w);
}

// forget unread: reset w to r
static inline void
rvb_forget(struct rvb *b)
{
	rvb_assert(b);

	b->u.ro.w = (b->u.ro.p) + (b->u.ro.r - b->u.ro.p);
}

// append s to d
static inline void
rvb_append(struct rvb *d, struct rvb *s)
{

	rvb_assert(d);
	rvb_assert(s);
	AZ(d->flags & RVB_F_END);

	//lint -e{666}
	size_t l = vmin(rvb_wl(d), rvb_rl(s));
	memcpy(rvb_w(d), rvb_r(s), l);
	d->u.wr.w += l;
	s->u.ro.r += l;

	if (rvb_rl(s) == 0 && s->flags & RVB_F_END)
		d->flags |= RVB_F_END;
}

// transfer unread and END flag from s to d, grow if needed
// (so d must be MALLOC)
static inline void
rvb_transfer(struct rvb *d, struct rvb *s, size_t hint)
{
	size_t rl;

	rvb_assert(s);

	rl = rvb_rl(s);
	if (hint < rl)
		hint = rl;

	if (! rvb_is(d))
		rvb_alloc(d, hint);
	else if (rvb_wl(d) < rl)
		rvb_grow(d, hint - rvb_wl(d));
	else
		rvb_assert(d);

	AZ(d->flags & RVB_F_END);
	AN(d->flags & RVB_F_MALLOC);

	if (s->flags & RVB_F_END) {
		d->flags |= RVB_F_END;
		s->flags &= ~RVB_F_END;
	}

	assert(rvb_wl(d) >= rl);
	memcpy(rvb_w(d), rvb_r(s), rl);
	d->u.wr.w += rl;
	rvb_forget(s);
}

// d references the first n bytes of s
static inline void
rvb_ref_prefix(struct rvb *d, const struct rvb *s, size_t n)
{
	AZ(rvb_is(d));
	rvb_assert(s);

	*d = *s;
	d->flags = s->flags & RVB_F_STABLE;
	d->u.ro.w = d->u.ro.r + n;
}

// suck into an rvb
static enum vfp_status
rvb_suck(struct rvb *b, struct vfp_ctx *vc)
{
	enum vfp_status r;
	ssize_t l;

	rvb_assert(b);
	l = (ssize_t)rvb_wl(b);
	AN(l);

	r = VFP_Suck(vc, rvb_w(b), &l);
	b->u.wr.w += l;

	if (r == VFP_END)
		b->flags |= RVB_F_END;
	rvb_assert(b);

	return (r);
}

// return from vfp
static enum vfp_status
rvb_ret(const struct rvb *b, const void *p, ssize_t *lp)
{
	rvb_assert(b);
	AZ(b->flags & RVB_F_MALLOC);
	assert(b->u.ro.p == p);
	*lp = b->u.ro.w - b->u.ro.p;
	return (rvb_vfp_status(b));
}

/* put as much as possible to d.
 * if space on d is insufficient and it is malloc'ed,
 * grow it
 */
static inline void
rvb_put(struct rvb *d, const char **p, size_t *lp)
{
	size_t l, ll;

	rvb_assert(d);
	AN(p);
	AN(*p);
	AN(lp);
	l = *lp;

	if (l == 0)
		return;

	ll = rvb_wl(d);
	if (l > ll && rvb_cangrow(d)) {
		rvb_grow(d, l);
		ll = rvb_wl(d);
		assert(l <= ll);
	}
	else if (l > ll)
		l = ll;

	memcpy(rvb_w(d), *p, l);
	d->u.wr.w += l;
	rvb_assert(d);

	(*p) += l;
	*lp -= l;
}

/* put as much as possible to d, and the rest to (o)verrun.
 * o will be alloc'ed if necessary, if already alloc'ed, it
 * must be malloc
 */

static void
rvb_puto(struct rvb *d, struct rvb *o, const char *p, size_t l)
{

	AN(d);
	AN(o);
	AN(p);

	if (l == 0)
		return;

	rvb_put(d, &p, &l);
	if (l == 0)
		return;

	if (rvb_is(o))
		assert(rvb_cangrow(o));
	else
		rvb_alloc(o, l);
	rvb_put(o, &p, &l);
	AZ(l);
}

static inline void
rvb_take(struct rvb *d, struct rvb *s)
{

	assert(! rvb_is(d));
	*d = *s;
	memset(s, 0, sizeof *s);
}

/* unclear: would it pay off to only memmove if the p..r distance
 * is above a certain value?
 *
 * my guess is: no, because branch prediction and because caches.
 *
 * but this would need a benchmark.
 *
 */

static void
rvb_compact(struct rvb *b)
{
	size_t rl;

	rvb_assert(b);
	AN(b->flags & RVB_F_WR);

	rl = rvb_rl(b);
	if (rl == 0)
		return;

	memmove(b->u.wr.p, rvb_r(b), rl);
	b->u.wr.r = b->u.wr.p;
	b->u.wr.w = b->u.wr.p + rl;
}

static int
rvb_vdp_bytes(struct rvb *b, struct vdp_ctx *vdx)
{
	enum vdp_action act;
	int r;

	if (b->flags & RVB_F_END)
		act = VDP_END;
	else if (b->flags & RVB_F_STABLE)
		act = VDP_NULL;
	else
		act = VDP_FLUSH;

	r = VDP_bytes(vdx, act, rvb_r(b), (ssize_t)rvb_rl(b));
	rvb_fini(b);
	return (r);
}
