/*-
 * Copyright (c) 2013-2015 Varnish Software AS
 * All rights reserved.
 *
 * Author: Poul-Henning Kamp <phk@FreeBSD.org>
 *
 * SPDX-License-Identifier: BSD-2-Clause
 *
 * 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 <stdlib.h>
#include <string.h>

#include "cache/cache.h"

#include "vcc_directors_if.h"

#include "vmod_directors.h"
#include "vsb.h"
#include "vbm.h"

struct vmod_directors_fallback {
	unsigned				magic;
#define VMOD_DIRECTORS_FALLBACK_MAGIC		0xad4e26ba
	struct vdir				*vd;
	VCL_BOOL				st;
	unsigned				cur;
};

static VCL_BOOL v_matchproto_(vdi_healthy)
vmod_fallback_healthy(VRT_CTX, VCL_BACKEND dir, VCL_TIME *changed)
{
	struct vmod_directors_fallback *fb;

	CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
	CHECK_OBJ_NOTNULL(dir, DIRECTOR_MAGIC);
	CAST_OBJ_NOTNULL(fb, dir->priv, VMOD_DIRECTORS_FALLBACK_MAGIC);
	return (vdir_any_healthy(ctx, fb->vd, changed));
}

static void v_matchproto_(vdi_list_f)
vmod_fallback_list(VRT_CTX, VCL_BACKEND dir, struct vsb *vsb, int pflag,
    int jflag)
{
	struct vmod_directors_fallback *fb;
	struct vdir *vd;
	VCL_BACKEND be;
	VCL_BOOL h;
	unsigned u, nh;

	CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
	CHECK_OBJ_NOTNULL(dir, DIRECTOR_MAGIC);
	CAST_OBJ_NOTNULL(fb, dir->priv, VMOD_DIRECTORS_FALLBACK_MAGIC);
	CAST_OBJ_NOTNULL(vd, fb->vd, VDIR_MAGIC);

	if (pflag) {
		if (jflag) {
			VSB_cat(vsb, "{\n");
			VSB_indent(vsb, 2);
			VSB_printf(vsb, "\"sticky\": %s,\n",
			    fb->st ? "true" : "false");
			VSB_cat(vsb, "\"backends\": {\n");
			VSB_indent(vsb, 2);
		} else {
			VSB_cat(vsb, "\n\n\tBackend\tCurrent\tHealth\n");
		}
	}

	vdir_rdlock(vd);
	vdir_update_health(ctx, vd);
	for (u = 0; pflag && u < vd->n_backend; u++) {
		be = vd->backend[u];
		CHECK_OBJ_NOTNULL(be, DIRECTOR_MAGIC);

		h = vbit_test(vd->healthy, u);

		if (jflag) {
			if (u)
				VSB_cat(vsb, ",\n");
			VSB_printf(vsb, "\"%s\": {\n", be->vcl_name);
			VSB_indent(vsb, 2);

			if (fb->cur == u)
				VSB_cat(vsb, "\"current\": true,\n");
			else
				VSB_cat(vsb, "\"current\": false,\n");

			if (h)
				VSB_cat(vsb, "\"health\": \"healthy\"\n");
			else
				VSB_cat(vsb, "\"health\": \"sick\"\n");

			VSB_indent(vsb, -2);
			VSB_cat(vsb, "}");
		} else {
			VSB_cat(vsb, "\t");
			VSB_cat(vsb, be->vcl_name);
			if (fb->cur == u)
				VSB_cat(vsb, "\t*\t");
			else
				VSB_cat(vsb, "\t\t");
			VSB_cat(vsb, h ? "healthy" : "sick");
			VSB_cat(vsb, "\n");
		}
	}
	nh = vd->n_healthy;
	u = vd->n_backend;
	vdir_unlock(vd);

	if (jflag && (pflag)) {
		VSB_cat(vsb, "\n");
		VSB_indent(vsb, -2);
		VSB_cat(vsb, "}\n");
		VSB_indent(vsb, -2);
		VSB_cat(vsb, "},\n");
	}

	if (pflag)
		return;

	if (jflag)
		VSB_printf(vsb, "[%u, %u, \"%s\"]", nh, u,
		    nh ? "healthy" : "sick");
	else
		VSB_printf(vsb, "%u/%u\t%s", nh, u, nh ? "healthy" : "sick");
}

static VCL_BACKEND v_matchproto_(vdi_resolve_f)
vmod_fallback_resolve(VRT_CTX, VCL_BACKEND dir)
{
	struct vmod_directors_fallback *fb;
	unsigned u;
	VCL_BACKEND be = NULL;

	CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
	CHECK_OBJ_NOTNULL(dir, DIRECTOR_MAGIC);
	CAST_OBJ_NOTNULL(fb, dir->priv, VMOD_DIRECTORS_FALLBACK_MAGIC);

	vdir_wrlock(fb->vd);
	if (!fb->st)
		fb->cur = 0;
	for (u = 0; u < fb->vd->n_backend; u++) {
		be = fb->vd->backend[fb->cur];
		CHECK_OBJ_NOTNULL(be, DIRECTOR_MAGIC);
		if (VRT_Healthy(ctx, be, NULL))
			break;
		if (++fb->cur == fb->vd->n_backend)
			fb->cur = 0;
	}
	vdir_unlock(fb->vd);
	if (u == fb->vd->n_backend)
		be = NULL;
	return (be);
}

static void v_matchproto_(vdi_destroy_f)
vmod_fallback_destroy(VCL_BACKEND dir)
{
	struct vmod_directors_fallback *fallback;

	CAST_OBJ_NOTNULL(fallback, dir->priv, VMOD_DIRECTORS_FALLBACK_MAGIC);
	vdir_delete(&fallback->vd);
	FREE_OBJ(fallback);
}

static const struct vdi_methods vmod_fallback_methods[1] = {{
	.magic =		VDI_METHODS_MAGIC,
	.type =			"fallback",
	.healthy =		vmod_fallback_healthy,
	.resolve =		vmod_fallback_resolve,
	.destroy =		vmod_fallback_destroy,
	.list =			vmod_fallback_list
}};


VCL_VOID v_matchproto_()
vmod_fallback__init(VRT_CTX,
    struct vmod_directors_fallback **fbp, const char *vcl_name, VCL_BOOL sticky)
{
	struct vmod_directors_fallback *fb;

	CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
	AN(fbp);
	AZ(*fbp);
	ALLOC_OBJ(fb, VMOD_DIRECTORS_FALLBACK_MAGIC);
	AN(fb);
	*fbp = fb;
	vdir_new(ctx, &fb->vd, vcl_name, vmod_fallback_methods, fb);
	fb->st = sticky;
}

VCL_VOID v_matchproto_()
vmod_fallback__fini(struct vmod_directors_fallback **fbp)
{
	struct vmod_directors_fallback *fb;

	TAKE_OBJ_NOTNULL(fb, fbp, VMOD_DIRECTORS_FALLBACK_MAGIC);
	VRT_DelDirector(&fb->vd->dir);
}

VCL_VOID v_matchproto_()
vmod_fallback_add_backend(VRT_CTX,
    struct vmod_directors_fallback *fb, VCL_BACKEND be)
{

	CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
	CHECK_OBJ_NOTNULL(fb, VMOD_DIRECTORS_FALLBACK_MAGIC);
	vdir_add_backend(ctx, fb->vd, be, 0.0);
}

VCL_VOID v_matchproto_()
vmod_fallback_remove_backend(VRT_CTX,
    struct vmod_directors_fallback *fb, VCL_BACKEND be)
{
	CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
	CHECK_OBJ_NOTNULL(fb, VMOD_DIRECTORS_FALLBACK_MAGIC);
	vdir_remove_backend(ctx, fb->vd, be, &fb->cur);
}

VCL_BACKEND v_matchproto_()
vmod_fallback_backend(VRT_CTX,
    struct vmod_directors_fallback *fb)
{
	CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
	CHECK_OBJ_NOTNULL(fb, VMOD_DIRECTORS_FALLBACK_MAGIC);
	return (fb->vd->dir);
}
