/*
 *
 *  Multimedia Messaging Service Daemon - The Next Generation
 *
 *  Copyright (C) 2009, Novell, Inc.
 *	  Author: Tambet Ingo (tambet@gmail.com).
 *		  2009-2019, Red Hat, Inc.
 *		  2012, Lanedo GmbH
 *		  2021, Chris Talbot <chris@talbothome.com>
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 2.1 of the License, or (at your option) any later version.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <glib.h>
#include <gio/gio.h>
#include "service-providers.h"
#include "mms.h"

typedef enum {
  PARSER_TOPLEVEL = 0,
  PARSER_COUNTRY,
  PARSER_PROVIDER,
  PARSER_METHOD_GSM,
  PARSER_METHOD_GSM_APN,
  PARSER_METHOD_CDMA,
  PARSER_DONE,
  PARSER_ERROR
} ParseContextState;

typedef struct
{
  char                            *mccmnc;
  GMarkupParseContext             *ctx;
  char                             buffer[4096];
  MmsdServiceProvidersCallback     callback;
  gpointer                         user_data;
  char                            *text_buffer;
  ParseContextState                state;
  gboolean                         mccmnc_matched;
  char                            *apn;
  char                            *apn_to_check;
  char                            *mmsc;
  char                            *proxy;
} ParseContext;

/*****************************************************************************/

static void
parser_toplevel_start (ParseContext *parse_context,
                       const char   *name,
                       const char  **attribute_names,
                       const char  **attribute_values)
{
  int i;

  if (strcmp (name, "serviceproviders") == 0)
    {
      for (i = 0; attribute_names && attribute_names[i]; i++)
        if (strcmp (attribute_names[i], "format") == 0)
          if (strcmp (attribute_values[i], "2.0"))
            {
              g_warning ("%s: mobile broadband provider database format '%s'"
                         " not supported.",
                         __func__,
                         attribute_values[i]);
              parse_context->state = PARSER_ERROR;
              break;
            }
    }
  else if (strcmp (name, "country") == 0)
    parse_context->state = PARSER_COUNTRY;
}

static void
parser_country_start (ParseContext *parse_context,
                      const char   *name,
                      const char  **attribute_names,
                      const char  **attribute_values)
{
  if (strcmp (name, "provider") == 0)
    parse_context->state = PARSER_PROVIDER;
}

static void
parser_provider_start (ParseContext *parse_context,
                       const char   *name,
                       const char  **attribute_names,
                       const char  **attribute_values)
{
  parse_context->mccmnc_matched = FALSE;
  if (strcmp (name, "gsm") == 0)
    parse_context->state = PARSER_METHOD_GSM;
  else if (strcmp (name, "cdma") == 0)
    parse_context->state = PARSER_METHOD_CDMA;
}

static void
parser_gsm_start (ParseContext *parse_context,
                  const char   *name,
                  const char  **attribute_names,
                  const char  **attribute_values)
{
  int i;

  if (strcmp (name, "network-id") == 0)
    {
      const char *mcc = NULL, *mnc = NULL;

      for (i = 0; attribute_names && attribute_names[i]; i++)
        {
          if (strcmp (attribute_names[i], "mcc") == 0)
            mcc = attribute_values[i];
          else if (strcmp (attribute_names[i], "mnc") == 0)
            mnc = attribute_values[i];
          if (mcc && strlen (mcc) && mnc && strlen (mnc))
            {
              char *mccmnc = g_strdup_printf ("%s%s", mcc, mnc);

              if (strcmp (mccmnc, parse_context->mccmnc) == 0)
                parse_context->mccmnc_matched = TRUE;
              g_free (mccmnc);
              break;
            }
        }
    }
  else if (strcmp (name, "apn") == 0)
    {
      g_clear_pointer (&parse_context->apn, g_free);
      g_clear_pointer (&parse_context->mmsc, g_free);
      g_clear_pointer (&parse_context->proxy, g_free);

      for (i = 0; attribute_names && attribute_names[i]; i++)
        if (strcmp (attribute_names[i], "value") == 0)
          {
            parse_context->state = PARSER_METHOD_GSM_APN;
            parse_context->apn   = g_strstrip (g_strdup (attribute_values[i]));
            break;
          }
    }
}

static void
parser_start_element (GMarkupParseContext *context,
                      const char          *element_name,
                      const char         **attribute_names,
                      const char         **attribute_values,
                      gpointer             user_data,
                      GError             **error)
{
  ParseContext *parse_context = user_data;

  g_clear_pointer (&parse_context->text_buffer, g_free);

  switch (parse_context->state)
    {
    case PARSER_TOPLEVEL:
      parser_toplevel_start (parse_context, element_name, attribute_names, attribute_values);
      break;
    case PARSER_COUNTRY:
      parser_country_start (parse_context, element_name, attribute_names, attribute_values);
      break;
    case PARSER_PROVIDER:
      parser_provider_start (parse_context, element_name, attribute_names, attribute_values);
      break;
    case PARSER_METHOD_GSM:
      parser_gsm_start (parse_context, element_name, attribute_names, attribute_values);
      break;
    case PARSER_METHOD_GSM_APN:
      break;
    case PARSER_METHOD_CDMA:
      break;
    case PARSER_ERROR:
      break;
    case PARSER_DONE:
      break;
    }
}

static void
parser_country_end (ParseContext *parse_context,
                    const char   *name)
{
  if (strcmp (name, "country") == 0)
    {
      g_clear_pointer (&parse_context->text_buffer, g_free);
      parse_context->state = PARSER_TOPLEVEL;
    }
}

static void
parser_provider_end (ParseContext *parse_context,
                     const char   *name)
{
  if (strcmp (name, "provider") == 0)
    {
      g_clear_pointer (&parse_context->text_buffer, g_free);
      parse_context->state = PARSER_COUNTRY;
    }
}

static void
parser_gsm_end (ParseContext *parse_context,
                const char   *name)
{
  if (strcmp (name, "gsm") == 0)
    {
      g_clear_pointer (&parse_context->text_buffer, g_free);
      parse_context->state = PARSER_PROVIDER;
    }
}

static void
parser_gsm_apn_end (ParseContext *parse_context,
                    const char   *name)
{
  if (strcmp (name, "mmsc") == 0)
    {
      g_clear_pointer (&parse_context->mmsc, g_free);
      parse_context->mmsc = g_steal_pointer (&parse_context->text_buffer);
    }
  else if (strcmp (name, "mmsproxy") == 0)
    {
      g_clear_pointer (&parse_context->proxy, g_free);
      parse_context->proxy = g_steal_pointer (&parse_context->text_buffer);
    }
  else if (strcmp (name, "apn") == 0)
    {
      g_clear_pointer (&parse_context->text_buffer, g_free);
      if (parse_context->mccmnc_matched &&
          g_strcmp0 (parse_context->apn_to_check, parse_context->apn) == 0 &&
          parse_context->mmsc != NULL)
        parse_context->state = PARSER_DONE;
      else
        parse_context->state = PARSER_METHOD_GSM;
    }
}

static void
parser_cdma_end (ParseContext *parse_context,
                 const char   *name)
{
  if (strcmp (name, "cdma") == 0)
    {
      g_clear_pointer (&parse_context->text_buffer, g_free);
      parse_context->state = PARSER_PROVIDER;
    }
}

static void
parser_end_element (GMarkupParseContext *context,
                    const char          *element_name,
                    gpointer             user_data,
                    GError             **error)
{
  ParseContext *parse_context = user_data;

  switch (parse_context->state)
    {
    case PARSER_TOPLEVEL:
      break;
    case PARSER_COUNTRY:
      parser_country_end (parse_context, element_name);
      break;
    case PARSER_PROVIDER:
      parser_provider_end (parse_context, element_name);
      break;
    case PARSER_METHOD_GSM:
      parser_gsm_end (parse_context, element_name);
      break;
    case PARSER_METHOD_GSM_APN:
      parser_gsm_apn_end (parse_context, element_name);
      break;
    case PARSER_METHOD_CDMA:
      parser_cdma_end (parse_context, element_name);
      break;
    case PARSER_ERROR:
      break;
    case PARSER_DONE:
      break;
    }
}

static void
parser_text (GMarkupParseContext *context,
             const char          *text,
             gsize                text_len,
             gpointer             user_data,
             GError             **error)
{
  ParseContext *parse_context = user_data;

  g_free (parse_context->text_buffer);
  parse_context->text_buffer = g_strdup (text);
}

static const GMarkupParser parser = {
  .start_element = parser_start_element,
  .end_element   = parser_end_element,
  .text          = parser_text,
  .passthrough   = NULL,
  .error         = NULL,
};

/*****************************************************************************/

static void
finish_parse_context (ParseContext *parse_context,
                      GError       *error)
{
  if (parse_context->callback)
    {
      if (error)
        parse_context
        ->callback (NULL, NULL, NULL, error, parse_context->user_data);
      else
        parse_context->callback (parse_context->apn,
                                 parse_context->mmsc,
                                 parse_context->proxy,
                                 error,
                                 parse_context->user_data);
    }

  g_free (parse_context->mccmnc);
  g_markup_parse_context_free (parse_context->ctx);

  g_free (parse_context->text_buffer);
  g_free (parse_context->apn);
  g_free (parse_context->apn_to_check);
  g_free (parse_context->mmsc);
  g_free (parse_context->proxy);
  g_slice_free (ParseContext, parse_context);
}

static void read_next_chunk (GInputStream *stream,
                             ParseContext *parse_context);

static void
stream_read_cb (GObject      *source_object,
                GAsyncResult *res,
                gpointer      user_data)
{
  GInputStream *stream = G_INPUT_STREAM (source_object);
  ParseContext *parse_context = user_data;
  gssize        len;
  GError        *error = NULL;

  len = g_input_stream_read_finish (stream, res, &error);
  if (len == -1)
    {
      g_prefix_error (&error, "Error reading service provider database: ");
      finish_parse_context (parse_context, error);
      g_clear_error (&error);
      return;
    }

  if (len == 0)
    {
      g_set_error (&error, 1, 1,
                   "Operator ID '%s' with APN '%s' not found in service provider database",
                   parse_context->mccmnc, parse_context->apn_to_check);
      finish_parse_context (parse_context, error);
      g_clear_error (&error);
      return;
    }

  if (!g_markup_parse_context_parse (parse_context->ctx,
                                     parse_context->buffer,
                                     len, &error))
    {
      g_prefix_error (&error, "Error parsing service provider database: ");
      finish_parse_context (parse_context, error);
      g_clear_error (&error);
      return;
    }

  if (parse_context->state == PARSER_DONE)
    {
      finish_parse_context (parse_context, NULL);
      return;
    }

  read_next_chunk (stream, parse_context);
}

static void
read_next_chunk (GInputStream *stream,
                 ParseContext *parse_context)
{
  g_input_stream_read_async (stream,
                             parse_context->buffer,
                             sizeof(parse_context->buffer),
                             G_PRIORITY_DEFAULT,
                             NULL,
                             stream_read_cb,
                             parse_context);
}

static void
file_read_cb (GObject      *source_object,
              GAsyncResult *res,
              gpointer      user_data)
{
  GFile             *file = G_FILE (source_object);
  ParseContext      *parse_context = user_data;
  GFileInputStream  *stream;
  g_autoptr(GError)  error = NULL;

  stream = g_file_read_finish (file, res, &error);
  if (!stream)
    {
      g_prefix_error (&error, "Error opening service provider database: ");
      finish_parse_context (parse_context, error);
      return;
    }

  read_next_chunk (G_INPUT_STREAM (stream), parse_context);

  g_object_unref (stream);
}

/*****************************************************************************/


void
mmsd_service_providers_find_settings (const char                  *service_providers,
                                      const char                  *mccmnc,
                                      const char                  *apn,
                                      MmsdServiceProvidersCallback callback,
                                      gpointer                     user_data)
{
  GFile        *file;
  ParseContext *parse_context;
  parse_context                   = g_slice_new0 (ParseContext);
  parse_context->callback         = callback;
  parse_context->mccmnc           = g_strdup (mccmnc);
  parse_context->apn_to_check     = g_strdup (apn);
  parse_context->user_data        = user_data;
  parse_context->ctx              = g_markup_parse_context_new (&parser, 0, parse_context, NULL);

  file = g_file_new_for_path (service_providers);
  DBG ("mccmnc: %s, apn: %s", parse_context->mccmnc, apn);

  g_file_read_async (file, G_PRIORITY_DEFAULT, NULL, file_read_cb, parse_context);

  g_object_unref (file);
}
