/*
 *   teamd_ctl.c - Teamd control subsustem
 *   Copyright (C) 2012-2015 Jiri Pirko <jiri@resnulli.us>
 *
 *   This library is free software; you can redistribute it and/or
 *   modify it under the terms of the GNU Lesser General Public
 *   License as published by the Free Software Foundation; either
 *   version 2.1 of the License, or (at your option) any later version.
 *
 *   This library is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *   Lesser General Public License for more details.
 *
 *   You should have received a copy of the GNU Lesser General Public
 *   License along with this library; if not, write to the Free Software
 *   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */

#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <team.h>
#include <private/misc.h>

#include "teamd.h"
#include "teamd_config.h"
#include "teamd_state.h"
#include "teamd_ctl.h"

static int teamd_ctl_method_port_config_update(struct teamd_context *ctx,
					       const struct teamd_ctl_method_ops *ops,
					       void *ops_priv)
{
	const char *port_devname;
	const char *port_config;
	uint32_t ifindex;
	int err;

	err = ops->get_args(ops_priv, "ss", &port_devname, &port_config);
	if (err)
		return ops->reply_err(ops_priv, "InvalidArgs", "Did not receive correct message arguments.");
	teamd_log_dbgx(ctx, 2, "port_devname \"%s\", port_config \"%s\"",
		       port_devname, port_config);

	ifindex = team_ifname2ifindex(ctx->th, port_devname);
	if (!ifindex) {
		teamd_log_err("Device \"%s\" does not exist.", port_devname);
		return ops->reply_err(ops_priv, "NoSuchDev", "No such device.");
	}
	err = teamd_config_port_update(ctx, port_devname, port_config);
	if (err) {
		teamd_log_err("Failed to update config for port \"%s\".",
			      port_devname);
		return ops->reply_err(ops_priv, "ConfigUpdateFail", "Failed to update config.");
	}
	return ops->reply_succ(ops_priv, NULL);
}

static int teamd_ctl_method_port_config_dump(struct teamd_context *ctx,
					     const struct teamd_ctl_method_ops *ops,
					     void *ops_priv)
{
	const char *port_devname;
	uint32_t ifindex;
	char *cfg;
	int err;

	err = ops->get_args(ops_priv, "s", &port_devname);
	if (err)
		return ops->reply_err(ops_priv, "InvalidArgs", "Did not receive correct message arguments.");
	teamd_log_dbgx(ctx, 2, "port_devname \"%s\"", port_devname);

	ifindex = team_ifname2ifindex(ctx->th, port_devname);
	if (!ifindex) {
		teamd_log_err("Device \"%s\" does not exist.", port_devname);
		return ops->reply_err(ops_priv, "NoSuchDev", "No such device.");
	}
	err = teamd_config_port_dump(ctx, port_devname, &cfg);
	if (err) {
		teamd_log_err("Failed to dump config for port \"%s\".",
			      port_devname);
		return ops->reply_err(ops_priv, "ConfigDumpFail", "Failed to dump config.");
	}
	err = ops->reply_succ(ops_priv, cfg);
	free(cfg);
	return err;
}

static int teamd_ctl_method_port_add(struct teamd_context *ctx,
				     const struct teamd_ctl_method_ops *ops,
				     void *ops_priv)
{
	const char *port_devname;
	int err;

	err = ops->get_args(ops_priv, "s", &port_devname);
	if (err)
		return ops->reply_err(ops_priv, "InvalidArgs", "Did not receive correct message arguments.");
	teamd_log_dbgx(ctx, 2, "port_devname \"%s\"", port_devname);

	err = teamd_port_add_ifname(ctx, port_devname);
	if (err) {
		teamd_log_err("%s: Failed to add port (%s).", port_devname,
			      strerror(-err));
	}

	switch (err) {
	case -ENODEV:
		return ops->reply_err(ops_priv, "NoSuchDev", "No such device.");
	case 0:
		break;
	default:
		return ops->reply_err(ops_priv, "PortAddFail", "Failed to add port.");
	}
	return ops->reply_succ(ops_priv, NULL);
}

static int teamd_ctl_method_port_remove(struct teamd_context *ctx,
					const struct teamd_ctl_method_ops *ops,
					void *ops_priv)
{
	const char *port_devname;
	int err;

	err = ops->get_args(ops_priv, "s", &port_devname);
	if (err)
		return ops->reply_err(ops_priv, "InvalidArgs", "Did not receive correct message arguments.");
	teamd_log_dbgx(ctx, 2, "port_devname \"%s\"", port_devname);

	err = teamd_port_remove_ifname(ctx, port_devname);
	switch (err) {
	case -ENODEV:
		return ops->reply_err(ops_priv, "NoSuchDev", "No such device.");
	case 0:
		break;
	default:
		return ops->reply_err(ops_priv, "PortRemoveFail", "Failed to del port.");
	}
	return ops->reply_succ(ops_priv, NULL);
}

static int teamd_ctl_method_config_dump(struct teamd_context *ctx,
					const struct teamd_ctl_method_ops *ops,
					void *ops_priv)
{
	char *cfg;
	int err;

	err = teamd_config_dump(ctx, &cfg);
	if (err) {
		teamd_log_err("Failed to dump config.");
		return ops->reply_err(ops_priv, "ConfigDumpFail", "Failed to dump config.");
	}
	err = ops->reply_succ(ops_priv, cfg);
	free(cfg);
	return err;
}

static int teamd_ctl_method_config_dump_actual(struct teamd_context *ctx,
					       const struct teamd_ctl_method_ops *ops,
					       void *ops_priv)
{
	char *cfg;
	int err;

	err = teamd_config_actual_dump(ctx, &cfg);
	if (err) {
		teamd_log_err("Failed to dump actual config.");
		return ops->reply_err(ops_priv, "ConfigDumpActualFail", "Failed to dump actual config.");
	}
	err = ops->reply_succ(ops_priv, cfg);
	free(cfg);
	return err;
}

static int teamd_ctl_method_state_dump(struct teamd_context *ctx,
				       const struct teamd_ctl_method_ops *ops,
				       void *ops_priv)
{
	char *state;
	int err;

	err = teamd_state_dump(ctx, &state);
	if (err) {
		teamd_log_err("Failed to dump state.");
		return ops->reply_err(ops_priv, "StateDumpFail", "Failed to dump state.");
	}
	err = ops->reply_succ(ops_priv, state);
	free(state);
	return err;
}

static int teamd_ctl_method_state_item_value_get(struct teamd_context *ctx,
						 const struct teamd_ctl_method_ops *ops,
						 void *ops_priv)
{
	const char *item_path;
	char *value;
	int err;

	err = ops->get_args(ops_priv, "s", &item_path);
	if (err)
		return ops->reply_err(ops_priv, "InvalidArgs", "Did not receive correct message arguments.");
	teamd_log_dbgx(ctx, 2, "item_path \"%s\"", item_path);

	err = teamd_state_item_value_get(ctx, item_path, &value);
	if (err == -ENOENT) {
		teamd_log_err("Failed to get state item \"%s\".", item_path);
		return ops->reply_err(ops_priv, "PathDoesNotExist", "Item path does not exist.");
	} else if (err) {
		teamd_log_err("Failed to get state item \"%s\".", item_path);
		return ops->reply_err(ops_priv, "ItemValueGetFail", "Failed to get item value.");
	}
	err =  ops->reply_succ(ops_priv, value);
	free(value);
	return err;
}

static int teamd_ctl_method_state_item_value_set(struct teamd_context *ctx,
						 const struct teamd_ctl_method_ops *ops,
						 void *ops_priv)
{
	const char *item_path;
	const char *value;
	int err;

	err = ops->get_args(ops_priv, "ss", &item_path, &value);
	if (err)
		return ops->reply_err(ops_priv, "InvalidArgs", "Did not receive correct message arguments.");
	teamd_log_dbgx(ctx, 2, "item_path \"%s\", value \"%s\"",
		       item_path, value);

	err = teamd_state_item_value_set(ctx, item_path, value);
	if (err == -ENOENT) {
		teamd_log_err("Failed to set state item \"%s\".", item_path);
		return ops->reply_err(ops_priv, "PathDoesNotExist", "Item path does not exist.");
	} else if (err == -EOPNOTSUPP) {
		teamd_log_err("Failed to set state item \"%s\".", item_path);
		return ops->reply_err(ops_priv, "OpNotSupp", "Operation not supported.");
	} else if (err) {
		teamd_log_err("Failed to set state item \"%s\".", item_path);
		return ops->reply_err(ops_priv, "ItemValueSetFail", "Failed to set item value.");
	}
	return ops->reply_succ(ops_priv, NULL);
}

typedef int (*teamd_ctl_method_func_t)(struct teamd_context *ctx,
				       const struct teamd_ctl_method_ops *ops,
				       void *ops_priv);
struct teamd_ctl_method {
	const char *name;
	teamd_ctl_method_func_t func;
};

static const struct teamd_ctl_method teamd_ctl_method_list[] = {
	{
		.name = "PortConfigUpdate",
		.func = teamd_ctl_method_port_config_update,

	},
	{
		.name = "PortConfigDump",
		.func = teamd_ctl_method_port_config_dump,

	},
	{
		.name = "PortAdd",
		.func = teamd_ctl_method_port_add,

	},
	{
		.name = "PortRemove",
		.func = teamd_ctl_method_port_remove,

	},
	{
		.name = "ConfigDump",
		.func = teamd_ctl_method_config_dump,

	},
	{
		.name = "ConfigDumpActual",
		.func = teamd_ctl_method_config_dump_actual,

	},
	{
		.name = "StateDump",
		.func = teamd_ctl_method_state_dump,

	},
	{
		.name = "StateItemValueGet",
		.func = teamd_ctl_method_state_item_value_get,

	},
	{
		.name = "StateItemValueSet",
		.func = teamd_ctl_method_state_item_value_set,

	},
};

#define TEAMD_CTL_METHOD_LIST_SIZE ARRAY_SIZE(teamd_ctl_method_list)

static teamd_ctl_method_func_t get_func_by_name(const char *method_name)
{
	int i;

	for (i = 0; i < TEAMD_CTL_METHOD_LIST_SIZE; i++) {
		const struct teamd_ctl_method *method;

		method = &teamd_ctl_method_list[i];
		if (!strcmp(method->name, method_name))
			return method->func;
	}
	return NULL;
}

bool teamd_ctl_method_exists(const char *method_name)
{
	return get_func_by_name(method_name);
}

int teamd_ctl_method_call(struct teamd_context *ctx, const char *method_name,
			  const struct teamd_ctl_method_ops *ops,
			  void *ops_priv)
{
	teamd_ctl_method_func_t func;

	func = get_func_by_name(method_name);
	if (!func) {
		teamd_log_err("Failed call non-existent method named \"%s\".",
			      method_name);
		return -EINVAL;
	}
	return func(ctx, ops, ops_priv);
}
