/*
 *
 *  Multimedia Messaging Service Daemon - The Next Generation
 *
 *  Copyright (C) 2018 Purism SPC
 *                2020-2021 Chris Talbot <chris@talbothome.com>
 *
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License version 2 as
 *  published by the Free Software Foundation.
 *
 *  This program 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 General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */


#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <errno.h>
#include <gio/gio.h>
#include <stdlib.h>
#include <libmm-glib.h>
#include "mms.h"
#include "dbus.h"
#include "phone-utils.h"
#include "service-providers.h"

// SETTINGS_STORE is synced with services.c
#define SETTINGS_STORE "mms"

// SETTINGS_GROUP is where we store our settings for this plugin
#define SETTINGS_GROUP "Modem Manager"

//Identifier of the plugin
#define IDENTIFIER     "modemmanager"
//dbus default timeout for Modem
#define MMSD_MM_MODEM_TIMEOUT       20000

#define MMS_MODEMMANAGER_INTERFACE      MMS_SERVICE ".ModemManager"


enum {
  MMSD_MM_STATE_NO_MANAGER,
  MMSD_MM_STATE_MANAGER_FOUND,
  MMSD_MM_STATE_NO_MODEM,
  MMSD_MM_STATE_MODEM_FOUND,
  MMSD_MM_STATE_NO_MESSAGING_MODEM,
  MMSD_MM_STATE_MODEM_DISABLED,
  MMSD_MM_STATE_READY
} e_mmsd_connection;

enum {
  MMSD_MM_MODEM_MMSC_MISCONFIGURED,              //the MMSC is the default value
  MMSD_MM_MODEM_NO_BEARERS_ACTIVE,              //The Modem has no bearers
  MMSD_MM_MODEM_INTERFACE_DISCONNECTED,         //mmsd found the right bearer, but it is disconnected
  MMSD_MM_MODEM_INCORRECT_APN_CONNECTED,        //no APN is connected that matches the settings
  MMSD_MM_MODEM_FUTURE_CASE_DISCONNECTED,       //Reserved for future case
  MMSD_MM_MODEM_CONTEXT_ACTIVE                  //No error, context activated properly
} mm_context_connection;

struct modem_data
{
  struct mms_service *service;       //Do not mess with the guts of this in plugin.c!
  GKeyFile *modemsettings;
  //These are pulled from the settings file, and can be set via the Dbus
  char                    *message_center;                      // The mmsc
  char                    *mms_apn;                             // The MMS APN
  char                    *MMS_proxy;                           // I *think* this is where mms proxy goes?
  char                    *default_number;
  // These are for settings the context (i.e. APN settings and if the bearer is active)
  char                    *context_interface;                   // Bearer interface here (e.g. "wwan0")
  char                    *context_path;                        // Dbus path of the bearer
  gboolean                 context_active;                      // Whether the context is active
  //The Bus org.ofono.mms.ModemManager
  guint                    owner_id;
  guint                    registration_id;
  gulong                   modem_state_watch_id;
  gulong                   sms_wap_signal_id;
  //These are modem manager related settings
  MMManager               *mm;
  guint                    mm_watch_id;
  MMObject                *object;
  MMModem                 *modem;
  char                    *path;
  MMSim                   *sim;
  gchar                   *imsi;
  gchar                   *mccmnc;
  char                    *registered_imsi;
  MMModemMessaging        *modem_messaging;
  MMModemState             state;
  GPtrArray               *device_arr;
  gboolean                 modem_available;
  gboolean                 modem_ready;
  gboolean                 manager_available;
  gboolean                 plugin_registered;
  gboolean                 auto_process_on_connection;
  gboolean                 autoprocess_sms_wap;
  gboolean                 get_all_sms_timeout;
};

typedef struct
{
  MMObject        *object;
  MMModem         *modem;
  MMSim           *sim;
} MmsdDevice;

/* Introspection data for the service we are exporting */
static const gchar introspection_xml_modemmanager[] =
  "<node>"
  "  <interface name='org.ofono.mms.ModemManager'>"
  "    <annotation name='org.ofono.mms.ModemManager' value='OnInterface'/>"
  "    <annotation name='org.ofono.mms.ModemManager' value='AlsoOnInterface'/>"
  "    <method name='PushNotify'>"
  "      <annotation name='org.ofono.mms.ModemManager' value='OnMethod'/>"
  "      <arg type='ay' name='smswap' direction='in'/>"
  "    </method>"
  "    <method name='ViewSettings'>"
  "      <arg type='a{sv}' name='properties' direction='out'/>"
  "    </method>"
  "    <method name='ChangeSettings'>"
  "      <arg type='s' name='setting' direction='in'/>"
  "      <arg type='v' name='value' direction='in'/>"
  "    </method>"
  "    <method name='ChangeAllSettings'>"
  "      <arg type='a{sv}' name='options' direction='in'/>"
  "    </method>"
  "    <method name='ProcessMessageQueue'>"
  "      <annotation name='org.ofono.mms.ModemManager' value='OnMethod'/>"
  "    </method>"
  "    <signal name='BearerHandlerError'>"
  "      <annotation name='org.ofono.mms.ModemManager' value='Onsignal'/>"
  "      <arg type='h' name='ContextError'/>"
  "    </signal>"
  "    <signal name='SettingsChanged'>"
  "      <annotation name='org.ofono.mms.ModemManager' value='Onsignal'/>"
  "      <arg type='sss' name='Settings'/>"
  "    </signal>"
  "  </interface>"
  "</node>";


static GDBusNodeInfo *introspection_data = NULL;
struct modem_data *modem;

static void mmsd_mm_state (int state);
static void mmsd_modem_available (void);
static void mmsd_modem_unavailable (void);
static void free_device (MmsdDevice *device);
static void bearer_handler (mms_bool_t active,
                            void      *user_data);
static int set_context (void);
static void cb_mm_manager_new (GDBusConnection *connection,
                               GAsyncResult    *res,
                               gpointer         user_data);
static void mm_appeared_cb (GDBusConnection *connection,
                            const gchar     *name,
                            const gchar     *name_owner,
                            gpointer         user_data);
static void mm_vanished_cb (GDBusConnection *connection,
                            const gchar     *name,
                            gpointer         user_data);
static int modemmanager_init (void);
static void modemmanager_exit (void);
static gboolean process_mms_process_message_queue (gpointer user_data);
static void mmsd_connect_to_sms_wap (void);
static void mmsd_disconnect_from_sms_wap (void);
static void mmsd_get_all_sms (void);

static void
save_string_settings (GKeyFile    *key_file,
                      const gchar *group_name,
                      const gchar *key,
                      const gchar *value,
                      gchar      **location)
{
  g_free (*location);
  *location = g_strdup (value);
  DBG ("%s set to '%s'", key, value);

  if (!value || !*value)
    value = "NULL";
  g_key_file_set_string (key_file, group_name, key, value);
}

static void
save_boolean_settings (GKeyFile    *key_file,
                       const gchar *group_name,
                       const gchar *key,
                       gboolean     value,
                       gboolean    *location)
{
  *location = value;
  DBG ("%s set to %s", key, value ? "TRUE" : "FALSE");
  g_key_file_set_boolean (key_file, group_name, key, value);
}

static gboolean
dbus_change_settings (const char *key,
                      GVariant   *value)
{
  GKeyFile *key_file;
  const gchar *val_str;
  gboolean val_bool;

  key_file = modem->modemsettings;
  if (g_strcmp0 (key, "CarrierMMSC") == 0)
    {
      g_variant_get (value, "&s", &val_str);
      save_string_settings (key_file, SETTINGS_GROUP, key,
                            val_str, &modem->message_center);
    }
  else if (g_strcmp0 (key, "MMS_APN") == 0)
    {
      g_variant_get (value, "&s", &val_str);
      save_string_settings (key_file, SETTINGS_GROUP, key,
                            val_str, &modem->mms_apn);
    }
  else if (g_strcmp0 (key, "CarrierMMSProxy") == 0)
    {
      g_variant_get (value, "&s", &val_str);
      if (!*val_str || g_strcmp0 (val_str, "NULL") == 0)
        val_str = NULL;
      save_string_settings (key_file, SETTINGS_GROUP, key,
                            val_str, &modem->MMS_proxy);
    }
  else if (g_strcmp0 (key, "DefaultModemNumber") == 0)
    {
      g_variant_get (value, "&s", &val_str);
      if (!*val_str || g_strcmp0 (val_str, "NULL") == 0)
        val_str = NULL;
      save_string_settings (key_file, SETTINGS_GROUP, key,
                            val_str, &modem->default_number);
    }
  else if (g_strcmp0 (key, "AutoProcessOnConnection") == 0)
    {
      g_variant_get (value, "b", &val_bool);
      if (val_bool == TRUE && val_bool != modem->auto_process_on_connection)
        process_mms_process_message_queue (NULL);
      save_boolean_settings (key_file, SETTINGS_GROUP, key,
                             val_bool, &modem->auto_process_on_connection);
    }
  else if (g_strcmp0 (key, "AutoProcessSMSWAP") == 0)
    {
      g_variant_get (value, "b", &val_bool);
      if (val_bool == FALSE &&
          modem->autoprocess_sms_wap == TRUE &&
          modem->modem_available == TRUE)
        mmsd_disconnect_from_sms_wap ();
      if (val_bool == TRUE &&
          modem->autoprocess_sms_wap == FALSE)
        {
          mmsd_connect_to_sms_wap ();
          if (modem->modem_ready == TRUE)
            mmsd_get_all_sms ();
        }
      save_boolean_settings (key_file, SETTINGS_GROUP, key,
                             val_bool, &modem->auto_process_on_connection);
    }
  else
    return FALSE;

  return TRUE;
}

static void
handle_method_call (GDBusConnection       *connection,
                    const gchar           *sender,
                    const gchar           *object_path,
                    const gchar           *interface_name,
                    const gchar           *method_name,
                    GVariant              *parameters,
                    GDBusMethodInvocation *invocation,
                    gpointer               user_data)
{
  if (g_strcmp0 (method_name, "PushNotify") == 0)
    {
      GVariant *smswap;
      const unsigned char *data;
      gsize data_len;
      if (modem->modem_ready == TRUE)
        {
          g_variant_get (parameters, "(@ay)", &smswap);
          data_len = g_variant_get_size (smswap);
          data = g_variant_get_fixed_array (smswap, &data_len, 1);
          DBG ("%s", __func__);

          mms_service_push_notify (modem->service, data, data_len);
          g_dbus_method_invocation_return_value (invocation, NULL);
        }
      else
        g_dbus_method_invocation_return_dbus_error (invocation,
                                                    MMS_MODEMMANAGER_INTERFACE,
                                                    "Modem is not active!");
    }
  else if (g_strcmp0 (method_name, "ViewSettings") == 0)
    {
      GVariantBuilder          settings_builder;
      GVariant                *settings, *all_settings;

      g_variant_builder_init (&settings_builder, G_VARIANT_TYPE ("a{sv}"));

      g_variant_builder_add_parsed (&settings_builder,
                                    "{'CarrierMMSC', <%s>}",
                                    modem->message_center);

      g_variant_builder_add_parsed (&settings_builder,
                                    "{'MMS_APN', <%s>}",
                                    modem->mms_apn);
      if (modem->MMS_proxy && *modem->MMS_proxy)
        g_variant_builder_add_parsed (&settings_builder,
                                      "{'CarrierMMSProxy', <%s>}",
                                      modem->MMS_proxy);
      else
        g_variant_builder_add_parsed (&settings_builder,
                                      "{'CarrierMMSProxy', <%s>}",
                                      "NULL");

      if (modem->default_number)
        g_variant_builder_add_parsed (&settings_builder,
                                      "{'DefaultModemNumber', <%s>}",
                                      modem->default_number);
      else
        g_variant_builder_add_parsed (&settings_builder,
                                      "{'DefaultModemNumber', <%s>}",
                                      "NULL");

      g_variant_builder_add_parsed (&settings_builder,
                                    "{'AutoProcessOnConnection', <%b>}",
                                    modem->auto_process_on_connection);

      g_variant_builder_add_parsed (&settings_builder,
                                    "{'AutoProcessSMSWAP', <%b>}",
                                    modem->autoprocess_sms_wap);

      settings = g_variant_builder_end (&settings_builder);

      all_settings = g_variant_new ("(*)", settings);

      g_dbus_method_invocation_return_value (invocation, all_settings);
    }
  else if (g_strcmp0 (method_name, "ChangeSettings") == 0)
    {
      g_autoptr(GVariant) value = NULL;
      g_autofree char *key = NULL;
      gboolean close_settings = FALSE, has_item = FALSE;

      if (modem->modemsettings == NULL)
        {
          close_settings = TRUE;
          modem->modemsettings = mms_settings_open (IDENTIFIER, SETTINGS_STORE);
        }


      g_variant_get (parameters, "(sv)", &key, &value);
      if (dbus_change_settings (key, value))
        has_item = TRUE;

      if (close_settings == TRUE)
        {
          mms_settings_close (IDENTIFIER, SETTINGS_STORE,
                              modem->modemsettings, has_item);
          modem->modemsettings = NULL;
        }
      else if (has_item)
        mms_settings_sync (IDENTIFIER,
                           SETTINGS_STORE,
                           modem->modemsettings);

      if (!has_item)
        {
          g_dbus_method_invocation_return_error (invocation,
                                                 G_DBUS_ERROR,
                                                 G_DBUS_ERROR_INVALID_ARGS,
                                                 "Cannot find the Property requested!");
          return;
        }

      g_dbus_method_invocation_return_value (invocation, NULL);
      if (!modem->MMS_proxy)
        modem->MMS_proxy = g_strdup ("NULL");

      if (!*modem->MMS_proxy)
        {
          g_clear_pointer (&modem->MMS_proxy, g_free);
          modem->MMS_proxy = g_strdup ("NULL");
        }

      g_dbus_connection_emit_signal (connection,
                                     NULL,
                                     MMS_PATH,
                                     MMS_MODEMMANAGER_INTERFACE,
                                     "SettingsChanged",
                                     g_variant_new ("(sss)", modem->mms_apn, modem->message_center, modem->MMS_proxy),
                                     NULL);

      if (g_strcmp0 (modem->MMS_proxy, "NULL") == 0)
        g_clear_pointer (&modem->MMS_proxy, g_free);

    }
  else if (g_strcmp0 (method_name, "ChangeAllSettings") == 0)
    {
      g_autoptr(GVariant) properties = NULL;
      GVariantIter *iter;
      GVariant *value;
      char *key;
      gboolean close_settings = FALSE, has_item = FALSE;

      if (modem->modemsettings == NULL)
        {
          close_settings = TRUE;
          modem->modemsettings = mms_settings_open (IDENTIFIER, SETTINGS_STORE);
        }

      g_variant_get (parameters, "(@a{sv})", &properties);
      g_variant_get (properties, "a{sv}", &iter);

      while (g_variant_iter_loop (iter, "{sv}", &key, &value))
        if (dbus_change_settings (key, value))
          has_item = TRUE;

      if (close_settings == TRUE)
        {
          mms_settings_close (IDENTIFIER, SETTINGS_STORE,
                              modem->modemsettings, has_item);
          modem->modemsettings = NULL;
        }
      else if (has_item)
        mms_settings_sync (IDENTIFIER,
                           SETTINGS_STORE,
                           modem->modemsettings);

      if (!has_item)
        {
          g_dbus_method_invocation_return_error (invocation,
                                                 G_DBUS_ERROR,
                                                 G_DBUS_ERROR_INVALID_ARGS,
                                                 "Cannot find the Property requested!");
          return;
        }

      g_dbus_method_invocation_return_value (invocation, NULL);
      if (!modem->MMS_proxy)
        modem->MMS_proxy = g_strdup ("NULL");

      if (!*modem->MMS_proxy)
        {
          g_clear_pointer (&modem->MMS_proxy, g_free);
          modem->MMS_proxy = g_strdup ("NULL");
        }

      g_dbus_connection_emit_signal (connection,
                                     NULL,
                                     MMS_PATH,
                                     MMS_MODEMMANAGER_INTERFACE,
                                     "SettingsChanged",
                                     g_variant_new ("(sss)", modem->mms_apn, modem->message_center, modem->MMS_proxy),
                                     NULL);

      if (g_strcmp0 (modem->MMS_proxy, "NULL") == 0)
        g_clear_pointer (&modem->MMS_proxy, g_free);

    }
  else if (g_strcmp0 (method_name, "ProcessMessageQueue") == 0)
    {
      if (modem->modem_ready == TRUE)
        {
          process_mms_process_message_queue (NULL);
          g_dbus_method_invocation_return_value (invocation, NULL);
        }
      else
        g_dbus_method_invocation_return_dbus_error (invocation,
                                                    MMS_MODEMMANAGER_INTERFACE,
                                                    "Modem is not connected!");
    }
}

static const GDBusInterfaceVTable interface_vtable = {
  handle_method_call
};

static void
cb_sms_delete_finish_retry (MMModemMessaging *modemmessaging,
                            GAsyncResult     *result,
                            MMSms            *sms)
{
  g_autoptr(GError) error = NULL;

  if (mm_modem_messaging_delete_finish (modemmessaging, result, &error))
    DBG ("Message delete finish");
  else
    DBG ("Couldn't delete SMS - error: %s", error ? error->message : "unknown");
}

static void
cb_sms_delete_finish (MMModemMessaging *modemmessaging,
                      GAsyncResult     *result,
                      MMSms            *sms)
{
  g_autoptr(GError) error = NULL;

  if (mm_modem_messaging_delete_finish (modemmessaging, result, &error))
    DBG ("Message delete finish");
  else {
      const char      *path;
      path = mm_sms_get_path (sms);
      DBG ("Couldn't delete SMS - error: %s", error ? error->message : "unknown");
      if (path)
        {
          DBG ("Trying to delete SMS again....");
          mm_modem_messaging_delete (modem->modem_messaging,
                                     path,
                                     NULL,
                                     (GAsyncReadyCallback)cb_sms_delete_finish_retry,
                                     sms);
        }
      else
        DBG ("mmsd-tng cannot process MMS at this time!");
    }
}

static gboolean
mmsd_mm_activate_bearer (gpointer user_data)
{
  activate_bearer (modem->service);
  return FALSE;
}

static gboolean
process_mms_process_message_queue (gpointer user_data)
{
  if (modem->modem_ready == TRUE)
    {
      DBG ("Processing any unsent/unreceived MMS messages.");
      /*
       * Prevent a race condition from the connection turning active to usable
       * for mmsd-tng
       */
      g_timeout_add_seconds (1, mmsd_mm_activate_bearer, NULL);
    }
  else
    DBG ("Modem is not ready to process any unsent/unreceived MMS messages.");
  return FALSE;
}

static void
mmsd_process_sms (MMSms *sms)
{
  const gchar     *message;
  const guint8    *data;
  const char      *path;
  gsize            data_len;

  data_len = 0;
  message = mm_sms_get_text (sms);
  data = mm_sms_get_data (sms, &data_len);

  if (message)
    DBG ("This is a regular SMS.");
  else if (data)
    {
      DBG ("Received SMS WAP!");
      if (modem->modem_ready == TRUE)
        {
          mms_service_push_notify (modem->service, data, data_len);
          path = mm_sms_get_path (sms);
          if (path)
            {
              DBG ("Deleting SMS Now...");
              mm_modem_messaging_delete (modem->modem_messaging,
                                         path,
                                         NULL,
                                         (GAsyncReadyCallback)cb_sms_delete_finish,
                                         sms);
            }
          else
            DBG ("mmsd-tng cannot process MMS at this time!");
        }
      else
        DBG ("Modem is not connected!");
    }
  else
    g_critical ("Not sure what kind of SMS this is!");
}

static void
cb_sms_state_change (MMSms      *sms,
                     GParamSpec *pspec,
                     gpointer   *user_data)
{
  MMSmsState state;

  state = mm_sms_get_state (sms);
  DBG ("%s: state %s", __func__,
       mm_sms_state_get_string (mm_sms_get_state (sms)));

  if (state == MM_SMS_STATE_RECEIVED)
    mmsd_process_sms (sms);
}

static void
mmsd_check_pdu_type (MMSms *sms)
{
  MMSmsState      state;
  MMSmsPduType    pdu_type;

  pdu_type = mm_sms_get_pdu_type (sms);
  state = mm_sms_get_state (sms);
  switch (pdu_type)
    {
    case MM_SMS_PDU_TYPE_CDMA_DELIVER:
    case MM_SMS_PDU_TYPE_DELIVER:

      if (state == MM_SMS_STATE_RECEIVED)
        mmsd_process_sms (sms);

      if (state == MM_SMS_STATE_RECEIVING)
        // The first chunk of a multipart SMS has been
        // received -> wait for MM_SMS_STATE_RECEIVED
        g_signal_connect (sms,
                          "notify::state",
                          G_CALLBACK (cb_sms_state_change),
                          NULL);
      break;

    case MM_SMS_PDU_TYPE_STATUS_REPORT:
    case MM_SMS_PDU_TYPE_SUBMIT:
      DBG ("This is not an SMS being received, do not care");
      break;

    case MM_SMS_PDU_TYPE_UNKNOWN:
      DBG ("Unknown PDU type");
      break;

    default:
      DBG ("PDU type not handled");
    }
}


static void
cb_sms_list_new_ready (MMModemMessaging *modemmessaging,
                       GAsyncResult     *result,
                       gchar            *path)
{
  GList                   *l, *list;
  g_autoptr(GError)        error = NULL;
  MMSms                   *sms;

  list = mm_modem_messaging_list_finish (modemmessaging, result, &error);

  if (error)
    g_critical ("Couldn't get SMS list - error: %s",
                error->message);
  else {
      for (l = list; l; l = g_list_next (l))
        //We are searching for the SMS from the list that is new
        if (!g_strcmp0 (mm_sms_get_path (MM_SMS (l->data)), path))
          {
            sms = g_object_ref (MM_SMS (l->data));
            mmsd_check_pdu_type (sms);
            break;
          }
      g_list_free_full (list, g_object_unref);
      g_free (path);
    }
}

static gboolean
cb_dbus_signal_sms_added (MMModemMessaging *device,
                          gchar            *const_path,
                          gpointer          user_data)
{
  gchar *path;
  path = g_strdup (const_path);
  DBG ("Got new SMS");
  mm_modem_messaging_list (modem->modem_messaging,
                           NULL,
                           (GAsyncReadyCallback)cb_sms_list_new_ready,
                           path);
  return TRUE;
}

static void
cb_sms_list_all_ready (MMModemMessaging *modemmessaging,
                       GAsyncResult     *result,
                       gpointer          user_data)
{
  GList                   *l, *list;
  g_autoptr(GError)       error = NULL;
  MMSms                   *sms;

  list = mm_modem_messaging_list_finish (modemmessaging, result, &error);

  if (error)
    DBG ("Couldn't get SMS list - error: %s", error->message);
  else {
      for (l = list; l; l = g_list_next (l))
        {
          sms = g_object_ref (MM_SMS (l->data));
          mmsd_check_pdu_type (sms);
        }
      g_list_free_full (list, g_object_unref);
    }
}

static gboolean
mmsd_mm_get_messaging_list (gpointer user_data)
{
  DBG ("Searching for all SMS");
  mm_modem_messaging_list (modem->modem_messaging,
                           NULL,
                           (GAsyncReadyCallback)cb_sms_list_all_ready,
                           NULL);

  return FALSE;
}

static void
mmsd_get_all_sms (void)
{
  g_return_if_fail (MM_IS_MODEM_MESSAGING (modem->modem_messaging));


  if (modem->get_all_sms_timeout == FALSE)
    {
      DBG ("Queue Search for all SMS");
      modem->get_all_sms_timeout = TRUE;
      g_timeout_add_seconds (5, mmsd_mm_get_messaging_list, NULL);
    }
}

static void
mmsd_disconnect_from_sms_wap (void)
{
  MmGdbusModemMessaging *gdbus_sms;
  if (modem->modem_messaging == NULL)
    {
      g_critical ("SMS WAP Disconnect: There is no modem with messaging capabilities!");
      return;
    }
  DBG ("Stopping watching SMS WAPs");
  gdbus_sms = MM_GDBUS_MODEM_MESSAGING (modem->modem_messaging);
  g_signal_handler_disconnect (gdbus_sms,
                               modem->sms_wap_signal_id);
}

static void
mmsd_connect_to_sms_wap (void)
{
  MmGdbusModemMessaging *gdbus_sms;
  if (modem->modem_messaging == NULL)
    {
      DBG ("SMS WAP Connect: There is no modem with messaging capabilities!");
      return;
    }
  gdbus_sms = MM_GDBUS_MODEM_MESSAGING (modem->modem_messaging);
  DBG ("Watching for new SMS WAPs");
  modem->sms_wap_signal_id = g_signal_connect (gdbus_sms,
                                               "added",
                                               G_CALLBACK (cb_dbus_signal_sms_added),
                                               NULL);
}

static gboolean
mmsd_mm_init_modem (MMObject *obj)
{
  modem->modem_messaging = mm_object_get_modem_messaging (MM_OBJECT (obj));
  if (modem->modem_messaging == NULL)
    return FALSE;

  modem->object = g_object_ref (obj);
  modem->modem = mm_object_get_modem (MM_OBJECT (obj));
  modem->path = mm_modem_dup_path (modem->modem);

  g_dbus_proxy_set_default_timeout (G_DBUS_PROXY (modem->modem),
                                    MMSD_MM_MODEM_TIMEOUT);


  DBG ("%s", __func__);

  return TRUE;
}

static void
free_device (MmsdDevice *device)
{
  if (!device)
    return;

  g_clear_object (&device->sim);
  g_clear_object (&device->modem);
  g_clear_object (&device->object);
  g_free (device);
}

static gboolean
device_match_by_object (MmsdDevice  *device,
                        GDBusObject *object)
{
  g_return_val_if_fail (G_IS_DBUS_OBJECT (object), FALSE);
  g_return_val_if_fail (MM_OBJECT (device->object), FALSE);

  return object == G_DBUS_OBJECT (device->object);
}

static void
mmsd_mm_add_object (MMObject *obj)
{
  MmsdDevice         *device;
  const gchar        *object_path;
  const gchar *const *modem_number_ref;
  MMSim              *sim;
  const gchar        *country_code;
  g_autoptr(GError)   error = NULL;
  g_autofree char *modem_number_formatted = NULL;

  object_path = g_dbus_object_get_object_path (G_DBUS_OBJECT (obj));

  g_return_if_fail (object_path);
  //Begin if statement
  if (g_ptr_array_find_with_equal_func (modem->device_arr,
                                        obj,
                                        (GEqualFunc)device_match_by_object,
                                        NULL))
    {
      //End if statement
      DBG ("Device %s already added", object_path);
      return;
    }

  if (modem->modem_available)
    {
      DBG ("There is already a modem registered!");
      return;
    }

  if (modem->default_number && *modem->default_number)
    {
      g_autofree char *default_number_formatted = NULL;
      DBG ("Checking if this modem number matches default number");
      sim = mm_modem_get_sim_sync (mm_object_get_modem (MM_OBJECT (obj)),
                                   NULL,
                                   &error);
      if (error != NULL)
        {
          g_critical ("Error Getting Sim: %s", error->message);
          return;
        }
      country_code = get_country_iso_for_mcc (mm_sim_get_imsi (sim));
      modem_number_ref = mm_modem_get_own_numbers (mm_object_get_modem (MM_OBJECT (obj)));

      for (guint i = 0; modem_number_ref && modem_number_ref[i]; i++)
        {
          const char *number;

          number = modem_number_ref[i];

          if (number)
            {
              modem_number_formatted = phone_utils_format_number_e164 (number, country_code, FALSE);
              break;
            }
        }

      if (modem_number_ref == NULL)
        {
          DBG ("Could not get number!");
          return;
        }
      default_number_formatted = phone_utils_format_number_e164 (modem->default_number, country_code, FALSE);

      if (default_number_formatted == NULL)
        {
          g_critical ("Default number not formatted correctly!");
          return;
        }

      if (g_strcmp0 (modem_number_formatted, default_number_formatted) != 0)
        {
          DBG ("This modem does not match default number!");
          return;
        }
    }
  else
    DBG ("Not checking for a default Modem");

  DBG ("Added device at: %s", object_path);

  if (mmsd_mm_init_modem (obj) == TRUE)
    {
      device = g_new0 (MmsdDevice, 1);
      device->object = g_object_ref (MM_OBJECT (obj));
      device->modem = mm_object_get_modem (MM_OBJECT (obj));
      g_ptr_array_add (modem->device_arr, device);

      mmsd_mm_state (MMSD_MM_STATE_MODEM_FOUND);
    }
  else
    mmsd_mm_state (MMSD_MM_STATE_NO_MESSAGING_MODEM);
}

static void
mmsd_mm_get_modems (void)
{
  GList *list, *l;
  gboolean has_modem = FALSE;

  DBG ("getting Modems");

  g_return_if_fail (MM_IS_MANAGER (modem->mm));

  list = g_dbus_object_manager_get_objects (G_DBUS_OBJECT_MANAGER (modem->mm));

  for (l = list; l != NULL; l = l->next)
    {
      if (!mm_object_peek_modem_messaging (l->data))
        continue;

      has_modem = TRUE;
      mmsd_mm_add_object (MM_OBJECT (l->data));
    }

  if (!has_modem)
    mmsd_mm_state (MMSD_MM_STATE_NO_MODEM);
  if (list)
    g_list_free_full (list, g_object_unref);
}


static void
cb_object_added (GDBusObjectManager *manager,
                 GDBusObject        *object,
                 gpointer            user_data)
{
  DBG ("%s", __func__);
  if (mm_object_peek_modem_messaging (MM_OBJECT (object)))
    {
      g_message ("New Object with Messaging feature found, Adding...");
      mmsd_mm_add_object (MM_OBJECT (object));
    }
}

static void
cb_object_removed (GDBusObjectManager *manager,
                   GDBusObject        *object,
                   gpointer            user_data)
{
  guint index;

  g_return_if_fail (G_IS_DBUS_OBJECT (object));
  g_return_if_fail (G_IS_DBUS_OBJECT_MANAGER (manager));
  //Begin if statement
  if (g_ptr_array_find_with_equal_func (modem->device_arr,
                                        object,
                                        (GEqualFunc)device_match_by_object,
                                        &index))
    //End if Statement
    g_ptr_array_remove_index_fast (modem->device_arr, index);

  if (MM_OBJECT (object) == modem->object)
    mmsd_mm_state (MMSD_MM_STATE_NO_MODEM);

  DBG ("Modem removed: %s", g_dbus_object_get_object_path (object));
}


static void
cb_name_owner_changed (GDBusObjectManager *manager,
                       GDBusObject        *object,
                       gpointer            user_data)
{
  gchar *name_owner;

  name_owner = g_dbus_object_manager_client_get_name_owner (G_DBUS_OBJECT_MANAGER_CLIENT (manager));

  if (!name_owner)
    mmsd_mm_state (MMSD_MM_STATE_NO_MANAGER);

  DBG ("Name owner changed");

  g_free (name_owner);
}

static void
cb_mm_manager_new (GDBusConnection *connection,
                   GAsyncResult    *res,
                   gpointer         user_data)
{
  gchar                   *name_owner;
  g_autoptr(GError)        error = NULL;


  modem->mm = mm_manager_new_finish (res, &error);
  modem->device_arr = g_ptr_array_new_with_free_func ((GDestroyNotify) free_device);

  if (modem->mm)
    {
      mmsd_mm_state (MMSD_MM_STATE_MANAGER_FOUND);

      g_signal_connect (modem->mm,
                        "interface-added",
                        G_CALLBACK (cb_object_added),
                        NULL);

      g_signal_connect (modem->mm,
                        "object-added",
                        G_CALLBACK (cb_object_added),
                        NULL);

      g_signal_connect (modem->mm,
                        "object-removed",
                        G_CALLBACK (cb_object_removed),
                        NULL);

      g_signal_connect (modem->mm,
                        "notify::name-owner",
                        G_CALLBACK (cb_name_owner_changed),
                        NULL);

      name_owner = g_dbus_object_manager_client_get_name_owner (G_DBUS_OBJECT_MANAGER_CLIENT (modem->mm));
      DBG ("ModemManager found: %s\n", name_owner);
      g_free (name_owner);

      mmsd_mm_get_modems ();
    }
  else {
      g_critical ("Error connecting to ModemManager: %s\n", error->message);

      mmsd_mm_state (MMSD_MM_STATE_NO_MANAGER);
    }
}

static void
mmsd_mm_get_modem_state (void)
{
  if (!modem->modem)
    {
      mmsd_mm_state (MMSD_MM_STATE_NO_MODEM);
      return;
    }

  if (modem->state < MM_MODEM_STATE_ENABLED)
    {
      DBG ("Something May be wrong with the modem, checking....");
      switch (modem->state)
        {
        case MM_MODEM_STATE_FAILED:
          DBG ("MM_MODEM_STATE_FAILED");
          mmsd_mm_state (MMSD_MM_STATE_MODEM_DISABLED);
          return;
        case MM_MODEM_STATE_UNKNOWN:
          DBG ("MM_MODEM_STATE_UNKNOWN");
          mmsd_mm_state (MMSD_MM_STATE_MODEM_DISABLED);
          return;
        case MM_MODEM_STATE_LOCKED:
          DBG ("MM_MODEM_STATE_FAILED");
          mmsd_mm_state (MMSD_MM_STATE_MODEM_DISABLED);
          return;
        case MM_MODEM_STATE_INITIALIZING:
          DBG ("MM_MODEM_STATE_INITIALIZING");
          mmsd_mm_state (MMSD_MM_STATE_MODEM_DISABLED);
          return;
        case MM_MODEM_STATE_DISABLED:
          DBG ("MM_MODEM_STATE_DISABLED");
          mmsd_mm_state (MMSD_MM_STATE_MODEM_DISABLED);
          return;
        case MM_MODEM_STATE_DISABLING:
          DBG ("MM_MODEM_STATE_DISABLING");
          mmsd_mm_state (MMSD_MM_STATE_MODEM_DISABLED);
          return;
        case MM_MODEM_STATE_ENABLING:
          DBG ("MM_MODEM_STATE_ENABLING");
          mmsd_mm_state (MMSD_MM_STATE_MODEM_DISABLED);
          return;
        default:
          DBG ("MM_MODEM_OTHER_BAD_STATE: %d", modem->state);
          mmsd_mm_state (MMSD_MM_STATE_MODEM_DISABLED);
          return;
        }
    }
  DBG ("MM_MODEM_GOOD_STATE: %d", modem->state);
  mmsd_mm_state (MMSD_MM_STATE_READY);

  /* Automatically process unsent/unreceived messages when the modem is connected */
  if (modem->state == MM_MODEM_STATE_CONNECTED)
    if (modem->auto_process_on_connection == TRUE)
      process_mms_process_message_queue (NULL);

  return;
}

static void
modem_state_changed_cb (MMModem                 *cb_modem,
                        MMModemState             old,
                        MMModemState             new,
                        MMModemStateChangeReason reason)
{
  DBG ("State Change: Old State: %d New State: %d, Reason: %d", old, new, reason);
  modem->state = new;
  mmsd_mm_get_modem_state ();
}

static void
mmsd_mm_state (int state)
{
  switch (state)
    {
    case MMSD_MM_STATE_MODEM_FOUND:
      if (!modem->modem_available)
        mmsd_modem_available ();
      break;
    case MMSD_MM_STATE_NO_MODEM:
      if (modem->modem_available)
        {
          mmsd_modem_unavailable ();
          DBG ("Modem vanished, Disabling plugin");
        }
      else
        DBG ("Could not connect to modem");
      modem->modem_available = FALSE;
      modem->modem_ready = FALSE;
      DBG ("MMSD_MM_STATE_NO_MODEM");
      break;

    case MMSD_MM_STATE_NO_MESSAGING_MODEM:
      DBG ("Modem has no messaging capabilities");
      DBG ("MMSD_MM_STATE_NO_MESSAGING_MODEM");
      modem->modem_available = FALSE;
      modem->modem_ready = FALSE;
      break;

    case MMSD_MM_STATE_MODEM_DISABLED:
      DBG ("Modem disabled");
      DBG ("MMSD_MM_STATE_MODEM_DISABLED");
      DBG ("Disabling Bearer Handler");
      mms_service_set_bearer_handler (modem->service, NULL, NULL);
      modem->modem_ready = FALSE;
      break;

    case MMSD_MM_STATE_MANAGER_FOUND:
      if (!modem->manager_available)
        modem->manager_available = TRUE;
      DBG ("MMSD_MM_STATE_MANAGER_FOUND");
      break;

    case MMSD_MM_STATE_NO_MANAGER:
      if (modem->modem_available)
        {
          mmsd_modem_unavailable ();
          DBG ("Modem vanished, Disabling plugin");
        }
      if (modem->manager_available)
        {
          g_clear_object (&modem->mm);
          if (modem->device_arr)
            {
              g_ptr_array_set_size (modem->device_arr, 0);
              g_ptr_array_unref (modem->device_arr);
              modem->device_arr = NULL;
            }
          modem->modem_available = FALSE;
          modem->modem_ready = FALSE;
        }
      else
        DBG ("Could not connect to ModemManager");
      modem->manager_available = FALSE;
      DBG ("MMSD_MM_STATE_NO_MANAGER");
      break;

    case MMSD_MM_STATE_READY:
      DBG ("MMSD_MM_STATE_READY");
      modem->modem_ready = TRUE;
      DBG ("Setting Bearer Handler");
      mms_service_set_bearer_handler (modem->service, bearer_handler, modem);
      if (modem->autoprocess_sms_wap)
        mmsd_get_all_sms ();
      break;

    default:
      g_return_if_reached ();
    }
}

static void
mm_appeared_cb (GDBusConnection *connection,
                const gchar     *name,
                const gchar     *name_owner,
                gpointer         user_data)
{
  g_assert (G_IS_DBUS_CONNECTION (connection));

  mm_manager_new (connection,
                  G_DBUS_OBJECT_MANAGER_CLIENT_FLAGS_NONE,
                  NULL,
                  (GAsyncReadyCallback) cb_mm_manager_new,
                  NULL);
}

static void
mm_vanished_cb (GDBusConnection *connection,
                const gchar     *name,
                gpointer         user_data)
{
  g_assert (G_IS_DBUS_CONNECTION (connection));

  DBG ("Modem Manager vanished");
  mmsd_mm_state (MMSD_MM_STATE_NO_MANAGER);
}

static void
bearer_handler (mms_bool_t active,
                void      *user_data)
{
  struct modem_data *modem = user_data;
  gint32 response;
  GDBusConnection *connection = mms_dbus_get_connection ();
  /* Check for any errors within the context */
  response = set_context ();
  if (response != MMSD_MM_MODEM_CONTEXT_ACTIVE)
    {
      DBG ("Set MMSC: %s, Set Proxy: %s, Set MMS APN: %s", modem->message_center, modem->MMS_proxy, modem->mms_apn);
      g_dbus_connection_emit_signal (connection,
                                     NULL,
                                     MMS_PATH,
                                     MMS_MODEMMANAGER_INTERFACE,
                                     "BearerHandlerError",
                                     g_variant_new ("(h)", response),
                                     NULL);
    }
  DBG ("At Bearer Handler: path %s active %d context_active %d", modem->path, active, modem->context_active);
  if (active == TRUE && modem->context_active == TRUE)
    {
      DBG ("active and context_active, bearer_notify");
      mms_service_bearer_notify (modem->service, TRUE, modem->context_interface, modem->MMS_proxy);
      return;
    }
  else if (active == TRUE && modem->context_active == FALSE)
    {
      DBG ("Error activating context!");
      mms_service_bearer_notify (modem->service, FALSE, NULL, NULL);
      return;
    }

  DBG ("checking for failure");
  if (active == FALSE && modem->context_active == FALSE)
    {
      DBG ("Context not active!");
      mms_service_bearer_notify (modem->service, FALSE, NULL, NULL);
      return;
    }
  else {
      DBG ("No failures");
      mms_service_bearer_notify (modem->service, FALSE, modem->context_interface, modem->MMS_proxy);
      return;
    }
}

static void
find_settings_cb (const char *apn,
                  const char *mmsc,
                  const char *proxy,
                  GError     *error,
                  gpointer    user_data)
{
  if (error)
    {
      g_warning ("Could not find settings: %s", error->message);
      g_warning ("Your MMS settings are not in the database! Please file a merge request at https://gitlab.gnome.org/GNOME/mobile-broadband-provider-info so they can be added");
    }
  else {
      DBG ("Found settings: APN:%s, mmsc: %s, Proxy: %s", apn, mmsc, proxy);
      if (mmsc != NULL)
        {
          GDBusConnection *connection = mms_dbus_get_connection ();
          g_free (modem->message_center);
          modem->message_center = g_strdup (mmsc);
          DBG ("Carrier MMSC set to %s", modem->message_center);
          g_key_file_set_string (modem->modemsettings,
                                 SETTINGS_GROUP,
                                 "CarrierMMSC",
                                 modem->message_center);
          g_free (modem->mms_apn);
          modem->mms_apn = g_strdup (apn);
          DBG ("MMS APN set to %s", modem->mms_apn);
          g_key_file_set_string (modem->modemsettings,
                                 SETTINGS_GROUP,
                                 "MMS_APN",
                                 modem->mms_apn);

          g_free (modem->MMS_proxy);
          modem->MMS_proxy = NULL;
          if (proxy && *proxy)
            modem->MMS_proxy = g_strdup (proxy);
          else
            modem->MMS_proxy = g_strdup ("NULL");

          g_key_file_set_string (modem->modemsettings,
                                 SETTINGS_GROUP,
                                 "CarrierMMSProxy",
                                 modem->MMS_proxy);

          /* proxy will not be NULL here */
          g_dbus_connection_emit_signal (connection,
                                         NULL,
                                         MMS_PATH,
                                         MMS_MODEMMANAGER_INTERFACE,
                                         "SettingsChanged",
                                         g_variant_new ("(sss)", modem->mms_apn, modem->message_center, modem->MMS_proxy),
                                         NULL);

          if (g_strcmp0 (modem->MMS_proxy, "NULL") == 0)
            {
              g_free (modem->MMS_proxy);
              modem->MMS_proxy = NULL;
            }
          DBG ("MMS Proxy set to %s", modem->MMS_proxy);

          mms_settings_sync (IDENTIFIER, SETTINGS_STORE, modem->modemsettings);

          /* Reprocess message queue since settings are right now */
          g_timeout_add_seconds (1, process_mms_process_message_queue, NULL);
        }
      else
        g_warning ("Your MMS settings are not in the database! Please file a merge request at https://gitlab.gnome.org/GNOME/mobile-broadband-provider-info so they can be added");

    }
}

static int
set_context (void)
{
  guint                    max_bearers, active_bearers;
  GList                   *bearer_list, *l;
  MMBearer                *modem_bearer;
  MMBearerProperties      *modem_bearer_properties;
  const gchar             *apn;
  gboolean                 interface_disconnected;
  gboolean                 bearer_connected;

  DBG ("Setting Context...");
  if (modem->context_active)
    {
      g_clear_pointer (&modem->context_interface, g_free);
      g_clear_pointer (&modem->context_path, g_free);
    }
  modem->context_active = FALSE;
  interface_disconnected = FALSE;
  mms_service_set_mmsc (modem->service, modem->message_center);
  max_bearers = mm_modem_get_max_active_bearers (modem->modem);
  DBG ("Max number of bearers: %d", max_bearers);
  bearer_list = mm_modem_list_bearers_sync (modem->modem, NULL, NULL);
  active_bearers = 0;
  if (bearer_list != NULL)
    {
      MMBearerIpConfig *modem_bearer_ip6_config;
      const gchar **dns_ipv6;
      gchar *dns_ipv6_csv = NULL;
      MMBearerIpConfig *modem_bearer_ip4_config;
      const gchar **dns_ipv4;
      gchar *dns_ipv4_csv = NULL;

      for (l = bearer_list; l != NULL; l = l->next)
        {
          active_bearers = active_bearers + 1;
          modem_bearer = (MMBearer *) l->data;
          modem_bearer_properties = mm_bearer_get_properties (modem_bearer);
          apn = mm_bearer_properties_get_apn (modem_bearer_properties);
          if (g_strcmp0 (modem->message_center, "http://mms.invalid") == 0)
            {
              DBG ("Attempting to autoconfigure settings");
              mmsd_service_providers_find_settings (MOBILE_BROADBAND_PROVIDER_INFO_DATABASE,
                                                    modem->mccmnc,
                                                    apn,
                                                    find_settings_cb,
                                                    NULL);
            }
          DBG ("Current Context APN: %s, mmsd-tng settings MMS APN: %s", apn, modem->mms_apn);
          bearer_connected = mm_bearer_get_connected (modem_bearer);
          if (g_strcmp0 (apn, modem->mms_apn) == 0)
            {
              if (modem->state != MM_MODEM_STATE_CONNECTED)
                {
                  DBG ("The modem interface is reporting it is disconnected!");
                  DBG ("Reported State: %d", modem->state);
                  interface_disconnected = TRUE;
                }
              else if (!bearer_connected)
                interface_disconnected = TRUE;
              else {
                  DBG ("You are connected to the correct APN! Enabling context...");
                  modem->context_interface = mm_bearer_dup_interface (modem_bearer);
                  modem->context_path = mm_bearer_dup_path (modem_bearer);
                  modem->context_active = TRUE;
                  mms_service_set_apn (modem->service, modem->mms_apn);

                  modem_bearer_ip4_config = mm_bearer_peek_ipv4_config (modem_bearer);
                  dns_ipv4 = mm_bearer_ip_config_get_dns (modem_bearer_ip4_config);
                  if (dns_ipv4)
                    dns_ipv4_csv = g_strjoinv (",", (gchar **)dns_ipv4);
                  modem_bearer_ip6_config = mm_bearer_peek_ipv6_config (modem_bearer);
                  dns_ipv6 = mm_bearer_ip_config_get_dns (modem_bearer_ip6_config);
                  if (dns_ipv6)
                    dns_ipv6_csv = g_strjoinv (",", (gchar **)dns_ipv6);
                  mms_service_set_resolvers (modem->service, dns_ipv4_csv, dns_ipv6_csv);
                  DBG ("DNS from modemmanager IPv6: %s, IPv4: %s", dns_ipv6_csv, dns_ipv4_csv);
                  g_clear_pointer (&dns_ipv6_csv, g_free);
                  g_clear_pointer (&dns_ipv4_csv, g_free);
                }
            }
        }
      g_list_free (bearer_list);
      g_list_free (l);
      if (!modem->context_active)            // I did not find the right context I wanted.
        {
          if (active_bearers == max_bearers)
            {
              if (interface_disconnected)
                {
                  DBG ("The proper context is not connected!");
                  return MMSD_MM_MODEM_INTERFACE_DISCONNECTED;
                }
              else {
                  DBG ("The modem is not connected to the correct APN!");
                  return MMSD_MM_MODEM_INCORRECT_APN_CONNECTED;
                }
            }
          else if (active_bearers == 0)
            {
              DBG ("The modem bearer is disconnected! Please enable modem data");
              return MMSD_MM_MODEM_NO_BEARERS_ACTIVE;
            }
          else if (active_bearers < max_bearers)
            {
              /*
               * TODO: Modem manager does not support this yet, but this is
               *	 where to add code when Modem manager supports multiple
               *	 contexts and/or a seperate MMS context.
               *	 The Pinephone and Librem 5 only support
               *	 one active bearer as well
               */
              DBG ("This is a stub for adding a new context/bearer, but Modem Manager does not support this yet.");
              return MMSD_MM_MODEM_FUTURE_CASE_DISCONNECTED;
            }
        }
    }
  else {
      DBG ("There are no modem bearers! Please enable modem data");
      return MMSD_MM_MODEM_NO_BEARERS_ACTIVE;
    }

  if (g_strcmp0 (modem->message_center, "http://mms.invalid") == 0)
    {
      DBG ("The MMSC is not configured! Please configure the MMSC and restart mmsd-tng.");
      return MMSD_MM_MODEM_MMSC_MISCONFIGURED;
    }
  return MMSD_MM_MODEM_CONTEXT_ACTIVE;
}

static void
mm_save_settings (void)
{
  g_autofree char *imsi_hash = NULL;

  /* Settings are default, no use in saving them */
  if (g_strcmp0 (modem->message_center, "http://mms.invalid") == 0)
    return;

  imsi_hash = g_strdup_printf ("Old Settings: %u", g_str_hash (modem->registered_imsi));

  g_key_file_set_string (modem->modemsettings, imsi_hash,
                         "CarrierMMSC", modem->message_center);

  g_key_file_set_string (modem->modemsettings, imsi_hash,
                         "MMS_APN", modem->mms_apn);

  if (modem->MMS_proxy)
    g_key_file_set_string (modem->modemsettings, imsi_hash,
                           "CarrierMMSProxy", modem->MMS_proxy);
  else
    g_key_file_set_string (modem->modemsettings, imsi_hash,
                           "CarrierMMSProxy", "NULL");

  mms_settings_sync (IDENTIFIER, SETTINGS_STORE, modem->modemsettings);
}

static gboolean
mm_retrieve_settings (void)
{
  g_autofree char *imsi_hash = NULL;
  g_autoptr(GError) error = NULL;

  imsi_hash = g_strdup_printf ("Old Settings: %u", g_str_hash (modem->imsi));

  modem->message_center = g_key_file_get_string (modem->modemsettings,
                                                 imsi_hash,
                                                 "CarrierMMSC", &error);
  if (error)
    return FALSE;

  modem->mms_apn = g_key_file_get_string (modem->modemsettings,
                                          imsi_hash,
                                          "MMS_APN", &error);
  if (error)
    {
      g_clear_pointer (&modem->message_center, g_free);
      return FALSE;
    }

  modem->MMS_proxy = g_key_file_get_string (modem->modemsettings,
                                            imsi_hash,
                                            "CarrierMMSProxy", &error);
  if (error)
    {
      g_clear_pointer (&modem->message_center, g_free);
      g_clear_pointer (&modem->mms_apn, g_free);
      return FALSE;
    }

  return TRUE;
}

static void
mmsd_modem_available (void)
{
  g_autoptr(GError) error = NULL;

  modem->modem_available = TRUE;
  if (modem->modem)
    {
      const char *imsi = NULL;
      const gchar *const *modem_number_ref;

      if (modem->plugin_registered == FALSE)
        {
          DBG ("Registering Modem Manager MMS Service");
          mms_service_register (modem->service);
          modem->modemsettings = mms_service_get_keyfile (modem->service);
          modem->plugin_registered = TRUE;
        }

      MmGdbusModem *gdbus_modem;
      modem->sim = mm_modem_get_sim_sync (modem->modem,
                                          NULL,
                                          &error);

      if (error == NULL)
        imsi = mm_sim_get_imsi (modem->sim);
      else
        g_warning ("Error Getting Sim: %s", error->message);

      if (imsi != NULL)
        {
          modem->imsi = g_strdup (imsi);

          g_free (modem->mccmnc);
          modem->mccmnc = mm_sim_dup_operator_identifier (modem->sim);
          if (!modem->mccmnc)
            modem->mccmnc = g_strndup (modem->imsi, 6);
          if (g_strcmp0 (modem->imsi, modem->registered_imsi) != 0)
            {
              if (g_strcmp0 (modem->registered_imsi, "invalid") != 0)
                {
                  GDBusConnection *connection = mms_dbus_get_connection ();
                  DBG ("IMSI Changed");

                  mm_save_settings ();

                  g_clear_pointer (&modem->message_center, g_free);
                  g_clear_pointer (&modem->mms_apn, g_free);
                  g_clear_pointer (&modem->MMS_proxy, g_free);
                  if (mm_retrieve_settings ())
                    DBG ("Reloading settings");
                  else {
                      DBG ("Resetting settings");
                      modem->message_center = g_strdup ("http://mms.invalid");
                      modem->mms_apn = g_strdup ("apn.invalid");
                      modem->MMS_proxy = g_strdup ("NULL");
                    }

                  g_key_file_set_string (modem->modemsettings, SETTINGS_GROUP,
                                         "CarrierMMSC", modem->message_center);

                  g_key_file_set_string (modem->modemsettings, SETTINGS_GROUP,
                                         "MMS_APN", modem->mms_apn);

                  g_key_file_set_string (modem->modemsettings, SETTINGS_GROUP,
                                         "CarrierMMSProxy", modem->MMS_proxy);

                  g_dbus_connection_emit_signal (connection,
                                                 NULL,
                                                 MMS_PATH,
                                                 MMS_MODEMMANAGER_INTERFACE,
                                                 "SettingsChanged",
                                                 g_variant_new ("(sss)", modem->mms_apn, modem->message_center, modem->MMS_proxy),
                                                 NULL);

                  if (g_strcmp0 (modem->MMS_proxy, "NULL") == 0)
                    g_clear_pointer (&modem->MMS_proxy, g_free);
                }
              g_free (modem->registered_imsi);
              modem->registered_imsi = g_strdup (modem->imsi);
              g_key_file_set_string (modem->modemsettings, SETTINGS_GROUP,
                                     "IMSI", modem->registered_imsi);

              mms_settings_sync (IDENTIFIER, SETTINGS_STORE, modem->modemsettings);
            }

          mms_service_set_country_code (modem->service,
                                        mm_sim_get_imsi (modem->sim));
        }
      else
        g_warning ("IMSI is NULL!");

      modem_number_ref = mm_modem_get_own_numbers (modem->modem);
      for (guint i = 0; modem_number_ref && modem_number_ref[i]; i++)
        {
          const char *number;

          number = modem_number_ref[i];

          if (number)
            {
              mms_service_set_own_number (modem->service, number);
              break;
            }
        }

      gdbus_modem = MM_GDBUS_MODEM (modem->modem);

      modem->modem_state_watch_id = g_signal_connect (gdbus_modem,
                                                      "state-changed",
                                                      G_CALLBACK (modem_state_changed_cb),
                                                      NULL);
      if (modem->autoprocess_sms_wap)
        mmsd_connect_to_sms_wap ();
      else
        DBG ("Not autoprocessing SMS WAPs");
      modem->state = mm_modem_get_state (modem->modem);
      mmsd_mm_get_modem_state ();
    }
  else
    g_critical ("Something very bad happened at mmsd_modem_available()!");

}

static void
mmsd_modem_unavailable (void)
{
  MmGdbusModem *gdbus_modem;

  DBG ("Disabling Bearer Handler");
  gdbus_modem = MM_GDBUS_MODEM (modem->modem);
  mms_service_set_bearer_handler (modem->service, NULL, NULL);
  if (modem->autoprocess_sms_wap)
    mmsd_disconnect_from_sms_wap ();
  modem->get_all_sms_timeout = FALSE;
  g_signal_handler_disconnect (gdbus_modem,
                               modem->modem_state_watch_id);
  g_object_unref (modem->sim);
  g_clear_pointer (&modem->mccmnc, g_free);
  g_free (modem->imsi);
  g_free (modem->path);
  g_clear_object (&modem->modem);
  g_clear_object (&modem->modem_messaging);
  g_clear_object (&modem->object);
  modem->modem_available = FALSE;
  modem->modem_ready = FALSE;
}

static int
modemmanager_init (void)
{
  GDBusConnection *connection = mms_dbus_get_connection ();
  g_autoptr(GError) error = NULL;

  DBG ("Starting Modem Manager Plugin!");
  // Set up modem Structure to be used here
  modem = g_try_new0 (struct modem_data, 1);

  if (modem == NULL)
    {
      g_critical ("Could not allocate space for modem data!");
      return -ENOMEM;
    }
  modem->service = mms_service_create ();
  mms_service_set_identity (modem->service, IDENTIFIER);

  modem->modemsettings = mms_settings_open (IDENTIFIER, SETTINGS_STORE);

  modem->message_center = g_key_file_get_string (modem->modemsettings,
                                                 SETTINGS_GROUP,
                                                 "CarrierMMSC", &error);
  if (error)
    {
      DBG ("No MMSC was configured!");
      modem->message_center = g_strdup ("http://mms.invalid");
      g_key_file_set_string (modem->modemsettings, SETTINGS_GROUP,
                             "CarrierMMSC", modem->message_center);
      g_clear_error (&error);
    }

  modem->mms_apn = g_key_file_get_string (modem->modemsettings,
                                          SETTINGS_GROUP,
                                          "MMS_APN", &error);
  if (error)
    {
      DBG ("No MMS APN was configured!");
      modem->mms_apn = g_strdup ("apn.invalid");
      g_key_file_set_string (modem->modemsettings, SETTINGS_GROUP,
                             "MMS_APN", modem->mms_apn);
      g_clear_error (&error);
    }

  modem->MMS_proxy = g_key_file_get_string (modem->modemsettings,
                                            SETTINGS_GROUP,
                                            "CarrierMMSProxy", &error);
  if (error)
    {
      DBG ("Setting MMS Procy to NULL");
      modem->MMS_proxy = g_strdup ("NULL");
      g_key_file_set_string (modem->modemsettings, SETTINGS_GROUP,
                             "CarrierMMSProxy", modem->MMS_proxy);
      g_clear_error (&error);
    }
  if (g_strcmp0 (modem->MMS_proxy, "NULL") == 0 || !*modem->MMS_proxy)
    g_clear_pointer (&modem->MMS_proxy, g_free);

  modem->default_number = g_key_file_get_string (modem->modemsettings,
                                                 SETTINGS_GROUP,
                                                 "DefaultModemNumber", &error);
  if (error)
    {
      DBG ("No Default Modem Number was configured! Setting to NULL.");
      modem->default_number = g_strdup ("NULL");

      g_key_file_set_string (modem->modemsettings, SETTINGS_GROUP,
                             "DefaultModemNumber", modem->default_number);
      g_clear_error (&error);
    }
  if (g_strcmp0 (modem->default_number, "NULL") == 0 || !*modem->default_number)
    g_clear_pointer (&modem->default_number, g_free);

  modem->auto_process_on_connection = g_key_file_get_boolean (modem->modemsettings,
                                                              SETTINGS_GROUP,
                                                              "AutoProcessOnConnection",
                                                              &error);
  if (error)
    {
      DBG ("Auto Process On Connection was not configured! Setting to TRUE.");
      modem->auto_process_on_connection = TRUE;
      g_key_file_set_boolean (modem->modemsettings, SETTINGS_GROUP,
                              "AutoProcessOnConnection", modem->auto_process_on_connection);
      g_clear_error (&error);
    }

  modem->autoprocess_sms_wap = g_key_file_get_boolean (modem->modemsettings,
                                                       SETTINGS_GROUP,
                                                       "AutoProcessSMSWAP",
                                                       &error);
  if (error)
    {
      DBG ("Auto Process SMS WAP was not configured! Setting to TRUE.");
      modem->autoprocess_sms_wap = TRUE;
      g_key_file_set_boolean (modem->modemsettings, SETTINGS_GROUP,
                              "AutoProcessSMSWAP", modem->autoprocess_sms_wap);
      g_clear_error (&error);
    }

  modem->registered_imsi = g_key_file_get_string (modem->modemsettings,
                                                  SETTINGS_GROUP,
                                                  "IMSI", &error);
  if (error)
    {
      modem->registered_imsi = g_strdup ("invalid");
      g_key_file_set_string (modem->modemsettings, SETTINGS_GROUP,
                             "IMSI", modem->registered_imsi);
      g_clear_error (&error);
    }

  mms_settings_close (IDENTIFIER, SETTINGS_STORE, modem->modemsettings, TRUE);
  modem->modemsettings = NULL;
  introspection_data = g_dbus_node_info_new_for_xml (introspection_xml_modemmanager, NULL);
  g_assert (introspection_data != NULL);

  modem->modem_available = FALSE;
  modem->modem_ready = FALSE;
  modem->manager_available = FALSE;
  modem->context_active = FALSE;
  modem->plugin_registered = FALSE;
  modem->get_all_sms_timeout = FALSE;

  modem->registration_id = g_dbus_connection_register_object (connection,
                                                              MMS_PATH,
                                                              introspection_data->interfaces[0],
                                                              &interface_vtable,
                                                              NULL,             /* user_data */
                                                              NULL,             /* user_data_free_func */
                                                              &error);          /* GError** */
  if (error)
    {
      g_critical ("Error registering MMSD ModemManager interface: %s\n", error->message);
      g_clear_error (&error);
    }

  modem->mm_watch_id = g_bus_watch_name (G_BUS_TYPE_SYSTEM,
                                         MM_DBUS_SERVICE,
                                         G_BUS_NAME_WATCHER_FLAGS_AUTO_START,
                                         (GBusNameAppearedCallback) mm_appeared_cb,
                                         (GBusNameVanishedCallback) mm_vanished_cb,
                                         NULL,
                                         NULL);

  return 0;
}

static void
modemmanager_exit (void)
{
  GDBusConnection *connection = mms_dbus_get_connection ();
  if (modem->modem_available)
    mmsd_mm_state (MMSD_MM_STATE_NO_MODEM);
  if (modem->manager_available)
    mmsd_mm_state (MMSD_MM_STATE_NO_MANAGER);
  if (modem->context_active)
    {
      g_clear_pointer (&modem->context_interface, g_free);
      g_clear_pointer (&modem->context_path, g_free);
    }
  if (modem->plugin_registered == TRUE)
    {
      mms_service_unregister (modem->service);
      modem->modemsettings = NULL;
    }
  mms_service_unref (modem->service);
  g_dbus_connection_unregister_object (connection,
                                       modem->registration_id);
  g_free (modem->registered_imsi);
  g_free (modem->message_center);
  g_free (modem->MMS_proxy);
  g_free (modem->mms_apn);
  g_bus_unwatch_name (modem->mm_watch_id);
  g_free (modem);
  g_dbus_node_info_unref (introspection_data);
}

MMS_PLUGIN_DEFINE (modemmanager, modemmanager_init, modemmanager_exit)
