/* header */

#include <glib.h>
#include <libxml/tree.h>

typedef struct _page page;

struct _page
{
  char* title_text;

  xmlDocPtr doc;
  xmlNodePtr head;
  xmlNodePtr body;
  xmlNodePtr title;
  xmlNodePtr content;
};

extern page* page_new(const char* title);
extern void page_emit(page* p);

extern xmlNodePtr add_node(xmlNodePtr parent, const char* name, const char* cont, ...);
extern void add_text(xmlNodePtr parent, const char* text);
extern void set_attr(xmlNodePtr node, const char* name, const char* value);
extern void page_add_meta(page* p, const char* name, const char* content);
extern xmlNodePtr add_link(xmlNodePtr parent, const char* href, const char* text, 
  const char* title);
extern xmlNodePtr add_node_id(xmlNodePtr parent, const char* name, const char* id);
extern xmlNodePtr add_node_class(xmlNodePtr parent, const char* name, const char* class);
extern xmlNodePtr add_hidden_input(xmlNodePtr parent, const char* name, const char* data);
extern void add_text_date(xmlNodePtr parent, time_t secs, char *format);
extern void add_span_age(xmlNodePtr parent, time_t t, time_t max_relative, char *format);
extern void add_html(xmlNodePtr parent, const char* html);

/* code */

#include <string.h>

static xmlChar* ensure_utf8(const char* str)
{
  char* good_str;

  if (str == NULL)
    return NULL;

  // don't do anything with valid utf8
  if (g_utf8_validate(str, -1, NULL))
    return BAD_CAST str;

  // convert string to UTF-8, fixing invalid characters in the process
  good_str = g_convert_with_fallback(str, -1, "UTF-8", "UTF-8", "_", 
    NULL, NULL, NULL);
  if (good_str == NULL)
    return BAD_CAST "[conversion failed]";

  return BAD_CAST good_str;
}

xmlNodePtr add_node(xmlNodePtr parent, const char* name, const char* cont, ...)
{
  xmlNodePtr node;
  va_list args;

  node = xmlNewNode(NULL, BAD_CAST ensure_utf8(name));
  if (parent)
    xmlAddChild(parent, node);

  va_start(args, cont);
  while (1) {
    char *prop_name, *prop_value;
      
    prop_name = va_arg(args, char*);
    if (prop_name == 0)
      break;
    prop_value = va_arg(args, char*);
    if (prop_value == 0)
      continue;
    set_attr(node, prop_name, prop_value);
  }
  va_end(args);

  if (cont)
    add_text(node, cont);

  return node;
}

void add_text(xmlNodePtr parent, const char* text)
{
  xmlAddChild(parent, xmlNewText(ensure_utf8(text)));
}

void set_attr(xmlNodePtr node, const char* name, const char* value)
{
  xmlSetProp(node, ensure_utf8(name), ensure_utf8(value));
}

void page_add_meta(page* p, const char* name, const char* content)
{
  add_node(p->head, "meta", NULL, "name", name, "content", content, 0);
}

xmlNodePtr add_link(xmlNodePtr parent, const char* href, const char* text, 
  const char* title)
{
  return add_node(parent, "a", text, "href", href, "title", title, 0);
}

xmlNodePtr add_node_id(xmlNodePtr parent, const char* name, const char* id)
{
  return add_node(parent, name, NULL, "id", id, 0);
}

xmlNodePtr add_node_class(xmlNodePtr parent, const char* name, const char* class)
{
  return add_node(parent, name, NULL, "class", class, 0);
}

xmlNodePtr add_hidden_input(xmlNodePtr parent, const char* name, const char* data)
{
  return add_node(parent, "input", NULL, "type", "hidden", "name", name, "value", data, 0);
}

void add_html(xmlNodePtr parent, const char* html)
{
  int len = strlen(html)+8;
  char* buffer = malloc(len);
  snprintf(buffer, len, "<r>%s</r>", ensure_utf8(html));
  xmlDocPtr doc = xmlReadMemory(buffer, len-1, 0, 0, 
    XML_PARSE_NOWARNING|XML_PARSE_NOERROR|XML_PARSE_NONET);
  if (doc == 0)
    return;
  xmlNodePtr nl = xmlDocCopyNodeList(parent->doc, xmlDocGetRootElement(doc)->children);
  xmlAddChildList(parent, nl);
  xmlFreeDoc(doc);
}

page* page_new(const char* title)
{
  xmlNodePtr root;
  page* p = malloc(sizeof(page));

  p->doc = xmlNewDoc(BAD_CAST "1.0");
  xmlCreateIntSubset(p->doc, BAD_CAST "html", 
    BAD_CAST "-//W3C//DTD XHTML 1.0 Strict//EN",
    BAD_CAST "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd");

  root = add_node(NULL, "html", NULL, "lang", "en", 0);
  xmlDocSetRootElement(p->doc, root);

  p->head = add_node(root, "head", NULL, 0);
  p->title = add_node(p->head, "title", title, 0);
  p->body = add_node(root, "body", NULL, 0);

  page_add_meta(p, "generator", g_strdup_printf("cgit v%s", "1.0"));
  add_node(p->head, "link", 0, "rel", "stylesheet", "type", "text/css", "href", "style.css", 0);

  return p;
}

void page_emit(page* p)
{
  char *buf, *out;
  int size;

  xmlDocDumpFormatMemoryEnc(p->doc, (xmlChar**)&buf, &size, "UTF-8", 1);
  /* remove <?xml ?> line to make IE happy */
  out = strchr(buf, '\n');
  if (out == NULL)
    return;
  out++;
  size -= (out - buf);

  /* send headers */
  fprintf(stdout, 
    "Content-Type: text/html; charset=UTF-8\r\n"
    "Content-Length: %d\r\n"
    "\r\n", 
    size);
  fwrite(out, size, 1, stdout);
  fflush(stdout);
}

int main()
{
  page* p = page_new("test");
  add_node(p->body, "div", "Content text", NULL);
  page_emit(p);
}

