#include "apt-cache-parser.h"
#include <string.h>

#define UBUNTU_WEBAPPS_NAME_KEY "Ubuntu-Webapps-Name"
#define UBUNTU_WEBAPPS_INCLUDES_KEY "Ubuntu-Webapps-Includes"
#define UBUNTU_WEBAPPS_DOMAIN_KEY "Ubuntu-Webapps-Domain"

struct _AptCacheParser {
  /* Current state of the parser */
  AptCacheParserState state;
  
  /* Webapp we are currently building */
  WebappInfo *current_webapp;

  /* List of found webapps */
  GList *webapp_infos;
};

AptCacheParser *
apt_cache_parser_new ()
{
  AptCacheParser *parser;
  
  parser = g_malloc0 (sizeof (AptCacheParser));
  parser->state = APT_CACHE_PARSER_STATE_WANT_PACKAGE;
  
  parser->current_webapp = NULL;
  parser->webapp_infos = NULL;
  
  return parser;
}

static void
apt_cache_parser_free_webapp_infos (AptCacheParser *parser)
{
  GList *w;
  
  for (w = parser->webapp_infos; w != NULL; w = w->next)
    {
      WebappInfo *info;
      
      info = (WebappInfo *)w->data;
      webapp_info_free (info);
    }
  g_list_free (parser->webapp_infos);
  
  parser->webapp_infos = NULL;
}

void
apt_cache_parser_free (AptCacheParser *parser)
{
  apt_cache_parser_free_webapp_infos (parser);
  if (parser->current_webapp != NULL)
    {
      webapp_info_free (parser->current_webapp);
    }
  g_free (parser);
}

gboolean
apt_cache_parser_parse_metadata_field (const gchar *line, gchar **out_key_name, gchar **out_key_value)
{
  const gchar *walk;
  gchar *working_line, *key_name, *key_value;
  guint key_length;
  gboolean parsed;
  
  key_value = NULL;
  key_name = NULL;
  
  working_line = g_strdup (line);

  // Simplification
  working_line = g_strstrip (working_line);
  
  parsed = FALSE;
  for (walk = working_line; *walk != '\0'; walk++)
    {
      if (*walk == ':')
	{
	  parsed = TRUE;
	  break;
	}
    }
  if (parsed == FALSE)
    {
      goto out;
    }

  key_length = walk-working_line;
  
  if (key_length == 0)
    {
      parsed = FALSE;
      goto out;
    }
  
  key_name = g_malloc0 ((key_length+1) * sizeof (gchar));
  strncpy (key_name, working_line, key_length);
  key_name[key_length] = '\0';
  
  // Skip ": "
  key_value = g_strdup(walk + 2 * sizeof (gchar));
  
 out:
  *out_key_name = key_name;
  *out_key_value = key_value;
  g_free (working_line);
  
  return parsed;
}

gboolean
apt_cache_parser_parse_package_name (const gchar *line, gchar **package_name)
{
  gboolean parsed;
  gchar *key;
  
  parsed = apt_cache_parser_parse_metadata_field (line, &key, package_name);
  if (parsed == FALSE)
    {
      return FALSE;
    }
  parsed = (g_strcmp0 (key, "Package") == 0);
  g_free (key);
  
  return parsed;
}

gboolean
apt_cache_parser_parse_application_name (AptCacheParser *parser, const gchar *name)
{
  gboolean has_name;
  
  g_assert (parser->current_webapp);
  
  has_name = (webapp_info_get_application_name (parser->current_webapp) != NULL);

  if (has_name == TRUE)
    {
      g_warning ("We found two webapp name fields in one package record (%s)", name);
      return FALSE;
    }
  webapp_info_set_application_name (parser->current_webapp, name);
  return TRUE;
}

gboolean
apt_cache_parser_parse_application_domain (AptCacheParser *parser, const gchar *name)
{
  gboolean has_name;
  
  g_assert (parser->current_webapp);
  
  has_name = (webapp_info_get_application_domain (parser->current_webapp) != NULL);

  if (has_name == TRUE)
    {
      g_warning ("We found two webapp name fields in one package record (%s)", name);
      return FALSE;
    }
  webapp_info_set_application_domain (parser->current_webapp, name);
  return TRUE;
}

static gboolean
apt_cache_parser_parse_application_includes (AptCacheParser *parser, const gchar *includes_value)
{
  gchar **includes;
  gint i, len;
  gboolean parsed, has_urls;
  
  g_assert (parser->current_webapp);
  
  has_urls = webapp_info_has_urls (parser->current_webapp);
  if (has_urls == TRUE)
    {
      g_warning("We found a webapp with two XB-Ubuntu-Webapp-Includes values (%s), includes_value", includes_value);
      return FALSE;
    }
  parsed = TRUE;
  
  includes = g_strsplit (includes_value, ";", -1);

  len = g_strv_length (includes);
  if (len == 0)
    {
      // We need some includes!
      parsed = FALSE;
      goto out;
    }
  for (i = 0; i < len; i++)
    {
      const gchar *include = (const gchar *)includes[i];
      webapp_info_add_url (parser->current_webapp, include);
    }

 out:
  g_strfreev (includes);
  return parsed;
}

gboolean
apt_cache_parser_parse_metadata_keys (AptCacheParser *parser, const gchar *key, const gchar *value)
{
  gboolean parsed;

  parsed = TRUE;

  if (g_strcmp0 (key, UBUNTU_WEBAPPS_NAME_KEY) == 0)
    {
      parsed = apt_cache_parser_parse_application_name (parser, value);
    }
  else if (g_strcmp0 (key, UBUNTU_WEBAPPS_DOMAIN_KEY) == 0)
    {
      parsed = apt_cache_parser_parse_application_domain (parser, value);
    }
  else if (g_strcmp0 (key, UBUNTU_WEBAPPS_INCLUDES_KEY) == 0)
    {
      parsed = apt_cache_parser_parse_application_includes (parser, value);
    }
  return parsed;
}

gboolean
apt_cache_parser_next_line (AptCacheParser *parser, const gchar *line)
{
  switch (parser->state)
    {
    case APT_CACHE_PARSER_STATE_WANT_PACKAGE:
      {
	gchar *package_name;
	gboolean parsed;

	parsed = apt_cache_parser_parse_package_name (line, &package_name);
	if (parsed == FALSE)
	  {
	    break;
	  }
	if (g_str_has_prefix(package_name, "unity-webapps") == FALSE)
	  {
	    g_free (package_name);
	    break;
	  }
	
	g_message("Investigating webapp: %s \n", package_name);
	parser->current_webapp = webapp_info_new (package_name);
	parser->state = APT_CACHE_PARSER_STATE_WANT_METADATA;
	g_free (package_name);
	break;
      }
    case APT_CACHE_PARSER_STATE_WANT_METADATA:
      {
	// The lifetime of these is a little awkward to follow, but control flow is a little hard in here :) be careful if changing
	gchar *key, *value;
	gboolean parsed;
	gboolean verified;

	if (g_strcmp0 (line, "\n") == 0)
	  {
	    g_warning("Found newline before all of metadata, skipping app");
	    parser->state = APT_CACHE_PARSER_STATE_WANT_PACKAGE;
	    
	    webapp_info_free (parser->current_webapp);
	    parser->current_webapp = NULL;
	    break;
	  }
	
	parsed = apt_cache_parser_parse_metadata_field (line, &key, &value);
	if (parsed == FALSE)
	  {
	    g_warning ("Failed to parse metadata line (%s), skipping package", line);
	    parser->state = APT_CACHE_PARSER_STATE_WANT_PACKAGE;
	    
	    webapp_info_free (parser->current_webapp);
	    parser->current_webapp = NULL;
	    break;
	  }
	parsed = apt_cache_parser_parse_metadata_keys (parser, key, value);
	if (parsed == FALSE)
	  {
	    g_warning ("Invalid metadata, skipping package");
	    parser->state = APT_CACHE_PARSER_STATE_WANT_PACKAGE;
	    
	    webapp_info_free (parser->current_webapp);
	    parser->current_webapp = NULL;
	    
	    g_free (key);
	    g_free (value);
	    break;
	  }
	
	verified = webapp_info_verify (parser->current_webapp);
	if (verified == TRUE)
	  {
	    parser->webapp_infos = g_list_append (parser->webapp_infos, parser->current_webapp);
	    parser->current_webapp = NULL;
	    parser->state = APT_CACHE_PARSER_STATE_WANT_PACKAGE;
	  }
	g_free (key);
	g_free (value);
	break;
      }
    case APT_CACHE_PARSER_STATE_INVALID:
      return FALSE;
    }
  return TRUE;
}
  
GList *
apt_cache_parser_get_webapp_infos (AptCacheParser *parser)
{
  return parser->webapp_infos;
}

gboolean
apt_cache_parser_parse_lines (AptCacheParser *parser,
			      const gchar **lines)
{
  gint i, len;
  
  len = g_strv_length ((gchar **)lines);
  
  for (i = 0; i < len; i++)
    {
      gboolean parsed;
      
      parsed = apt_cache_parser_next_line (parser, lines[i]);
      
      if (parsed == FALSE)
	{
	  return FALSE;
	}
    }
  return TRUE;
}

gboolean
apt_cache_parser_parse_available (AptCacheParser *parser,
				  const gchar *available)
{
  gchar **lines;
  gboolean parsed;
  
  lines = g_strsplit (available, "\n", -1);
  
  parsed = apt_cache_parser_parse_lines (parser, (const gchar **)lines);
  
  g_strfreev (lines);
  
  return parsed;
}

GVariant *
apt_cache_parser_serialize_results (AptCacheParser *parser)
{
  GList *w;
  GVariantBuilder b;
  
  g_variant_builder_init (&b, G_VARIANT_TYPE ("(a(ssa(s)))"));
  g_variant_builder_open (&b, G_VARIANT_TYPE ("a(ssa(s))"));
  
  for (w = parser->webapp_infos; w != NULL; w = w->next)
    {
      WebappInfo *info;
      
      info = (WebappInfo *)w->data;
      g_variant_builder_add_value (&b, webapp_info_serialize (info));
    }
			  
  g_variant_builder_close (&b);
  return g_variant_builder_end (&b);
}
