/* -*- mode: c; c-file-style: "openbsd" -*- */
/*
 * Copyright (c) 2010 Andreas Hofmeister <andi@collax.com>
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

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

#if defined(__clang__)
#  pragma clang diagnostic push
#  pragma clang diagnostic ignored "-Wdocumentation"
#endif
#include <libxml/encoding.h>
#include <libxml/xmlwriter.h>
#if defined(__clang__)
#  pragma clang diagnostic pop
#endif

#include "writer.h"
#include "../compat/compat.h"
#include "../log.h"

struct xml_writer_private {
	FILE *fh;
	ssize_t depth;
	xmlTextWriterPtr xw;
	xmlDocPtr doc;
};

static void
xml_new_writer(struct xml_writer_private *priv)
{
	priv->xw = xmlNewTextWriterDoc(&(priv->doc), 0);
	if (!priv->xw) fatalx("lldpctl", "cannot create xml writer");

	xmlTextWriterSetIndent(priv->xw, 4);

	if (xmlTextWriterStartDocument(priv->xw, NULL, "UTF-8", NULL) < 0)
		fatalx("lldpctl", "cannot start xml document");
}

static void
xml_start(struct writer *w, const char *tag, const char *descr)
{
	struct xml_writer_private *p = w->priv;

	if (p->depth == 0) xml_new_writer(p);

	if (xmlTextWriterStartElement(p->xw, BAD_CAST tag) < 0)
		log_warnx("lldpctl", "cannot start '%s' element", tag);

	if (descr && (strlen(descr) > 0)) {
		if (xmlTextWriterWriteFormatAttribute(p->xw, BAD_CAST "label", "%s",
			descr) < 0)
			log_warnx("lldpctl",
			    "cannot add attribute 'label' to element %s", tag);
	}

	p->depth++;
}

static void
xml_attr(struct writer *w, const char *tag, const char *descr, const char *value)
{
	struct xml_writer_private *p = w->priv;

	if (xmlTextWriterWriteFormatAttribute(p->xw, BAD_CAST tag, "%s",
		value ? value : "") < 0)
		log_warnx("lldpctl", "cannot add attribute %s with value %s", tag,
		    value ? value : "(none)");
}

static void
xml_data(struct writer *w, const char *data)
{
	struct xml_writer_private *p = w->priv;
	if (xmlTextWriterWriteString(p->xw, BAD_CAST(data ? data : "")) < 0)
		log_warnx("lldpctl", "cannot add '%s' as data to element",
		    data ? data : "(none)");
}

static void
xml_end(struct writer *w)
{
	struct xml_writer_private *p = w->priv;

	if (xmlTextWriterEndElement(p->xw) < 0)
		log_warnx("lldpctl", "cannot end element");

	if (--p->depth == 0) {
		int failed = 0;

		if (xmlTextWriterEndDocument(p->xw) < 0) {
			log_warnx("lldpctl", "cannot finish document");
			failed = 1;
		}

		xmlFreeTextWriter(p->xw);
		if (!failed) {
			xmlDocDump(p->fh, p->doc);
			fflush(p->fh);
		}
		xmlFreeDoc(p->doc);
	}
}

static void
xml_finish(struct writer *w)
{
	struct xml_writer_private *p = w->priv;
	if (p->depth != 0) {
		log_warnx("lldpctl", "unbalanced tags");
		/* memory leak... */
	}

	free(p);
	free(w);
}

struct writer *
xml_init(FILE *fh)
{

	struct writer *result;
	struct xml_writer_private *priv;

	priv = malloc(sizeof(*priv));
	if (!priv) {
		fatalx("lldpctl", "out of memory");
		return NULL;
	}
	priv->fh = fh;
	priv->depth = 0;

	result = malloc(sizeof(struct writer));
	if (!result) fatalx("lldpctl", "out of memory");

	result->priv = priv;
	result->start = xml_start;
	result->attr = xml_attr;
	result->data = xml_data;
	result->end = xml_end;
	result->finish = xml_finish;

	return result;
}
