#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <ctype.h>
#include <sys/mman.h>
#include <fcntl.h>

#include <glib.h>
#include <libxml/tree.h>
#include <libxml/parser.h>
#include <unistd.h>
#include <string.h>

/* this file contains frequently used macros and functions,
 for xml parsing/generation */

#define for_each_node(parent, child) \
  for (xmlNodePtr child = parent->xmlChildrenNode; child != 0; child = child->next)

#define match_node(_node, _name) \
  (_node->type == XML_ELEMENT_NODE && !strcmp((gchar*)_node->name, _name))

#define match_comment(_node) \
  (_node->type == XML_COMMENT_NODE)

/* load xml file */
static __inline__ gint xml_load_doc(const gchar* file, xmlDocPtr* d, xmlNodePtr* r)
{
  if (access(file, R_OK) < 0)
    return 1;
  *d = xmlReadFile(file, 0, XML_PARSE_NOWARNING|XML_PARSE_NOERROR|XML_PARSE_NONET);
  if (*d == 0)
    return 1;
  *r = xmlDocGetRootElement(*d);
  if (*r == 0)
  {
    xmlFreeDoc(*d);
    return 1;
  }
  return 0;
}

#define xml_load_doc_or(file) \
  xmlDocPtr d=0; xmlNodePtr r=0; \
  if (xml_load_doc(file, &d, &r))

static __inline__ void xml_set_prop(xmlNodePtr n, const gchar* name, const gchar* value)
{
  xmlSetProp(n, BAD_CAST name, BAD_CAST value);
}

static __inline__ void xml_set_cont(xmlNodePtr n, const gchar* cont)
{
  xmlNodeSetContent(n, BAD_CAST cont);
}

/* get property from xml node */
static __inline__ gchar* xml_get_prop(xmlNodePtr n, const gchar* name)
{
  xmlChar* prop = xmlGetProp(n, BAD_CAST name);
  if (prop == 0)
    return 0;
  gchar* dup = g_strdup((gchar*)prop);
  xmlFree(prop);
  return dup;
}

static __inline__ gchar* xml_get_cont(xmlDocPtr d, xmlNodePtr n)
{
  xmlChar* str = xmlNodeListGetString(d, n->xmlChildrenNode, 1);
  if (str == 0)
    return 0;
  gchar* dup = g_strdup((gchar*)str);
  xmlFree(str);
  return dup;
}

static __inline__ gint xml_get_prop_cont(xmlDocPtr d, xmlNodePtr n, const gchar* name, gchar** p, gchar** c)
{
  gchar* id = xml_get_prop(n, name);
  gchar* cont = xml_get_cont(d, n);
  if (id == 0 || cont == 0)
  {
    g_free(id);
    g_free(cont);
    return 1;
  }
  *p = id;
  *c = cont;
  return 0;
}

/* this program parses text files to find occurances of wrap strings
 * and add them to xml file with their translations. It supports
 * grouping of strings. Group can be started by <@Group Name@> string.
 */

struct dict_entry {
  gchar* original;
  gchar* translated;
  gint type; /* 0 string, 1 group, 2 disabled */
};

static GSList* _old_dict = 0;
static GSList* _new_dict = 0;

/* load translations from file */
static gint lang_load(const gchar* file)
{
  xml_load_doc_or (file)
    return 1;
  for_each_node(r, s)
  {
    if (!match_node(s, "e"))
      continue;
    gchar *original = xml_get_prop(s, "s");
    gchar *translated = xml_get_cont(d, s);
    if (!original)
      continue;
    struct dict_entry* e = g_new0(struct dict_entry, 1);
    e->original = original;
    e->translated = (gchar*)(translated?translated:"");
    _old_dict = g_slist_prepend(_old_dict, e);
  }
  _old_dict = g_slist_reverse(_old_dict);
  xmlFreeDoc(d);
  return 0;
}

static void lang_save(const gchar* file, const gchar* code)
{
  xmlDocPtr d = xmlNewDoc(BAD_CAST "1.0");
  xmlCreateIntSubset(d, BAD_CAST "lang", NULL, BAD_CAST "lang.dtd");
  xmlNodePtr r = xmlNewNode(NULL, BAD_CAST "lang");
  xmlNewProp(r, BAD_CAST "code", BAD_CAST code);
  xmlDocSetRootElement(d, r);

  GSList* l;
  for (l=_new_dict; l!=0; l=l->next)
  {
    struct dict_entry* e = (struct dict_entry*)l->data;
    if (e->type == 1)
    {
      xmlAddChild(r, xmlNewComment(BAD_CAST e->original));
    }
    else if (e->type == 0)
    {
      xmlNodePtr n = xmlNewChild(r, NULL, BAD_CAST "e", BAD_CAST e->translated);
      xmlNewProp(n, BAD_CAST "s", BAD_CAST e->original);
    }
  }

  xmlSaveFormatFileEnc(file, d, "UTF-8", 1);
  xmlFreeDoc(d);
}

static struct dict_entry* add_group(gchar* msg)
{
  struct dict_entry* e = g_new0(struct dict_entry, 1);
  e->original = msg;
  e->type = 1;
  _new_dict = g_slist_append(_new_dict, e);
  return e;
}

static void add(gchar* msg)
{
  GSList* l;
  gchar* translation = "";
  /* search it in the _new_dict */
  for (l=_new_dict; l!=0; l=l->next)
  {
    struct dict_entry* e = (struct dict_entry*)l->data;
    /* we already have this translation */
    if (!strcmp(e->original, msg))
      return;
  }
  /* search it in the _old_dict */
  for (l=_old_dict; l!=0; l=l->next)
  {
    struct dict_entry* e = (struct dict_entry*)l->data;
    /* ok, we have this already translated, so use it */
    if (!strcmp(e->original, msg))
      translation = e->translated;
  }
  
  struct dict_entry* e = g_new0(struct dict_entry, 1);
  e->original = msg;
  e->translated = translation;
  _new_dict = g_slist_append(_new_dict, e);
}

/* 0 = success, 1 = error, 2 = empty */
static gint parse_buffer(gchar* b, gsize s)
{
  gchar* c = b;
  gint is_empty = 1;
  while (1)
  {
    /* end of buffer */
   next:
    if ((c-b) >= s)
      break;
    else if (!strncmp(c, "I(\"", 3))
    {
      c += 3;
      gchar* msg = c;
      while (1)
      {
        if ((c-b) >= s)
          return 1;
        else if (c[0] == '"' && c[-1] != '\\' && c[1] == ')')
        {
          msg = g_strndup(msg, c-msg);
          add(msg);
          is_empty = 0;
          c += 2;
          goto next;
        }
        else
          c++;
      }
    }
    else if (!strncmp(c, "<@", 2))
    {
      c += 2;
      gchar* msg = c;
      while (1)
      {
        if ((c-b) >= s)
          return 1;
        else if (c[0] == '@' && c[1] == '>')
        {
          msg = g_strndup(msg, c-msg);
          add_group(msg); /* comment group */
          is_empty = 0;
          c += 2;
          goto next;
        }
        else
          c++;
      }
    }
    else
      c++;
  }
  return is_empty?2:0;
}

int main(gint ac, gchar* av[])
{
  if (ac < 3)
  {
    printf("usage: langup <lang.xml> <code> <source.php> ...\n");
    return 1;
  }

  lang_load(av[1]); /* don't need error */

  for (gint i=3; i<ac; i++)
  {
    gint fd = open(av[i], O_RDONLY);
    if (fd == -1)
    {
      perror("open");
      return 1;
    }
    gsize s = lseek(fd, 0, SEEK_END);
    gchar* a = (gchar*)mmap(0, s+1, PROT_READ, MAP_PRIVATE, fd, 0);
    if (a == (gchar*)-1)
    {
      close(fd);
      perror("mmap");
      return 1;
    }

    struct dict_entry* e = add_group(av[i]); /* file group */
    gint rv = parse_buffer(a, s);
    if (rv == 2)
      e->type = 2;

    munmap(a, s+1);
    close(fd);
  }

  lang_save(av[1], av[2]);
  return 0;
}

