/*
// Copyright (c) 2010-2017 Intel Corporation
//
// 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.
*/

#include "prox_malloc.h"
#include "lconf.h"
#include "rx_pkt.h"
#include "tx_pkt.h"
#include "log.h"
#include "quit.h"
#include "prox_cfg.h"

struct lcore_cfg *lcore_cfg;
/* only used at initialization time */
struct lcore_cfg  lcore_cfg_init[RTE_MAX_LCORE];

static int core_targ_next_from(struct lcore_cfg **lconf, struct task_args **targ, struct lcore_cfg *lcore_cfg, const int with_master)
{
	uint32_t lcore_id, task_id;

	if (*lconf && *targ) {
		lcore_id = *lconf - lcore_cfg;
		task_id = *targ - lcore_cfg[lcore_id].targs;

		if (task_id + 1 < lcore_cfg[lcore_id].n_tasks_all) {
			*targ = &lcore_cfg[lcore_id].targs[task_id + 1];
			return 0;
		} else {
			if (prox_core_next(&lcore_id, with_master))
				return -1;
			*lconf = &lcore_cfg[lcore_id];
			*targ = &lcore_cfg[lcore_id].targs[0];
			return 0;
		}
	} else {
		lcore_id = -1;

		if (prox_core_next(&lcore_id, with_master))
			return -1;
		*lconf = &lcore_cfg[lcore_id];
		*targ = &lcore_cfg[lcore_id].targs[0];
		return 0;
	}
}

int core_targ_next(struct lcore_cfg **lconf, struct task_args **targ, const int with_master)
{
	return core_targ_next_from(lconf, targ, lcore_cfg, with_master);
}

int core_targ_next_early(struct lcore_cfg **lconf, struct task_args **targ, const int with_master)
{
	return core_targ_next_from(lconf, targ, lcore_cfg_init, with_master);
}

struct task_args *core_targ_get(uint32_t lcore_id, uint32_t task_id)
{
	return &lcore_cfg[lcore_id].targs[task_id];
}

void lcore_cfg_alloc_hp(void)
{
	size_t mem_size = RTE_MAX_LCORE * sizeof(struct lcore_cfg);

	lcore_cfg = prox_zmalloc(mem_size, rte_socket_id());
	PROX_PANIC(lcore_cfg == NULL, "Could not allocate memory for core control structures\n");
	rte_memcpy(lcore_cfg, lcore_cfg_init, mem_size);

	/* get thread ID for master core */
	lcore_cfg[rte_lcore_id()].thread_id = pthread_self();
}

int lconf_run(__attribute__((unused)) void *dummy)
{
	uint32_t lcore_id = rte_lcore_id();
	struct lcore_cfg *lconf = &lcore_cfg[lcore_id];

	/* get thread ID, and set cancellation type to asynchronous */
	lconf->thread_id = pthread_self();
	int ret = pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);
	if (ret != 0)
		plog_warn("pthread_setcanceltype() failed on core %u: %i\n", lcore_id, ret);

	plog_info("Entering main loop on core %u\n", lcore_id);
	return lconf->thread_x(lconf);
}

static void msg_stop(struct lcore_cfg *lconf)
{
	int idx = -1;
	struct task_base *t = NULL;

	if (lconf->msg.task_id == -1) {
		for (int i = 0; i < lconf->n_tasks_all; ++i) {
			if (lconf->task_is_running[i]) {
				lconf->task_is_running[i] = 0;
				t = lconf->tasks_all[i];
				if (t->aux->stop)
				    t->aux->stop(t);
			}
		}
		lconf->n_tasks_run = 0;

		if (t && t->aux->stop_last)
			t->aux->stop_last(t);
	}
	else {
		for (int i = 0; i < lconf->n_tasks_run; ++i) {
			if (lconf_get_task_id(lconf, lconf->tasks_run[i]) == lconf->msg.task_id) {
				idx = i;
			}
			else if (idx != -1) {
				lconf->tasks_run[idx] = lconf->tasks_run[i];

				idx++;
			}
		}
		lconf->task_is_running[lconf->msg.task_id] = 0;

		t = lconf->tasks_all[lconf->msg.task_id];
		if (t->aux->stop)
			t->aux->stop(t);
		lconf->n_tasks_run--;
		if (lconf->n_tasks_run == 0 && t->aux->stop_last)
			t->aux->stop_last(t);
	}
}

static void msg_start(struct lcore_cfg *lconf)
{
	int idx = 1;
	struct task_base *t = NULL;

	if (lconf->msg.task_id == -1) {
		for (int i = 0; i < lconf->n_tasks_all; ++i) {
			t = lconf->tasks_run[i] = lconf->tasks_all[i];
			lconf->task_is_running[i] = 1;
			if (lconf->n_tasks_run == 0 && t->aux->start_first) {
				t->aux->start_first(t);
				lconf->n_tasks_run = 1;
			}
			if (t->aux->start)
				t->aux->start(t);
		}
		lconf->n_tasks_run = lconf->n_tasks_all;
	}
	else if (lconf->n_tasks_run == 0) {
		t = lconf->tasks_run[0] = lconf->tasks_all[lconf->msg.task_id];
		lconf->n_tasks_run = 1;
		lconf->task_is_running[lconf->msg.task_id] = 1;

		if (t->aux->start_first)
			t->aux->start_first(t);
		if (t->aux->start)
			t->aux->start(t);
	}
	else {
		for (int i = lconf->n_tasks_run - 1; i >= 0; --i) {
			idx = lconf_get_task_id(lconf, lconf->tasks_run[i]);
			if (idx == lconf->msg.task_id) {
				break;
			}
			else if (idx > lconf->msg.task_id) {
				lconf->tasks_run[i + 1] = lconf->tasks_run[i];
				if (i == 0) {
					lconf->tasks_run[i] = lconf->tasks_all[lconf->msg.task_id];
					lconf->n_tasks_run++;
					break;
				}
			}
			else {
				lconf->tasks_run[i + 1] = lconf->tasks_all[lconf->msg.task_id];
				lconf->n_tasks_run++;
				break;
			}
		}
		lconf->task_is_running[lconf->msg.task_id] = 1;

		if (lconf->tasks_all[lconf->msg.task_id]->aux->start)
			lconf->tasks_all[lconf->msg.task_id]->aux->start(lconf->tasks_all[lconf->msg.task_id]);
	}
}

int lconf_do_flags(struct lcore_cfg *lconf)
{
	struct task_base *t;
	int ret = 0;

	if ((lconf->msg.type == LCONF_MSG_TRACE) && (lconf->tasks_all[lconf->msg.task_id]->tx_pkt == tx_pkt_drop_all)) {
		/* We are asked to dump packets through command dump.
		 * This usually means map RX and TX packets before printing them.
		 * However we do not transmit the packets in this case => use the DUMP_RX function.
		 * This will prevent seeing the received packets also printed as TX[255] (= dropped)
		 */
		lconf->msg.type = LCONF_MSG_DUMP_RX;
	}

	switch (lconf->msg.type) {
	case LCONF_MSG_STOP:
		msg_stop(lconf);
		ret = -1;
		break;
	case LCONF_MSG_START:
		msg_start(lconf);
		ret = -1;
		break;
	case LCONF_MSG_DUMP_RX:
	case LCONF_MSG_DUMP_TX:
	case LCONF_MSG_DUMP:
		t = lconf->tasks_all[lconf->msg.task_id];

		if (lconf->msg.val) {
			if (lconf->msg.type == LCONF_MSG_DUMP ||
			    lconf->msg.type == LCONF_MSG_DUMP_RX) {
				t->aux->task_rt_dump.n_print_rx = lconf->msg.val;

				task_base_add_rx_pkt_function(t, rx_pkt_dump);
			}

			if (lconf->msg.type == LCONF_MSG_DUMP ||
			    lconf->msg.type == LCONF_MSG_DUMP_TX) {
				t->aux->task_rt_dump.n_print_tx = lconf->msg.val;
				if (t->tx_pkt == tx_pkt_l3) {
					if (t->aux->tx_pkt_orig)
						t->aux->tx_pkt_l2 = t->aux->tx_pkt_orig;
					t->aux->tx_pkt_orig = t->aux->tx_pkt_l2;
					t->aux->tx_pkt_l2 = tx_pkt_dump;
				} else {
					if (t->aux->tx_pkt_orig)
						t->tx_pkt = t->aux->tx_pkt_orig;
					t->aux->tx_pkt_orig = t->tx_pkt;
					t->tx_pkt = tx_pkt_dump;
				}
			}
		}
		break;
	case LCONF_MSG_TRACE:
		t = lconf->tasks_all[lconf->msg.task_id];

		if (lconf->msg.val) {
			t->aux->task_rt_dump.n_trace = lconf->msg.val;

			if (task_base_get_original_rx_pkt_function(t) != rx_pkt_dummy) {
				task_base_add_rx_pkt_function(t, rx_pkt_trace);
				if (t->tx_pkt == tx_pkt_l3) {
					if (t->aux->tx_pkt_orig)
						t->aux->tx_pkt_l2 = t->aux->tx_pkt_orig;
					t->aux->tx_pkt_orig = t->aux->tx_pkt_l2;
					t->aux->tx_pkt_l2 = tx_pkt_trace;
				} else {
					if (t->aux->tx_pkt_orig)
						t->tx_pkt = t->aux->tx_pkt_orig;
					t->aux->tx_pkt_orig = t->tx_pkt;
					t->tx_pkt = tx_pkt_trace;
				}
			} else {
				t->aux->task_rt_dump.n_print_tx = lconf->msg.val;
				if (t->tx_pkt == tx_pkt_l3) {
					if (t->aux->tx_pkt_orig)
						t->aux->tx_pkt_l2 = t->aux->tx_pkt_orig;
					t->aux->tx_pkt_orig = t->aux->tx_pkt_l2;
					t->aux->tx_pkt_l2 = tx_pkt_dump;
				} else {
					if (t->aux->tx_pkt_orig)
						t->tx_pkt = t->aux->tx_pkt_orig;
					t->aux->tx_pkt_orig = t->tx_pkt;
					t->tx_pkt = tx_pkt_dump;
				}
			}
		}
		break;
	case LCONF_MSG_RX_DISTR_START:
		for (uint8_t task_id = 0; task_id < lconf->n_tasks_all; ++task_id) {
			t = lconf->tasks_all[task_id];
			task_base_add_rx_pkt_function(t, rx_pkt_distr);
			memset(t->aux->rx_bucket, 0, sizeof(t->aux->rx_bucket));
			lconf->flags |= LCONF_FLAG_RX_DISTR_ACTIVE;
		}
		break;
	case LCONF_MSG_TX_DISTR_START:
		for (uint8_t task_id = 0; task_id < lconf->n_tasks_all; ++task_id) {
			t = lconf->tasks_all[task_id];

			if (t->tx_pkt == tx_pkt_l3) {
				t->aux->tx_pkt_orig = t->aux->tx_pkt_l2;
				t->aux->tx_pkt_l2 = tx_pkt_distr;
			} else {
				t->aux->tx_pkt_orig = t->tx_pkt;
				t->tx_pkt = tx_pkt_distr;
			}
			memset(t->aux->tx_bucket, 0, sizeof(t->aux->tx_bucket));
			lconf->flags |= LCONF_FLAG_TX_DISTR_ACTIVE;
		}
		break;
	case LCONF_MSG_RX_DISTR_STOP:
		for (uint8_t task_id = 0; task_id < lconf->n_tasks_all; ++task_id) {
			t = lconf->tasks_all[task_id];
			task_base_del_rx_pkt_function(t, rx_pkt_distr);
			lconf->flags &= ~LCONF_FLAG_RX_DISTR_ACTIVE;
		}
		break;
	case LCONF_MSG_TX_DISTR_STOP:
		for (uint8_t task_id = 0; task_id < lconf->n_tasks_all; ++task_id) {
			t = lconf->tasks_all[task_id];
			if (t->aux->tx_pkt_orig) {
				if (t->tx_pkt == tx_pkt_l3) {
					t->aux->tx_pkt_l2 = t->aux->tx_pkt_orig;
					t->aux->tx_pkt_orig = NULL;
				} else {
					t->tx_pkt = t->aux->tx_pkt_orig;
					t->aux->tx_pkt_orig = NULL;
				}
				lconf->flags &= ~LCONF_FLAG_TX_DISTR_ACTIVE;
			}
		}
		break;
	case LCONF_MSG_RX_DISTR_RESET:
		for (uint8_t task_id = 0; task_id < lconf->n_tasks_all; ++task_id) {
			t = lconf->tasks_all[task_id];

			memset(t->aux->rx_bucket, 0, sizeof(t->aux->rx_bucket));
		}
		break;
	case LCONF_MSG_TX_DISTR_RESET:
		for (uint8_t task_id = 0; task_id < lconf->n_tasks_all; ++task_id) {
			t = lconf->tasks_all[task_id];

			memset(t->aux->tx_bucket, 0, sizeof(t->aux->tx_bucket));
		}
		break;
	case LCONF_MSG_RX_BW_START:
		for (uint8_t task_id = 0; task_id < lconf->n_tasks_all; ++task_id) {
			t = lconf->tasks_all[task_id];
			task_base_add_rx_pkt_function(t, rx_pkt_bw);
			lconf->flags |= LCONF_FLAG_RX_BW_ACTIVE;
		}
		break;
	case LCONF_MSG_RX_BW_STOP:
		for (uint8_t task_id = 0; task_id < lconf->n_tasks_all; ++task_id) {
			t = lconf->tasks_all[task_id];
			task_base_del_rx_pkt_function(t, rx_pkt_bw);
			lconf->flags &= ~LCONF_FLAG_RX_BW_ACTIVE;
		}
		break;
	case LCONF_MSG_TX_BW_START:
		for (uint8_t task_id = 0; task_id < lconf->n_tasks_all; ++task_id) {
			t = lconf->tasks_all[task_id];

			if (t->tx_pkt == tx_pkt_l3) {
				t->aux->tx_pkt_orig = t->aux->tx_pkt_l2;
				t->aux->tx_pkt_l2 = tx_pkt_bw;
			} else {
				t->aux->tx_pkt_orig = t->tx_pkt;
				t->tx_pkt = tx_pkt_bw;
			}
			lconf->flags |= LCONF_FLAG_TX_BW_ACTIVE;
		}
		break;
	case LCONF_MSG_TX_BW_STOP:
		for (uint8_t task_id = 0; task_id < lconf->n_tasks_all; ++task_id) {
			t = lconf->tasks_all[task_id];
			if (t->aux->tx_pkt_orig) {
				if (t->tx_pkt == tx_pkt_l3) {
					t->aux->tx_pkt_l2 = t->aux->tx_pkt_orig;
					t->aux->tx_pkt_orig = NULL;
				} else {
					t->tx_pkt = t->aux->tx_pkt_orig;
					t->aux->tx_pkt_orig = NULL;
				}
				lconf->flags &= ~LCONF_FLAG_TX_BW_ACTIVE;
			}
		}
		break;
	}

	lconf_unset_req(lconf);
	return ret;
}

int lconf_get_task_id(const struct lcore_cfg *lconf, const struct task_base *task)
{
	for (int i = 0; i < lconf->n_tasks_all; ++i) {
		if (lconf->tasks_all[i] == task)
			return i;
	}

	return -1;
}

int lconf_task_is_running(const struct lcore_cfg *lconf, uint8_t task_id)
{
	return lconf->task_is_running[task_id];
}