/*
 * Copyright (C) 2004, 2005 Jean-Yves Lefort
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of Jean-Yves Lefort nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include "config.h"
#include <string.h>
#include <stdlib.h>
#include <glib/gi18n-lib.h>
#include "translate.h"
#include "translate-generic-parser.h"
#include "translate-generic-service.h"

typedef struct
{
  char				*name;
  char				*nick;
  unsigned int			max_chunk_len;
  TranslateGenericGroup		*group;
  GSList			*groups;
} ServiceDefinition;

typedef struct
{
  GMarkupParseContext		*context;
  const char			*filename;

  char				*path;
  ServiceDefinition		*definition;
  GSList			*definitions;
} ParseInfo;

typedef enum
{
  REQUIRED,
  OPTIONAL
} AttributeType;

static void translate_generic_parser_start_element_cb (GMarkupParseContext *context,
						       const char *element_name,
						       const char **attribute_names,
						       const char **attribute_values,
						       gpointer user_data,
						       GError **err);
static void translate_generic_parser_end_element_cb (GMarkupParseContext *context,
						     const char *element_name,
						     gpointer user_data,
						     GError **err);
static void translate_generic_parser_scan_attributes (ParseInfo *info,
						      const char **attribute_names,
						      const char **attribute_values,
						      GError **err,
						      ...);
static void translate_generic_parser_warning (ParseInfo *info,
					      const char *format,
					      ...) G_GNUC_PRINTF(2, 3);
static void translate_generic_parser_set_error (GError **err,
						ParseInfo *info,
						const char *format,
						...) G_GNUC_PRINTF(3, 4);

static void translate_generic_definition_free (ServiceDefinition *definition);

static void translate_generic_parser_handle_http_header (ParseInfo *info,
							 const char **attribute_names,
							 const char **attribute_values,
							 GSList **list,
							 GError **err);
static void translate_generic_http_header_free (TranslateGenericHttpHeader *header);

static void translate_generic_parser_handle_location (ParseInfo *info,
						      const char **attribute_names,
						      const char **attribute_values,
						      TranslateGenericLocation **location,
						      GError **err);
static void translate_generic_location_free (TranslateGenericLocation *location);

void
translate_generic_parse (const char *filename)
{
  GError *err = NULL;
  GIOChannel *channel;
  char *contents;
  gsize length;
  ParseInfo info;
  GMarkupParser parser = {
    translate_generic_parser_start_element_cb,
    translate_generic_parser_end_element_cb,
    NULL,
    NULL,
    NULL
  };

  g_return_if_fail(filename != NULL);

  if (! g_file_test(filename, G_FILE_TEST_IS_REGULAR))
    return;

  channel = g_io_channel_new_file(filename, "r", &err);
  if (! channel)
    {
      g_warning(_("unable to open %s: %s"), filename, err->message);
      g_error_free(err);
      return;
    }
  
  if (g_io_channel_read_to_end(channel, &contents, &length, &err) != G_IO_STATUS_NORMAL)
    {
      g_warning(_("unable to read %s: %s"), filename, err->message);
      g_error_free(err);
      goto end;
    }
  
  info.context = g_markup_parse_context_new(&parser, 0, &info, NULL);
  info.filename = filename;
  info.path = NULL;
  info.definition = NULL;
  info.definitions = NULL;

  if (g_markup_parse_context_parse(info.context, contents, length, &err)
      && g_markup_parse_context_end_parse(info.context, &err))
    {
      GSList *l;
      for (l = info.definitions; l != NULL; l = l->next)
	{
	  ServiceDefinition *definition = l->data;
	  TranslateService *service;

	  service = translate_generic_service_new(definition->name,
						  definition->nick,
						  definition->max_chunk_len,
						  definition->groups);
	  if (! translate_add_service(service))
	    g_warning(_("%s: service \"%s\" already exists, ignored"),
		      filename, translate_service_get_name(service));
	  g_object_unref(service);
	}
    }
  else
    {
      g_warning(_("unable to parse %s: %s"), filename, err->message);
      g_error_free(err);
    }

  g_markup_parse_context_free(info.context);
  g_free(info.path);
  if (info.definition)
    translate_generic_definition_free(info.definition);
  g_slist_foreach(info.definitions, (GFunc) translate_generic_definition_free, NULL);
  g_slist_free(info.definitions);

 end:
  g_io_channel_shutdown(channel, TRUE, NULL);
  g_io_channel_unref(channel);
}

static void
translate_generic_parser_start_element_cb (GMarkupParseContext *context,
					   const char *element_name,
					   const char **attribute_names,
					   const char **attribute_values,
					   gpointer user_data,
					   GError **err)
{
  ParseInfo *info = user_data;
  char *new_path;

  if (info->path)
    {
      new_path = g_strconcat(info->path, "/", element_name, NULL);
      g_free(info->path);
    }
  else
    new_path = g_strconcat("/", element_name, NULL);

  info->path = new_path;

  if (! strcmp(info->path, "/services"))
    translate_generic_parser_scan_attributes(info,
					     attribute_names,
					     attribute_values,
					     err,
					     NULL);
  else if (! strcmp(info->path, "/services/custom-language"))
    {
      const char *tag;
      const char *name;

      translate_generic_parser_scan_attributes(info,
					       attribute_names,
					       attribute_values,
					       err,
					       "tag", REQUIRED, &tag,
					       "name", REQUIRED, &name,
					       NULL);

      if (! *err)
	{
	  if (! translate_add_language(tag, _(name)))
	    translate_generic_parser_warning(info, _("language \"%s\" already exists, ignored"), tag);
	}
    }
  else if (! strcmp(info->path, "/services/service"))
    {
      const char *name;
      const char *nick;
      const char *max_chunk_len;

      translate_generic_parser_scan_attributes(info,
					       attribute_names,
					       attribute_values,
					       err,
					       "name", REQUIRED, &name,
					       "nick", OPTIONAL, &nick,
					       "max-chunk-len", OPTIONAL, &max_chunk_len,
					       NULL);

      if (! *err)
	{
	  unsigned int max_chunk_len_i = 1000; /* keep in sync with services.dtd */

	  if (max_chunk_len)
	    {
	      if (! *max_chunk_len)
		translate_generic_parser_set_error(err, info, _("max-chunk-len is empty"));
	      else if (strspn(max_chunk_len, "0123456789") != strlen(max_chunk_len))
		translate_generic_parser_set_error(err, info, _("max-chunk-len \"%s\" is not an unsigned integer"), max_chunk_len);
	      else
		max_chunk_len_i = atoi(max_chunk_len);
	    }

	  if (! *err)
	    {
	      info->definition = g_new0(ServiceDefinition, 1);
	      info->definition->name = g_strdup(name);
	      info->definition->nick = g_strdup(nick ? nick : name);
	      info->definition->max_chunk_len = max_chunk_len_i;
	    }
	}
    }
  else if (! strcmp(info->path, "/services/service/group"))
    {
      translate_generic_parser_scan_attributes(info,
					       attribute_names,
					       attribute_values,
					       err,
					       NULL);

      if (! *err)
	{
	  info->definition->group = g_new0(TranslateGenericGroup, 1);
	  info->definition->group->ref_count = 1;
	  info->definition->group->service_tags = g_hash_table_new_full(translate_ascii_strcase_hash,
									translate_ascii_strcase_equal,
									g_free,
									g_free);
	}
    }
  else if (! strcmp(info->path, "/services/service/group/language"))
    {
      const char *tag;
      const char *service_tag;
      const char *to;

      translate_generic_parser_scan_attributes(info,
					       attribute_names,
					       attribute_values,
					       err,
					       "tag", REQUIRED, &tag,
					       "service-tag", OPTIONAL, &service_tag,
					       "to", OPTIONAL, &to,
					       NULL);

      if (! *err)
	{
	  TranslateGenericLanguage *language;
	  
	  language = g_new(TranslateGenericLanguage, 1);
	  language->tag = g_strdup(tag);
	  language->target_tags = to ? g_strsplit(to, ",", 0) : NULL;

	  info->definition->group->languages = g_slist_append(info->definition->group->languages, language);
	  if (service_tag)
	    g_hash_table_insert(info->definition->group->service_tags,
				g_strdup(tag),
				g_strdup(service_tag));
	}
    }
  else if (! strcmp(info->path, "/services/service/group/http-header"))
    translate_generic_parser_handle_http_header(info,
						attribute_names,
						attribute_values,
						&info->definition->group->http_headers,
						err);
  else if (! strcmp(info->path, "/services/service/group/text-translation"))
    {
      if (info->definition->group->text_location)
	translate_generic_parser_warning(info, _("element \"text-translation\" already specified"));
      else
	translate_generic_parser_handle_location(info,
						 attribute_names,
						 attribute_values,
						 &info->definition->group->text_location,
						 err);
    }
  else if (! strcmp(info->path, "/services/service/group/text-translation/pre-marker"))
    {
      const char *text;

      translate_generic_parser_scan_attributes(info,
					       attribute_names,
					       attribute_values,
					       err,
					       "text", REQUIRED, &text,
					       NULL);

      if (! *err)
	info->definition->group->text_pre_markers = g_slist_append(info->definition->group->text_pre_markers, g_strdup(text));
    }
  else if (! strcmp(info->path, "/services/service/group/text-translation/error-marker"))
    {
      const char *text;

      translate_generic_parser_scan_attributes(info,
					       attribute_names,
					       attribute_values,
					       err,
					       "text", REQUIRED, &text,
					       NULL);

      if (! *err)
	info->definition->group->text_error_markers = g_slist_append(info->definition->group->text_error_markers, g_strdup(text));
    }
  else if (! strcmp(info->path, "/services/service/group/text-translation/post-marker"))
    {
      if (info->definition->group->text_post_marker)
	translate_generic_parser_warning(info, _("element \"post-marker\" already specified"));
      else
	{
	  const char *text;
	  
	  translate_generic_parser_scan_attributes(info,
						   attribute_names,
						   attribute_values,
						   err,
						   "text", REQUIRED, &text,
						   NULL);

	  if (! *err)
	    info->definition->group->text_post_marker = g_strdup(text);
	}
    }
  else if (! strcmp(info->path, "/services/service/group/text-translation/http-header"))
    translate_generic_parser_handle_http_header(info,
						attribute_names,
						attribute_values,
						&info->definition->group->text_location->http_headers,
						err);
  else if (! strcmp(info->path, "/services/service/group/web-page-translation"))
    {
      if (info->definition->group->web_page_location)
	translate_generic_parser_warning(info, _("element \"web-page-translation\" already specified"));
      else
	translate_generic_parser_handle_location(info,
						 attribute_names,
						 attribute_values,
						 &info->definition->group->web_page_location,
						 err);
    }
  else if (! strcmp(info->path, "/services/service/group/web-page-translation/http-header"))
    translate_generic_parser_handle_http_header(info,
						attribute_names,
						attribute_values,
						&info->definition->group->web_page_location->http_headers,
						err);
  else
    translate_generic_parser_warning(info, _("unknown element \"%s\", ignored"), element_name);
}

static void
translate_generic_parser_end_element_cb (GMarkupParseContext *context,
					 const char *element_name,
					 gpointer user_data,
					 GError **err)
{
  ParseInfo *info = user_data;
  char *slash;

  g_return_if_fail(info->path != NULL);

  if (! strcmp(info->path, "/services/service"))
    {
      info->definitions = g_slist_append(info->definitions, info->definition);
      info->definition = NULL;
    }
  else if (! strcmp(info->path, "/services/service/group"))
    {
      info->definition->groups = g_slist_append(info->definition->groups, info->definition->group);
      info->definition->group = NULL;
    }
  
  slash = strrchr(info->path, '/');
  if (slash)
    *slash = 0;
  else
    {
      g_free(info->path);
      info->path = NULL;
    }
}

static void
translate_generic_parser_scan_attributes (ParseInfo *info,
					  const char **attribute_names,
					  const char **attribute_values,
					  GError **err,
					  ...)
{
  va_list args;
  const char *name;
  GSList *seen_attributes = NULL;
  GSList *l;
  int i;
  gboolean found;

  g_return_if_fail(info != NULL);
  g_return_if_fail(attribute_names != NULL);
  g_return_if_fail(attribute_values != NULL);

  va_start(args, err);

  while ((name = va_arg(args, const char *)))
    {
      AttributeType type;
      const char **ptr;

      type = va_arg(args, AttributeType);
      
      ptr = va_arg(args, const char **);
      g_return_if_fail(ptr != NULL);

      *ptr = NULL;
      found = FALSE;

      for (i = 0; attribute_names[i] && attribute_values[i]; i++)
	if (! strcmp(attribute_names[i], name))
	  {
	    if (found)
	      translate_generic_parser_warning(info, _("attribute \"%s\" already specified"), name);
	    else
	      {
		seen_attributes = g_slist_append(seen_attributes, (gpointer) name);
	    
		*ptr = attribute_values[i];
		found = TRUE;
	      }
	  }

      if (! found && type == REQUIRED)
	{
	  translate_generic_parser_set_error(err, info, _("required attribute \"%s\" missing"), name);
	  goto end;
	}
    }

  for (i = 0; attribute_names[i] && attribute_values[i]; i++)
    {
      found = FALSE;
      for (l = seen_attributes; l != NULL && ! found; l = l->next)
	if (! strcmp(l->data, attribute_names[i]))
	  found = TRUE;

      if (! found)
	translate_generic_parser_warning(info, _("unknown attribute \"%s\", ignored"), attribute_names[i]);
    }
  
 end:
  va_end(args);

  g_slist_free(seen_attributes);
}

static void
translate_generic_parser_warning (ParseInfo *info, const char *format, ...)
{
  va_list args;
  char *message;
  int line_number;

  g_return_if_fail(info != NULL);
  g_return_if_fail(format != NULL);

  va_start(args, format);
  message = g_strdup_vprintf(format, args);
  va_end(args);

  g_markup_parse_context_get_position(info->context, &line_number, NULL);

  g_warning(_("%s: around line %i: %s"), info->filename, line_number, message);
  g_free(message);
}

static void
translate_generic_parser_set_error (GError **err,
				    ParseInfo *info,
				    const char *format,
				    ...)
{
  va_list args;
  char *message;
  int line_number;

  g_return_if_fail(info != NULL);
  g_return_if_fail(format != NULL);

  va_start(args, format);
  message = g_strdup_vprintf(format, args);
  va_end(args);

  g_markup_parse_context_get_position(info->context, &line_number, NULL);

  g_set_error(err, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, "around line %i: %s", line_number, message);
  g_free(message);
}

static void
translate_generic_definition_free (ServiceDefinition *definition)
{
  g_return_if_fail(definition != NULL);

  g_free(definition->name);
  g_free(definition->nick);
  if (definition->group)
    translate_generic_group_unref(definition->group);
  g_slist_foreach(definition->groups, (GFunc) translate_generic_group_unref, NULL);
  g_slist_free(definition->groups);
  g_free(definition);
}

TranslateGenericGroup *
translate_generic_group_ref (TranslateGenericGroup *group)
{
  g_return_val_if_fail(group != NULL, NULL);

  g_atomic_int_inc(&group->ref_count);

  return group;
}

void
translate_generic_group_unref (TranslateGenericGroup *group)
{
  g_return_if_fail(group != NULL);
  
  if (g_atomic_int_dec_and_test(&group->ref_count))
    {
      GSList *l;

      for (l = group->languages; l != NULL; l = l->next)
	{
	  TranslateGenericLanguage *language = l->data;

	  g_free(language->tag);
	  g_strfreev(language->target_tags);
	  g_free(language);
	}
      g_slist_free(group->languages);

      g_hash_table_destroy(group->service_tags);
      
      g_slist_foreach(group->http_headers, (GFunc) translate_generic_http_header_free, NULL);
      g_slist_free(group->http_headers);

      if (group->text_location)
	translate_generic_location_free(group->text_location);

      g_slist_foreach(group->text_pre_markers, (GFunc) g_free, NULL);
      g_slist_free(group->text_pre_markers);

      g_free(group->text_post_marker);

      g_slist_foreach(group->text_error_markers, (GFunc) g_free, NULL);
      g_slist_free(group->text_error_markers);

      if (group->web_page_location)
	translate_generic_location_free(group->web_page_location);

      g_free(group);
    }
}

const char *
translate_generic_group_get_service_tag (TranslateGenericGroup *group,
					 const char *tag)
{
  const char *service_tag;

  g_return_val_if_fail(group != NULL, NULL);
  g_return_val_if_fail(tag != NULL, NULL);

  service_tag = g_hash_table_lookup(group->service_tags, tag);
  
  return service_tag ? service_tag : tag;
}

void
translate_generic_group_foreach_pair (TranslateGenericGroup *group,
				      TranslateGenericGroupForeachPairFunc func,
				      gpointer user_data)
{
  GSList *l;

  g_return_if_fail(group != NULL);
  g_return_if_fail(func != NULL);

  for (l = group->languages; l != NULL; l = l->next)
    {
      TranslateGenericLanguage *language = l->data;

      if (language->target_tags)
	{
	  int i;
	  
	  for (i = 0; language->target_tags[i]; i++)
	    if (! strcmp(language->target_tags[i], "*"))
	      {
		GSList *m;
		
		for (m = group->languages; m != NULL; m = m->next)
		  {
		    TranslateGenericLanguage *to = m->data;
		    
		    if (g_ascii_strcasecmp(language->tag, to->tag))
		      {
			if (! func(language->tag, to->tag, user_data))
			  return;
		      }
		  }
	      }
	    else
	      {
		if (! func(language->tag, language->target_tags[i], user_data))
		  return;
	      }
	}
    }
}

static void
translate_generic_parser_handle_http_header (ParseInfo *info,
					     const char **attribute_names,
					     const char **attribute_values,
					     GSList **list,
					     GError **err)
{
  const char *name;
  const char *value;

  g_return_if_fail(info != NULL);
  g_return_if_fail(attribute_names != NULL);
  g_return_if_fail(attribute_values != NULL);
  g_return_if_fail(list != NULL);

  translate_generic_parser_scan_attributes(info,
					   attribute_names,
					   attribute_values,
					   err,
					   "name", REQUIRED, &name,
					   "value", REQUIRED, &value,
					   NULL);

  if (! *err)
    {
      TranslateGenericHttpHeader *header;

      header = g_new(TranslateGenericHttpHeader, 1);
      header->name = g_strdup(name);
      header->value = g_strdup(value);

      *list = g_slist_append(*list, header);
    }
}

static void
translate_generic_http_header_free (TranslateGenericHttpHeader *header)
{
  g_return_if_fail(header != NULL);

  g_free(header->name);
  g_free(header->value);
  g_free(header);
}

static void
translate_generic_parser_handle_location (ParseInfo *info,
					  const char **attribute_names,
					  const char **attribute_values,
					  TranslateGenericLocation **location,
					  GError **err)
{
  const char *url;
  const char *post;
  const char *content_type;

  g_return_if_fail(info != NULL);
  g_return_if_fail(attribute_names != NULL);
  g_return_if_fail(attribute_values != NULL);
  g_return_if_fail(location != NULL);
  
  translate_generic_parser_scan_attributes(info,
					   attribute_names,
					   attribute_values,
					   err,
					   "url", REQUIRED, &url,
					   "post", OPTIONAL, &post,
					   "content-type", OPTIONAL, &content_type,
					   NULL);

  if (! *err)
    {
      *location = g_new0(TranslateGenericLocation, 1);
      (*location)->url = g_strdup(url);
      (*location)->post = g_strdup(post);
      (*location)->content_type = g_strdup(content_type ? content_type : "application/x-www-form-urlencoded");
    }
}

static void
translate_generic_location_free (TranslateGenericLocation *location)
{
  g_return_if_fail(location != NULL);

  g_free(location->url);
  g_free(location->post);
  g_free(location->content_type);
  g_slist_foreach(location->http_headers, (GFunc) translate_generic_http_header_free, NULL);
  g_slist_free(location->http_headers);
  g_free(location);
}
