/*
   drbdadm_dump.c

   This file is part of DRBD by Philipp Reisner and Lars Ellenberg.

   Copyright (C) 2003-2008, LINBIT Information Technologies GmbH.
   Copyright (C) 2003-2008, Philipp Reisner <philipp.reisner@linbit.com>.
   Copyright (C) 2003-2008, Lars Ellenberg <lars.ellenberg@linbit.com>.

   drbd is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2, or (at your option)
   any later version.

   drbd 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 General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with drbd; see the file COPYING.  If not, write to
   the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#include "drbdadm.h"
#include "drbdtool_common.h"

static int indent = 0;
#define INDENT_WIDTH 4
#define BFMT  "%s;\n"
#define IPV4FMT "%-16s %s %s:%s%s"
#define IPV6FMT "%-16s %s [%s]:%s%s"
#define MDISK "%-16s %s;\n"
#define MDISKI "%-16s %s [%s];\n"
#define printI(fmt, args... ) printf("%*s" fmt,INDENT_WIDTH * indent,"" , ## args )
#define printA(name, val ) \
	printf("%*s%*s %3s;\n", \
	  INDENT_WIDTH * indent,"" , \
	  -24+INDENT_WIDTH * indent, \
	  name, val )

static void __dump_options(struct options const *options)
{
	struct d_option *option;

	STAILQ_FOREACH(option, options, link) {
		if (option->value)
			printA(option->name,
			       option->is_escaped ? option->value : esc(option->
									value));
		else
			printI(BFMT, option->name);
	}
}

static void dump_options(const char *section_name, const char *subsection_name, int count, ...)
{
	struct options *options;
	bool all_empty = true;
	bool subsection = false;
	va_list ap;
	int i;

	va_start(ap, count);
        for (i = 0; i < count; i++) {
		options = va_arg(ap, struct options*);
                if (!STAILQ_EMPTY(options)) {
			all_empty = false;
			if (i >= 1)
				subsection = true;
                }
        }
	va_end(ap);
	if (all_empty)
		return;

	if (!subsection_name)
		subsection = false;

	printI("%s {\n", section_name);
	++indent;
	va_start(ap, count);
	options = va_arg(ap, struct options*);
	__dump_options(options);
	if (subsection) {
		printI("%s {\n", subsection_name);
		++indent;
        }
	for (i = 1; i < count; i++) {
		options = va_arg(ap, struct options*);
		__dump_options(options);
        }
	va_end(ap);
	if (subsection) {
		--indent;
		printI("}\n");
        }
	--indent;
	printI("}\n");
}

void dump_global_info()
{
	static const char * const yes_no_ask[] = {
		[UC_YES] = "yes",
		[UC_NO] = "no",
		[UC_ASK] = "ask",
	};
	if (!global_options.minor_count
	    && !global_options.disable_ip_verification
	    && global_options.dialog_refresh == 1
	    && global_options.usage_count == UC_ASK
	    && !verbose)
		return;
	printI("global {\n");
	++indent;
	if (global_options.disable_ip_verification)
		printI("disable-ip-verification;\n");
	if (global_options.minor_count)
		printI("minor-count %i;\n", global_options.minor_count);
	if (global_options.dialog_refresh != 1)
		printI("dialog-refresh %i;\n", global_options.dialog_refresh);
	if (global_options.usage_count != UC_ASK)
		printI("usage-count %s;\n", yes_no_ask[global_options.usage_count]);
	--indent;
	printI("}\n\n");
}

static void fake_startup_options(struct d_resource *res);

static void dump_common_info()
{
	if (!common)
		return;
	printI("common {\n");
	++indent;

	fake_startup_options(common);
	dump_options("options", NULL, 1, &common->res_options);
	dump_options("net", NULL, 1, &common->net_options);
	dump_options("disk", NULL, 3, &common->device_options, &common->disk_options, &common->pd_options);
	dump_options("startup", NULL, 1, &common->startup_options);
	dump_options("proxy", "plugin", 2, &common->proxy_options, &common->proxy_plugins);
	dump_options("handlers", NULL, 1, &common->handlers);
	--indent;
	printf("}\n\n");
}

static void dump_address(char *name, struct d_address *address, char *postfix)
{
	if (!strcmp(address->af, "ipv6"))
		printI(IPV6FMT, name, address->af, address->addr, address->port, postfix);
	else
		printI(IPV4FMT, name, address->af, address->addr, address->port, postfix);
}

static void dump_proxy_info(const char *prefix, struct d_proxy_info *pi)
{
	printI("%sproxy on %s {\n", prefix, names_to_str(&pi->on_hosts));
	++indent;
	dump_address("inside", &pi->inside, ";\n");
	dump_address("outside", &pi->outside, ";\n");
	dump_options("options", "plugin", 2, &pi->options, &pi->plugins);
	--indent;
	printI("}\n");
}

static void dump_volume(int has_lower, struct d_volume *vol)
{
	if (!vol->implicit) {
		printI("volume %d {\n", vol->vnr);
		++indent;
	}

	/* Handle volume of '_remote_host' */
	if (vol->device_minor == -1 && !vol->device
	&& !vol->disk && !vol->meta_disk && !vol->meta_index)
		goto out;

	dump_options("disk", NULL, 3, &vol->device_options, &vol->disk_options, &vol->pd_options);

	if (vol->parsed_device || verbose) {
		printI("device%*s", -19 + INDENT_WIDTH * indent, "");
		if (vol->device)
			printf("%s ", esc(vol->device));
		printf("minor %d;\n", vol->device_minor);
	}

	if (!has_lower && (vol->parsed_disk || verbose))
		printA("disk", esc(vol->disk ? vol->disk : "none"));

	if (!has_lower && (vol->parsed_meta_disk || verbose) && vol->meta_disk) {
		if (!strcmp(vol->meta_index, "flexible"))
			printI(MDISK, "meta-disk", esc(vol->meta_disk));
		else if (!strcmp(vol->meta_index, "internal"))
			printA("meta-disk", "internal");
		else
			printI(MDISKI, "meta-disk", esc(vol->meta_disk),
			       vol->meta_index);
	}

	if (!vol->implicit) {
	out:
		--indent;
		printI("}\n");
	}
}

static void dump_host_info(struct d_host_info *hi)
{
	struct d_volume *vol;

	if (!hi) {
		printI("  # No host section data available.\n");
		return;
	}

	if (hi->implicit && !verbose)
		return;

	if (hi->lower) {
		printI("stacked-on-top-of %s {\n", esc(hi->lower->name));
		++indent;
		printI("# on %s \n", names_to_str(&hi->on_hosts));
	} else if (hi->by_address) {
		dump_address("floating", &hi->address, " {\n");
		++indent;
	} else {
		printI("on %s {\n", names_to_str(&hi->on_hosts));
		++indent;
	}
	printI("node-id %s;\n", hi->node_id);

	dump_options("options", NULL, 1, &hi->res_options);

	for_each_volume(vol, &hi->volumes) {
		if (vol->parsed_device || vol->parsed_disk || vol->parsed_meta_disk || verbose)
			dump_volume(!!hi->lower, vol);
	}

	if (!hi->by_address && hi->address.addr)
		dump_address("address", &hi->address, ";\n");
	if (hi->proxy_compat_only && !verbose)
		dump_proxy_info("", hi->proxy_compat_only);
	--indent;
	printI("}\n");
}

static void dump_hname_address_pairs(struct hname_address_pairs *hname_address_pairs)
{
	struct hname_address *ha;

	STAILQ_FOREACH(ha, hname_address_pairs, link) {
		if (ha->by_address || ha->faked_hostname) {
			dump_address("address", &ha->address,
				ssprintf("%s# on %s\n", ha->proxy ? " " : "; ", ha->name));
		} else {
			printI("host %s", ha->name);
			if (ha->parsed_address || (verbose && ha->address.addr))
				dump_address(" address", &ha->address, "");
			else if (ha->parsed_port)
				printf(" port %s", ha->address.port);
		}

		if (ha->proxy)
			dump_proxy_info(" via ", ha->proxy);
		else if (ha->parsed_outside_address)
			dump_address(" via outside-address", &ha->outside_address, ";\n");
		else
			printf(";\n");
	}
}

static void dump_connection(struct connection *conn)
{
	struct peer_device *pd;
	struct path *path;

	if (conn->implicit && !verbose)
		return;

	printI("connection");
	if (conn->name)
		printf(" %s", esc(conn->name));
	printf(" {\n");
	++indent;

	path = STAILQ_FIRST(&conn->paths);
	if (path->implicit && !verbose) {
		dump_hname_address_pairs(&path->hname_address_pairs);
	} else {
		for_each_path(path, &conn->paths) {
			printI("path {\n");
			++indent;
			dump_hname_address_pairs(&path->hname_address_pairs);
			--indent;
			printI("}\n");
		}
	}

	dump_options("net", NULL, 1, &conn->net_options);
	dump_options("disk", NULL, 1, &conn->pd_options);
	STAILQ_FOREACH(pd, &conn->peer_devices, connection_link) {
		if (pd->implicit && !verbose)
			continue;
		printI("volume %d {\n", pd->vnr);
		++indent;
		dump_options("disk", NULL, 1, &pd->pd_options);
		--indent;
		printI("}\n");
	}
	--indent;
	printI("}\n");
}

static void __dump_options_xml(struct options *options)
{
	struct d_option *option;

	STAILQ_FOREACH(option, options, link) {
		if (option->value)
			printI("<option name=\"%s\" value=\"%s\"/>\n",
			       option->name,
			       option->is_escaped ? option->value : esc_xml(option->
									    value));
		else
			printI("<option name=\"%s\"/>\n", option->name);
	}
}

static void dump_options_xml(const char *section_name, const char *subsection_name, int count, ...)
{
	struct options *options;
	bool all_empty = true;
	bool subsection = false;
	va_list ap;
	int i;

	va_start(ap, count);
        for (i = 0; i < count; i++) {
		options = va_arg(ap, struct options*);
                if (!STAILQ_EMPTY(options)) {
			all_empty = false;
			if (i >= 1)
				subsection = true;
                }
        }
	va_end(ap);
	if (all_empty)
		return;

	if (!subsection_name)
		subsection = false;

	printI("<section name=\"%s\">\n", section_name);
	++indent;
	va_start(ap, count);
	options = va_arg(ap, struct options*);
	__dump_options_xml(options);
	if (subsection) {
		printI("<section name=\"%s\">\n", subsection_name);
		++indent;
        }
	for (i = 1; i < count; i++) {
		options = va_arg(ap, struct options*);
		__dump_options_xml(options);
        }
	va_end(ap);
	if (subsection) {
		--indent;
		printI("</section>\n");
        }
	--indent;
	printI("</section>\n");
}

static void dump_global_info_xml()
{
	if (!global_options.minor_count
	    && !global_options.disable_ip_verification
	    && global_options.dialog_refresh == 1)
		return;
	printI("<global>\n");
	++indent;
	if (global_options.disable_ip_verification)
		printI("<disable-ip-verification/>\n");
	if (global_options.minor_count)
		printI("<minor-count count=\"%i\"/>\n",
		       global_options.minor_count);
	if (global_options.dialog_refresh != 1)
		printI("<dialog-refresh refresh=\"%i\"/>\n",
		       global_options.dialog_refresh);
	--indent;
	printI("</global>\n");
}

static void dump_common_info_xml()
{
	if (!common)
		return;
	printI("<common>\n");
	++indent;
	fake_startup_options(common);
	dump_options_xml("options", NULL, 1, &common->res_options);
	dump_options_xml("net", NULL, 1, &common->net_options);
	dump_options_xml("disk", NULL, 3, &common->device_options, &common->disk_options, &common->pd_options);
	dump_options_xml("startup", NULL, 1, &common->startup_options);
	dump_options_xml("proxy", "plugin", 2, &common->proxy_options, &common->proxy_plugins);
	dump_options_xml("handlers", NULL, 1, &common->handlers);
	--indent;
	printI("</common>\n");
}

static void dump_proxy_info_xml(struct d_proxy_info *pi)
{
	printI("<proxy hostname=\"%s\">\n", names_to_str(&pi->on_hosts));
	++indent;
	printI("<inside family=\"%s\" port=\"%s\">%s</inside>\n", pi->inside.af,
	       pi->inside.port, pi->inside.addr);
	printI("<outside family=\"%s\" port=\"%s\">%s</outside>\n",
	       pi->outside.af, pi->outside.port, pi->outside.addr);
	dump_options_xml("options", NULL, 2, &pi->options, &pi->plugins);
	--indent;
	printI("</proxy>\n");
}

static void dump_volume_xml(struct d_volume *vol)
{
	printI("<volume vnr=\"%d\">\n", vol->vnr);
	++indent;

	dump_options_xml("disk", NULL, 3, &vol->device_options, &vol->disk_options, &vol->pd_options);
	printI("<device minor=\"%d\">%s</device>\n", vol->device_minor,
	       esc_xml(vol->device));
	printI("<disk>%s</disk>\n", esc_xml(vol->disk));

	if (vol->meta_index) {
		/* Stacked do not have this... */
		if (!strcmp(vol->meta_index, "flexible"))
			printI("<meta-disk>%s</meta-disk>\n",
			       esc_xml(vol->meta_disk));
		else if (!strcmp(vol->meta_index, "internal"))
			printI("<meta-disk>internal</meta-disk>\n");
		else {
			printI("<meta-disk index=\"%s\">%s</meta-disk>\n",
			       vol->meta_index, esc_xml(vol->meta_disk));
		}
	}
	--indent;
	printI("</volume>\n");
}

static void dump_host_info_xml(struct d_host_info *hi)
{
	struct d_volume *vol;

	if (!hi) {
		printI("<!-- No host section data available. -->\n");
		return;
	}

	if (hi->by_address)
		printI("<host floating=\"1\">\n");
	else
		printI("<host name=\"%s\">\n", names_to_str(&hi->on_hosts));

	++indent;

	dump_options_xml("options", NULL, 1, &hi->res_options);
	for_each_volume(vol, &hi->volumes)
		dump_volume_xml(vol);

	printI("<address family=\"%s\" port=\"%s\">%s</address>\n",
	       hi->address.af, hi->address.port, hi->address.addr);
	--indent;
	printI("</host>\n");
}

static void dump_hname_address_pairs_xml(struct hname_address_pairs *hname_address_pairs)
{
	struct hname_address *ha;

	STAILQ_FOREACH(ha, hname_address_pairs, link) {
		printI("<host name=\"%s\">", ha->name);
		if (ha->proxy) {
			printf("\n");
			++indent;
			printI("");
		}
		if (ha->address.addr)
			printf("<address family=\"%s\" port=\"%s\">%s</address>",
			       ha->address.af, ha->address.port, ha->address.addr);
		else
			printf("<address family=\"%s\" port=\"%s\">%s</address>",
			       ha->host_info->address.af, ha->host_info->address.port,
			       ha->host_info->address.addr);
		if (ha->proxy) {
			printf("\n");
			dump_proxy_info_xml(ha->proxy);
			--indent;
			printI("</host>\n");
		} else {
			printf("</host>\n");
		}
	}
}

static void dump_connection_xml(struct connection *conn)
{
	struct peer_device *pd;
	struct path *path;


	if (conn->name)
		printI("<connection name=\"%s\">\n", esc_xml(conn->name));
	else
		printI("<connection>\n");
	++indent;

	path = STAILQ_FIRST(&conn->paths);
	if (path->implicit && !verbose) {
		dump_hname_address_pairs_xml(&path->hname_address_pairs);
	} else {
		for_each_path(path, &conn->paths) {
			printI("<path>\n");
			++indent;
			dump_hname_address_pairs_xml(&path->hname_address_pairs);
			--indent;
			printI("</path>\n");
		}
	}

	dump_options_xml("net", NULL, 1, &conn->net_options);
	dump_options_xml("disk", NULL, 1, &conn->pd_options);
	STAILQ_FOREACH(pd, &conn->peer_devices, connection_link) {
		if (pd->implicit && !verbose)
			continue;
		printI("<volume vnr=\"%d\">\n", pd->vnr);
		++indent;
		dump_options_xml("disk", NULL, 1, &pd->pd_options);
		--indent;
		printI("</volume>\n");
	}
	--indent;
	printI("</connection>\n");
}

static void fake_startup_options(struct d_resource *res)
{
	struct d_option *opt;

	if (res->stacked_timeouts) {
		opt = new_opt(strdup("stacked-timeouts"), NULL);
		insert_tail(&res->startup_options, opt);
	}
}

static void dump_mesh(struct d_resource *res)
{
	struct mesh *mesh;

	STAILQ_FOREACH(mesh, &res->meshes, link) {
		struct d_name *h;

		printI("connection-mesh {\n");
		++indent;
		printI("hosts");
		STAILQ_FOREACH(h, &mesh->hosts, link)
			printf(" %s", h->name);
		printf(";\n");
		dump_options("net", NULL, 1, &mesh->net_options);
		--indent;
		printI("}\n");
	}
}

int adm_dump(const struct cfg_ctx *ctx)
{
	struct d_host_info *host;
	struct d_resource *res = ctx->res;
	struct connection *conn;
	struct d_volume *vol;

	if (!res) {
		printI("# no resources configured\n");
		return 0;
	}

	printI("# resource %s on %s: %s, %s\n",
	       esc(res->name), hostname,
	       res->ignore ? "ignored" : "not ignored",
	       res->stacked ? "stacked" : "not stacked");
	printI("# defined at %s:%u\n", res->config_file, res->start_line);
	printI("resource %s {\n", esc(res->name));
	++indent;

	if (res->template)
		printI("template-file %s;\n", esc(res->template->config_file));

	for_each_volume(vol, &res->volumes) {
		if (!verbose && (vol->parsed_device || vol->parsed_disk || vol->parsed_meta_disk))
			dump_volume(res->stacked, vol);
	}

	for_each_host(host, &res->all_hosts)
		dump_host_info(host);

	for_each_connection(conn, &res->connections)
		dump_connection(conn);

	if (!verbose)
		dump_mesh(res);

	fake_startup_options(res);
	dump_options("options", NULL, 1, &res->res_options);
	dump_options("net", NULL, 1, &res->net_options);
	dump_options("disk", NULL, 3, &res->device_options, &res->disk_options, &res->pd_options);
	dump_options("startup", NULL, 1, &res->startup_options);
	dump_options("proxy", "plugin", 2, &res->proxy_options, &res->proxy_plugins);
	dump_options("handlers", NULL, 1, &res->handlers);
	--indent;
	printf("}\n\n");

	return 0;
}

int adm_dump_xml(const struct cfg_ctx *ctx)
{
	struct d_host_info *host;
	struct d_resource *res = ctx->res;
	struct connection *conn;

	if (!res) {
		printI("<!-- No resources configured -->\n");
		return 0;
	}

	printI("<resource name=\"%s\" conf-file-line=\"%s:%u\">\n",
		esc_xml(res->name),
		esc_xml(res->config_file), res->start_line);
	++indent;

	if (res->template)
		printI("<template file=\"%s\">\n", esc_xml(res->template->config_file));

	// else if (common && common->protocol) printA("# common protocol", common->protocol);
	for_each_host(host, &res->all_hosts)
		dump_host_info_xml(host);
	for_each_connection(conn, &res->connections)
		dump_connection_xml(conn);
	fake_startup_options(res);
	dump_options_xml("options", NULL, 1, &res->res_options);
	dump_options_xml("net", NULL, 1, &res->net_options);
	dump_options_xml("disk", NULL, 3, &res->device_options, &res->disk_options, &res->pd_options);
	dump_options_xml("startup", NULL, 1, &res->startup_options);
	dump_options_xml("proxy", "plugin", 2, &res->proxy_options, &res->proxy_plugins);
	dump_options_xml("handlers", NULL, 1, &res->handlers);
	--indent;
	printI("</resource>\n");

	return 0;
}

void print_dump_xml_header(void)
{
	printf("<config file=\"%s\">\n", config_save);
	++indent;
	dump_global_info_xml();
	dump_common_info_xml();
}

void print_dump_header(void)
{
	printf("# %s\n", config_save);
	dump_global_info();
	dump_common_info();
}
