/*
 * Copyright 2023 Vsevolod Stakhov
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * 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.
 */

/*
 * Copyright (c) 2014, Vsevolod Stakhov
 *
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *	 * Redistributions of source code must retain the above copyright
 *	   notice, this list of conditions and the following disclaimer.
 *	 * 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 AUTHOR ''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 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.
 */

#ifndef UPSTREAM_H_
#define UPSTREAM_H_

#include <time.h>
#include <stdio.h>

/**
 * @file upstream.h
 * The basic macros to define upstream objects
 */

#ifndef upstream_fatal
#define upstream_fatal(msg) \
	do {                    \
		perror(msg);        \
		exit(-1);           \
	} while (0)
#endif

#ifndef upstream_malloc
#define upstream_malloc(size) malloc(size)
#endif

#ifndef upstream_free
#define upstream_free(size, ptr) free(ptr)
#endif

struct upstream_entry_s;
struct upstream_common_data {
	void **upstreams;
	unsigned int allocated_nelts;
	unsigned int nelts;
	unsigned int alive;
};

typedef struct upstream_entry_s {
	unsigned short errors; /**< errors for this upstream 	*/
	unsigned short dead;
	unsigned short priority;
	unsigned short weight;
	time_t time;                         /**< time of marking 			*/
	void *parent;                        /**< parent object				*/
	struct upstream_common_data *common; /**< common data				*/
	void *next;                          /**< link to the next			*/
} upstream_entry_t;

/*
 * Here we define some reasonable defaults:
 * if an upstream has more than `UPSTREAM_MAX_ERRORS` in the period of time
 * of `UPSTREAM_ERROR_TIME` then we shut it down for `UPSTREAM_REVIVE_TIME`.
 * In this particular case times are 10 seconds for 10 errors and revive in
 * 30 seconds.
 */
#ifndef UPSTREAM_REVIVE_TIME
#define UPSTREAM_REVIVE_TIME 30
#endif
#ifndef UPSTREAM_ERROR_TIME
#define UPSTREAM_ERROR_TIME 10
#endif
#ifndef UPSTREAM_MAX_ERRORS
#define UPSTREAM_MAX_ERRORS 10
#endif

#define UPSTREAM_FAIL(u, now)                                  \
	do {                                                       \
		if ((u)->up.time != 0) {                               \
			if ((now) - (u)->up.time >= UPSTREAM_ERROR_TIME) { \
				if ((u)->up.errors >= UPSTREAM_MAX_ERRORS) {   \
					(u)->up.dead = 1;                          \
					(u)->up.time = now;                        \
					(u)->up.common->alive--;                   \
				}                                              \
				else {                                         \
					(u)->up.errors = 1;                        \
					(u)->up.time = (now);                      \
				}                                              \
			}                                                  \
			else {                                             \
				(u)->up.errors++;                              \
			}                                                  \
		}                                                      \
		else {                                                 \
			(u)->up.errors++;                                  \
			(u)->up.time = (now);                              \
		}                                                      \
	} while (0)

#define UPSTREAM_OK(u)      \
	do {                    \
		(u)->up.errors = 0; \
		(u)->up.time = 0;   \
	} while (0)

#define UPSTREAM_ADD(head, u, priority)                                   \
	do {                                                                  \
		if (head == NULL) {                                               \
			struct upstream_common_data *cd;                              \
			cd = upstream_malloc(sizeof(struct upstream_common_data));    \
			if (cd == NULL) {                                             \
				upstream_fatal("malloc failed");                          \
			}                                                             \
			cd->upstreams = upstream_malloc(sizeof(void *) * 8);          \
			if (cd == NULL) {                                             \
				upstream_fatal("malloc failed");                          \
			}                                                             \
			cd->allocated_nelts = 8;                                      \
			cd->nelts = 1;                                                \
			cd->alive = 1;                                                \
			cd->upstreams[0] = (u);                                       \
			(u)->up.common = cd;                                          \
		}                                                                 \
		else {                                                            \
			struct upstream_common_data *cd = (head)->up.common;          \
			(u)->up.common = cd;                                          \
			if (cd->nelts == cd->allocated_nelts) {                       \
				void **nup;                                               \
				nup = upstream_malloc(sizeof(void *) * cd->nelts * 2);    \
				if (nup == NULL) {                                        \
					upstream_fatal("malloc failed");                      \
				}                                                         \
				memcpy(nup, cd->upstreams, cd->nelts * sizeof(void *));   \
				upstream_free(cd->nelts * sizeof(void *), cd->upstreams); \
				cd->upstreams = nup;                                      \
				cd->allocated_nelts *= 2;                                 \
			}                                                             \
			cd->upstreams[cd->nelts++] = (u);                             \
			cd->alive++;                                                  \
		}                                                                 \
		(u)->up.next = (head);                                            \
		(head) = (u);                                                     \
		if (priority > 0) {                                               \
			(u)->up.priority = (u)->up.weight = (priority);               \
		}                                                                 \
		else {                                                            \
			(u)->up.priority = (u)->up.weight = 65535;                    \
		}                                                                 \
		(u)->up.time = 0;                                                 \
		(u)->up.errors = 0;                                               \
		(u)->up.dead = 0;                                                 \
		(u)->up.parent = (u);                                             \
	} while (0)

#define UPSTREAM_DEL(head, u)                                           \
	do {                                                                \
		if (head != NULL) {                                             \
			struct upstream_common_data *cd = (head)->up.common;        \
			if ((u)->up.next != NULL) {                                 \
				(head) = (u)->up.next;                                  \
				cd->nelts--;                                            \
				cd->alive--;                                            \
			}                                                           \
			else {                                                      \
				upstream_free(cd->allocated_nelts * sizeof(void *),     \
							  cd->upstreams);                           \
				upstream_free(sizeof(struct upstream_common_data), cd); \
				(head) = NULL;                                          \
			}                                                           \
		}                                                               \
	} while (0)

#define UPSTREAM_FOREACH(head, u) for ((u) = (head); (u) != NULL; (u) = (u)->up.next)
#define UPSTREAM_FOREACH_SAFE(head, u, tmp)             \
	for ((u) = (head);                                  \
		 (u) != NULL && ((tmp = (u)->up.next) || true); \
		 (u) = (tmp))

#define UPSTREAM_REVIVE_ALL(head)                            \
	do {                                                     \
		__typeof(head) elt = (head);                         \
		while (elt != NULL) {                                \
			elt->up.dead = 0;                                \
			elt->up.errors = 0;                              \
			elt->up.time = 0;                                \
			elt = elt->up.next;                              \
		}                                                    \
		(head)->up.common->alive = (head)->up.common->nelts; \
	} while (0)

#define UPSTREAM_RESCAN(head, now)                                     \
	do {                                                               \
		__typeof(head) elt = (head);                                   \
		if ((head)->up.common->alive == 0) {                           \
			UPSTREAM_REVIVE_ALL((head));                               \
		}                                                              \
		else {                                                         \
			while (elt != NULL) {                                      \
				if (elt->up.dead) {                                    \
					if ((now) -elt->up.time >= UPSTREAM_REVIVE_TIME) { \
						elt->up.dead = 0;                              \
						elt->up.errors = 0;                            \
						elt->up.weight = elt->up.priority;             \
						(head)->up.common->alive++;                    \
					}                                                  \
				}                                                      \
				else {                                                 \
					if ((now) -elt->up.time >= UPSTREAM_ERROR_TIME &&  \
						elt->up.errors >= UPSTREAM_MAX_ERRORS) {       \
						elt->up.dead = 1;                              \
						elt->up.time = now;                            \
						(head)->up.common->alive--;                    \
					}                                                  \
				}                                                      \
				elt = elt->up.next;                                    \
			}                                                          \
		}                                                              \
	} while (0)

#define UPSTREAM_SELECT_ROUND_ROBIN(head, selected)      \
	do {                                                 \
		__typeof(head) elt = (head);                     \
		(selected) = NULL;                               \
		unsigned max_weight = 0;                         \
		if ((head)->up.common->alive == 0) {             \
			UPSTREAM_REVIVE_ALL(head);                   \
		}                                                \
		while (elt != NULL) {                            \
			if (!elt->up.dead) {                         \
				if (elt->up.weight > max_weight) {       \
					max_weight = elt->up.weight;         \
					(selected) = elt;                    \
				}                                        \
			}                                            \
			elt = elt->up.next;                          \
		}                                                \
		if (max_weight == 0) {                           \
			elt = (head);                                \
			while (elt != NULL) {                        \
				elt->up.weight = elt->up.priority;       \
				if (!elt->up.dead) {                     \
					if (elt->up.priority > max_weight) { \
						max_weight = elt->up.priority;   \
						(selected) = elt;                \
					}                                    \
				}                                        \
				elt = elt->up.next;                      \
			}                                            \
		}                                                \
		(selected)->up.weight--;                         \
	} while (0)

#endif /* UPSTREAM_H_ */
