/*
 *
 *  Visual Voicemail Daemon
 *
 *  Copyright (C) 2018 Purism SPC
 *                2020-2022 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 "vvm3-activation.h"
#include "vvm.h"
#include "dbus.h"
#include "vvmutil.h"
#include "phone-utils.h"
#include "service-providers.h"

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

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

#define VVM_MODEMMANAGER_INTERFACE      VVM_SERVICE ".ModemManager"

enum {
  VVMD_MM_STATE_NO_MANAGER,
  VVMD_MM_STATE_MANAGER_FOUND,
  VVMD_MM_STATE_NO_MODEM,
  VVMD_MM_STATE_MODEM_FOUND,
  VVMD_MM_STATE_NO_MESSAGING_MODEM,
  VVMD_MM_STATE_MODEM_DISABLED,
  VVMD_MM_STATE_READY
} e_vvmd_connection;

struct modem_data
{
  struct vvm_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
  int                      provision_status;
  int                      enable_vvm;
  char                    *vvm_destination_number;
  char                    *vvm_type;
  char                    *default_number;
  char                    *carrier_prefix;
  // 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.kop316.vvm.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                 get_all_sms_timeout;
  gboolean                 strict_number_check;
};

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

/* Introspection data for the service we are exporting */
static const gchar introspection_xml_modemmanager[] =
  "<node>"
  "  <interface name='org.kop316.vvm.ModemManager'>"
  "    <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='SyncVVM'>"
  "      <annotation name='org.kop316.vvm.ModemManager' value='OnMethod'/>"
  "    </method>"
  "    <method name='CheckSubscriptonStatus'>"
  "      <annotation name='org.kop316.vvm.ModemManager' value='OnMethod'/>"
  "    </method>"
  "    <signal name='ProvisionStatusChanged'>"
  "      <arg type='s' name='name'/>"
  "      <arg type='v' name='value'/>"
  "    </signal>"
  "    <signal name='SettingsChanged'>"
  "      <arg type='sss' name='Settings'/>"
  "    </signal>"
  "  </interface>"
  "</node>";

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

static void vvmd_mm_state (int state);
static void vvmd_modem_available (void);
static void vvmd_modem_unavailable (void);
static void free_device (VvmdDevice *device);
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 void mm_sync_vvm_imap_server (void);
static void vvmd_connect_to_sms_wap (void);
static void vvmd_disconnect_from_sms_wap (void);
static void vvmd_get_all_sms (void);
static void vvmd_unsubscribe_service (void);
static void vvmd_check_subscription_status (void);
static void vvmd_subscribe_service (void);

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, "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,
                                    "{'VVMEnabled', <%b>}",
                                    modem->enable_vvm);

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


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

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

      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");

      switch (modem->provision_status)
        {
        case VVM_PROVISION_STATUS_NOT_SET:
          g_variant_builder_add_parsed (&settings_builder,
                                        "{'ProvisionStatus', <%s>}",
                                        "Not Set");
          break;
        case VVM_PROVISION_STATUS_NEW:
          g_variant_builder_add_parsed (&settings_builder,
                                        "{'ProvisionStatus', <%s>}",
                                        "New");
          break;
        case VVM_PROVISION_STATUS_READY:
          g_variant_builder_add_parsed (&settings_builder,
                                        "{'ProvisionStatus', <%s>}",
                                        "Ready");
          break;
        case VVM_PROVISION_STATUS_PROVISIONED:
          g_variant_builder_add_parsed (&settings_builder,
                                        "{'ProvisionStatus', <%s>}",
                                        "Provisioned");
          break;
        case VVM_PROVISION_STATUS_UNKNOWN:
          g_variant_builder_add_parsed (&settings_builder,
                                        "{'ProvisionStatus', <%s>}",
                                        "Unknown");
          break;
        case VVM_PROVISION_STATUS_BLOCKED:
          g_variant_builder_add_parsed (&settings_builder,
                                        "{'ProvisionStatus', <%s>}",
                                        "Blocked");
          break;
        default:
          g_variant_builder_add_parsed (&settings_builder,
                                        "{'ProvisionStatus', <%s>}",
                                        "Cannot Determine");
        }

      settings = g_variant_builder_end (&settings_builder);

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

      DBG ("All Settings: %s", g_variant_print (all_settings, TRUE));

      g_dbus_method_invocation_return_value (invocation, all_settings);
    }
  else if (g_strcmp0 (method_name, "ChangeSettings") == 0)
    {
      GVariant *variantstatus;
      gchar *dict;
      gboolean close_settings = FALSE;


      g_variant_get (parameters, "(sv)", &dict, &variantstatus);
      DBG ("Dict: %s", dict);
      if (modem->modemsettings == NULL)
        {
          close_settings = TRUE;
          modem->modemsettings = vvm_settings_open (IDENTIFIER, SETTINGS_STORE);
        }

      if (g_strcmp0 (dict, "VVMEnabled") == 0)
        {
          int VVMStatus;
          g_variant_get (variantstatus, "b", &VVMStatus);

          if (modem->enable_vvm == FALSE && VVMStatus == TRUE)
            {
              DBG ("Subscribing from VVM");
              vvmd_subscribe_service ();
            }
          else if (modem->enable_vvm == TRUE && VVMStatus == FALSE)
            {
              DBG ("Unsubscribing from VVM");
              vvmd_unsubscribe_service ();
            }
          else
            DBG ("VVM status Unchanged!");
          modem->enable_vvm = VVMStatus;

          g_key_file_set_boolean (modem->modemsettings,
                                  SETTINGS_GROUP_MODEMMANAGER,
                                  "VVMEnabled",
                                  modem->enable_vvm);

          DBG ("VVM status now: %d", modem->enable_vvm);
        }
      else if (g_strcmp0 (dict, "VVMType") == 0)
        {
          g_autofree char *vvm_type = NULL;
          g_variant_get (variantstatus, "s", &vvm_type);
          g_free (modem->vvm_type);
          modem->vvm_type = g_strdup (vvm_type);

          g_key_file_set_string (modem->modemsettings,
                                 SETTINGS_GROUP_MODEMMANAGER,
                                 "VVMType",
                                 modem->vvm_type);

          DBG ("VVM type now: %s", modem->vvm_type);
        }
      else if (g_strcmp0 (dict, "VVMDestinationNumber") == 0)
        {
          g_autofree char *vvm_dest_number = NULL;
          g_variant_get (variantstatus, "s", &vvm_dest_number);
          g_free (modem->vvm_destination_number);
          modem->vvm_destination_number = g_strdup (vvm_dest_number);

          g_key_file_set_string (modem->modemsettings,
                                 SETTINGS_GROUP_MODEMMANAGER,
                                 "VVMDestinationNumber",
                                 modem->vvm_destination_number);
          DBG ("VVM destination number now: %s", modem->vvm_destination_number);
        }
      else if (g_strcmp0 (dict, "CarrierPrefix") == 0)
        {
          g_autofree char *carrier_prefix = NULL;
          g_variant_get (variantstatus, "s", &carrier_prefix);
          g_free (modem->carrier_prefix);

          modem->carrier_prefix = g_strdup (carrier_prefix);

          g_key_file_set_string (modem->modemsettings,
                                 SETTINGS_GROUP_MODEMMANAGER,
                                 "CarrierPrefix",
                                 modem->carrier_prefix);
          DBG ("VVM carrier prefix now: %s", modem->carrier_prefix);
        }
      else if (g_strcmp0 (dict, "DefaultModemNumber") == 0)
        {
          g_autofree char *DefaultNumber = NULL;
          g_variant_get (variantstatus, "s", &DefaultNumber);
          g_free (modem->default_number);
          modem->default_number = g_strdup (DefaultNumber);

          g_key_file_set_string (modem->modemsettings,
                                 SETTINGS_GROUP_MODEMMANAGER,
                                 "DefaultModemNumber",
                                 modem->default_number);

          if (g_strcmp0 (modem->default_number, "NULL") == 0)
            {
              g_free (modem->default_number);
              modem->default_number = NULL;
            }
          DBG ("Default Modem Number set to %s", modem->default_number);
        }
      else {
          if (close_settings == TRUE)
            {
              vvm_settings_close (IDENTIFIER, SETTINGS_STORE,
                                  modem->modemsettings, FALSE);
              modem->modemsettings = NULL;
            }

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

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

      g_dbus_method_invocation_return_value (invocation, NULL);
    }
  else if (g_strcmp0 (method_name, "SyncVVM") == 0)
    {
      if (modem->state == MM_MODEM_STATE_CONNECTED)
        {
          if (modem->provision_status == VVM_PROVISION_STATUS_NEW ||
              modem->provision_status == VVM_PROVISION_STATUS_READY)
            {
              if (modem->enable_vvm)
                {
                  DBG ("Processing any unreceived VVM messages.");
                  mm_sync_vvm_imap_server ();
                  g_dbus_method_invocation_return_value (invocation, NULL);
                }
              else {
                  DBG ("You have Visual Voicemail disabled");
                  g_dbus_method_invocation_return_dbus_error (invocation,
                                                              VVM_MODEMMANAGER_INTERFACE,
                                                              "You have Visual Voicemail disabled");
                }
            }
          else {
              g_critical ("You are not subscribed to the Visual Voicemail.");
              g_dbus_method_invocation_return_dbus_error (invocation,
                                                          VVM_MODEMMANAGER_INTERFACE,
                                                          "You are not subscribed to the Visual Voicemail.");
            }
        }
      else {
          g_critical ("Modem is not ready to process any unsent/unreceived VVM messages.");
          g_dbus_method_invocation_return_dbus_error (invocation,
                                                      VVM_MODEMMANAGER_INTERFACE,
                                                      "Modem is not ready to process any unsent/unreceived VVM messages.");
        }
    }
  else if (g_strcmp0 (method_name, "CheckSubscriptonStatus") == 0)
    {
      DBG ("Checking Subscription Status.");
      vvmd_check_subscription_status ();
      g_dbus_method_invocation_return_value (invocation, NULL);
    }
}

static const GDBusInterfaceVTable interface_vtable = {
  handle_method_call
};

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))
    g_debug ("Message delete finish");
  else
    g_debug ("Couldn't delete SMS - error: %s", error ? error->message : "unknown");
}

static void
mm_sync_vvm_imap_server (void)
{
  if (modem->state == MM_MODEM_STATE_CONNECTED)
    {
      if (modem->provision_status == VVM_PROVISION_STATUS_NEW ||
          modem->provision_status == VVM_PROVISION_STATUS_READY)
        {
          if (modem->enable_vvm)
            {
              DBG ("Processing any unreceived VVM messages.");
              /*
               * Prevent a race condition from the connection turning active to usable
               * for vvmd
               */
              sleep (1);
              vvm_service_sync_vvm_imap_server (modem->service);
            }
          else
            DBG ("You have Visual Voicemail disabled");
        }
      else
        g_critical ("You are not subscribed to the Visual Voicemail.");
    }
  else
    g_critical ("Modem is not ready to process any unsent/unreceived VVM messages.");
}

static void
emit_provision_status_changed (const char *new_status)
{
  GDBusConnection *connection = vvm_dbus_get_connection ();
  GVariant *changedproperty;
  g_autoptr(GError) error = NULL;
  DBG ("Provision Status changed to %s", new_status);
  changedproperty = g_variant_new_parsed ("('ProvisionStatus', <%s>)", new_status);
  g_dbus_connection_emit_signal (connection,
                                 NULL,
                                 VVM_PATH,
                                 VVM_MODEMMANAGER_INTERFACE,
                                 "ProvisionStatusChanged",
                                 changedproperty,
                                 &error);

  if (error != NULL)
    g_warning ("Error in Proxy call: %s\n", error->message);
}

static void
vvm_process_status_message (struct sms_control_message *sms_msg)
{
  int old_provision_status = modem->provision_status;
  g_autofree char *provisionstatus = NULL;

  if (g_strcmp0 (sms_msg->type, "status") != 0)
    {
      g_critical ("This is not a status sms!");
      return;
    }
  DBG ("Previous Provisioning Status to %d", old_provision_status);
  modem->provision_status = sms_msg->provision_status;
  DBG ("Setting Provisioning Status to %d", modem->provision_status);
  g_key_file_set_integer (modem->modemsettings, SETTINGS_GROUP_MODEMMANAGER,
                          "ProvisionStatus", modem->provision_status);

  if (sms_msg->mailbox_hostname)
    vvm_service_set_hostname (modem->service, sms_msg->mailbox_hostname);

  if (sms_msg->mailbox_port)
    vvm_service_set_port (modem->service, sms_msg->mailbox_port);

  if (sms_msg->mailbox_username)
    vvm_service_set_username (modem->service, sms_msg->mailbox_username);

  if (sms_msg->mailbox_password)
    vvm_service_set_password (modem->service, sms_msg->mailbox_password);

  if (sms_msg->language)
    vvm_service_set_language (modem->service, sms_msg->language);

  if (sms_msg->greeting_length)
    vvm_service_set_greeting_length (modem->service, sms_msg->greeting_length);

  if (sms_msg->voice_signature_length)
    vvm_service_set_voice_signature_length (modem->service, sms_msg->voice_signature_length);

  if (sms_msg->TUI_password_length)
    vvm_service_set_tui_password_length (modem->service, sms_msg->TUI_password_length);

  vvm_service_set_subscription_configuration (modem->service, modem->vvm_type);

  switch (modem->provision_status)
    {
    case VVM_PROVISION_STATUS_NOT_SET:
      provisionstatus = g_strdup ("Not Set");
      break;
    case VVM_PROVISION_STATUS_NEW:
      provisionstatus = g_strdup ("New");
      break;
    case VVM_PROVISION_STATUS_READY:
      provisionstatus = g_strdup ("Ready");
      break;
    case VVM_PROVISION_STATUS_PROVISIONED:
      provisionstatus = g_strdup ("Provisioned");
      break;
    case VVM_PROVISION_STATUS_UNKNOWN:
      provisionstatus = g_strdup ("Unknown");
      break;
    case VVM_PROVISION_STATUS_BLOCKED:
      provisionstatus = g_strdup ("Blocked");
      break;
    default:
      provisionstatus = g_strdup ("Cannot Determine");
    }

  if (old_provision_status != modem->provision_status)
    emit_provision_status_changed (provisionstatus);

  if (modem->provision_status == VVM_PROVISION_STATUS_PROVISIONED)
    {
      if (old_provision_status == VVM_PROVISION_STATUS_NEW ||
          old_provision_status == VVM_PROVISION_STATUS_READY)
        vvm_service_deactivate_vvm_imap_server (modem->service);

      if (modem->enable_vvm)
        {
          vvmd_subscribe_service ();
          DBG ("Subscribing to your carrier's VVM service");
        }
    }
  else if (modem->provision_status == VVM_PROVISION_STATUS_NEW ||
           modem->provision_status == VVM_PROVISION_STATUS_READY)
    {
      if (!modem->enable_vvm)
        {
          DBG ("Unsubscribing from your carrier's VVM service");
          vvmd_unsubscribe_service ();
        }
      else {
          /*
           * The carrier may send a STATUS message to update something,
           * like the IMAP server. You need to make sure you incorporate the fix!
           */
          vvm_service_deactivate_vvm_imap_server (modem->service);
          mm_sync_vvm_imap_server ();
        }
    }
  else if (modem->provision_status == VVM_PROVISION_STATUS_UNKNOWN)
    {
      //VZW reports that the Provision status is unknown if the VVM service is unprovisioned
      if (g_strcmp0 (modem->vvm_type, "vvm3") == 0)
        {
          if (modem->state == MM_MODEM_STATE_CONNECTED)
            {
              const gchar *const *modem_number_ref;
              const char *country_code;
              g_autofree char *stripped_number = NULL;

              country_code = get_country_iso_for_mcc (modem->imsi);
              if (g_strcmp0 (country_code, "US") != 0)
                {
                  g_warning ("you are using a standard for VZW USA but the country code is not US");
                  return;
                }

              modem_number_ref = mm_modem_get_own_numbers (modem->modem);
              if (modem_number_ref == NULL)
                {
                  g_critical ("Could not get modem number!");
                  return;
                }
              g_autofree char *formatted_number = NULL;
              formatted_number = phone_utils_format_number_e164 (*modem_number_ref,
                                                                 country_code,
                                                                 FALSE);
              if (formatted_number)
                {
                  char *stripped = NULL;
                  if (g_str_has_prefix (formatted_number, "+1"))
                    stripped = formatted_number + strlen ("+1");
                  else
                    stripped = formatted_number;

                  stripped_number = g_strdup (stripped);
                }
              else {
                  g_critical ("Could not format modem number!");
                  return;
                }
              vvm3_activation (stripped_number, sms_msg->activate_url);
            }
          else {
              g_warning ("Modem is disconnected, cannot attempt to provision vvm3");
              return;
            }
        }
      else
        g_critical ("Provisioning Status is unknown! Contact your Carrier");
    }
  else if (modem->provision_status == VVM_PROVISION_STATUS_BLOCKED)
    g_critical ("Provisioning Status is blocked! Contact your Carrier");
}

static gboolean
retry_sync_vvm_imap_server (gpointer user_data)
{
  DBG ("Retrying sync....");

  mm_sync_vvm_imap_server ();
  return FALSE;
}

static gboolean
new_sync_vvm_timeout (gpointer user_data)
{
  char *dup_msg = user_data;
  DBG ("Processing VVM sync message.");
  if (vvm_service_new_vvm (modem->service, dup_msg, NULL, modem->vvm_type) == FALSE)
    {
      g_critical ("Error making VVM!.");
      g_timeout_add_seconds (60, retry_sync_vvm_imap_server, NULL);
    }
  g_free (dup_msg);
  return FALSE;
}

static void
vvmd_process_sms (MMSms *sms)
{
  char            *message;
  const char      *path;
  struct sms_control_message *sms_msg;

  if (modem->strict_number_check)
  {
    const char      *number;
    const char      *country_code;

    number = mm_sms_get_number (sms);
    country_code = vvm_service_get_country_code (modem->service);

    if (!number || !*number)
      return;

    if (!country_code || !*country_code)
      return;

    if (!phone_utils_simple_is_valid (number))
      {
        DBG ("SMS number does not pass simple validation");
        return;
      }

    /* VVM numbers are generally regarded as unvalid by libphonenumber */
    if (phone_utils_is_valid (number, country_code))
      {
        DBG ("SMS number is not a VVM related number");
        return;
      }
  } else
    g_warning ("You disabled strict number checking.");

  message = mm_sms_dup_text (sms);
  if (message)
    {
      int sms_message_type;

      sms_message_type = vvm_util_parse_sms_message_type (message,
                                                          modem->carrier_prefix,
                                                          modem->vvm_type);

      if (sms_message_type == SMS_MESSAGE_STATUS)
        {
          sms_msg = g_try_new0 (struct sms_control_message, 1);

          if (sms_msg == NULL)
            g_critical ("Could not allocate space for modem data!");
          DBG ("Processing VVM status message.");
          vvm_util_parse_status_sms_message (message, sms_msg, modem->vvm_type);
          vvm_process_status_message (sms_msg);
          vvm_util_delete_status_message (sms_msg);
        }
      else if (sms_message_type == SMS_MESSAGE_SYNC)
        {
          DBG ("Timeout waiting for sync message.");
          char *dup_msg = g_strdup (message);
          g_timeout_add_seconds (1, new_sync_vvm_timeout, dup_msg);
        }
      else
        DBG ("This is not a VVM related Message");

      if (sms_message_type == SMS_MESSAGE_STATUS || sms_message_type == SMS_MESSAGE_SYNC)
        {
          path = mm_sms_get_path (sms);
          if (path)
            {
              g_critical ("Deleting SMS!");
              mm_modem_messaging_delete (modem->modem_messaging,
                                         path,
                                         NULL,
                                         (GAsyncReadyCallback)cb_sms_delete_finish,
                                         sms);
            }
          else
            g_critical ("vvmd cannot delete SMS at this time!");
        }
      g_free (message);
    }
}

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)
    vvmd_process_sms (sms);
}

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

  if (!modem->enable_vvm)
    {
      DBG ("VVM is not enabled! Not Processing SMS");
      return;
    }

  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)
        vvmd_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 (!list)
    g_critical ("Couldn't get SMS list - error: %s",
                error ? error->message : "unknown");
  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));
            vvmd_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);
  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 (!list)
    g_debug ("Couldn't get SMS list - error: %s", error ? error->message : "unknown");
  else
    for (l = list; l; l = g_list_next (l))
      {
        sms = g_object_ref (MM_SMS (l->data));
        vvmd_check_pdu_type (sms);
      }
}

static gboolean
vvmd_get_all_sms_timeout (gpointer user_data)
{
  DBG ("Removing timeout to vvmd_get_all_sms()");
  modem->get_all_sms_timeout = FALSE;

  return FALSE;
}

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

  if (modem->get_all_sms_timeout == FALSE)
    {
      DBG ("Searching for any new SMS WAPs...");

      // This is needed in case vvmd is
      // trying to come out of suspend
      sleep (1);

      mm_modem_messaging_list (modem->modem_messaging,
                               NULL,
                               (GAsyncReadyCallback)cb_sms_list_all_ready,
                               NULL);
      modem->get_all_sms_timeout = TRUE;
      DBG ("Adding timeout to vvmd_get_all_sms()");
      //Adding five second timeout
      g_timeout_add_seconds (5, vvmd_get_all_sms_timeout, NULL);
    }
  else
    DBG ("vvmd_get_all_sms() timed out!");
}

static void
vvmd_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
vvmd_connect_to_sms_wap (void)
{
  MmGdbusModemMessaging *gdbus_sms;
  if (modem->modem_messaging == NULL)
    {
      g_critical ("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
vvmd_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),
                                    VVMD_MM_MODEM_TIMEOUT);


  DBG ("%s", __func__);

  return TRUE;
}

static void
free_device (VvmdDevice *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 (VvmdDevice  *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
vvmd_mm_add_object (MMObject *obj)
{
  VvmdDevice         *device;
  const gchar        *object_path;
  const gchar *const *modem_number_ref;
  MMSim              *sim;
  const gchar        *country_code;
  g_autoptr(GError)   error = NULL;
  gchar              *modem_number_formatted;

  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 != NULL)
    {
      DBG ("Checking if this modem number matches: %s", modem->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)));
      if (modem_number_ref == NULL)
        {
          g_critical ("Could not get number!");
          return;
        }
      modem_number_formatted = phone_utils_format_number_e164 (*modem_number_ref, country_code, FALSE);
      DBG ("Formatted Number %s", modem_number_formatted);
      if (g_strcmp0 (modem_number_formatted, modem->default_number) != 0)
        {
          g_free (modem_number_formatted);
          g_critical ("This modem does not match default number!");
          return;
        }
      g_free (modem_number_formatted);
    }
  else
    DBG ("Not checking for a default Modem");

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

  if (vvmd_mm_init_modem (obj) == TRUE)
    {
      device = g_new0 (VvmdDevice, 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);

      vvmd_mm_state (VVMD_MM_STATE_MODEM_FOUND);
    }
  else
    vvmd_mm_state (VVMD_MM_STATE_NO_MESSAGING_MODEM);
}

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

  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;
      vvmd_mm_add_object (MM_OBJECT (l->data));
    }

  if (!has_modem)
    vvmd_mm_state (VVMD_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)))
    vvmd_mm_add_object (MM_OBJECT (object));
  else
    g_warning ("New Object does not have Messaging feature, ignoring....");
}

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)
    vvmd_mm_state (VVMD_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)
    vvmd_mm_state (VVMD_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)
    {
      vvmd_mm_state (VVMD_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);

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

      vvmd_mm_state (VVMD_MM_STATE_NO_MANAGER);
    }
}

static void
vvmd_mm_get_modem_state (void)
{
  if (!modem->modem)
    {
      vvmd_mm_state (VVMD_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");
          vvmd_mm_state (VVMD_MM_STATE_MODEM_DISABLED);
          return;
        case MM_MODEM_STATE_UNKNOWN:
          DBG ("MM_MODEM_STATE_UNKNOWN");
          vvmd_mm_state (VVMD_MM_STATE_MODEM_DISABLED);
          return;
        case MM_MODEM_STATE_LOCKED:
          DBG ("MM_MODEM_STATE_FAILED");
          vvmd_mm_state (VVMD_MM_STATE_MODEM_DISABLED);
          return;
        case MM_MODEM_STATE_INITIALIZING:
          DBG ("MM_MODEM_STATE_INITIALIZING");
          vvmd_mm_state (VVMD_MM_STATE_MODEM_DISABLED);
          return;
        case MM_MODEM_STATE_DISABLED:
          DBG ("MM_MODEM_STATE_DISABLED");
          vvmd_mm_state (VVMD_MM_STATE_MODEM_DISABLED);
          return;
        case MM_MODEM_STATE_DISABLING:
          DBG ("MM_MODEM_STATE_DISABLING");
          vvmd_mm_state (VVMD_MM_STATE_MODEM_DISABLED);
          return;
        case MM_MODEM_STATE_ENABLING:
          DBG ("MM_MODEM_STATE_ENABLING");
          vvmd_mm_state (VVMD_MM_STATE_MODEM_DISABLED);
          return;
        default:
          DBG ("MM_MODEM_OTHER_BAD_STATE: %d", modem->state);
          vvmd_mm_state (VVMD_MM_STATE_MODEM_DISABLED);
          return;
        }
    }
  DBG ("MM_MODEM_GOOD_STATE: %d", modem->state);
  vvmd_mm_state (VVMD_MM_STATE_READY);

  if (modem->state == MM_MODEM_STATE_CONNECTED)
    {
      GList    *bearer_list, *l;
      MMBearer *modem_bearer;
      bearer_list = mm_modem_list_bearers_sync (modem->modem, NULL, NULL);
      /* There is only one bearer */
      for (l = bearer_list; l != NULL; l = l->next)
        {
          const gchar *mailbox_interface;
          modem_bearer = (MMBearer *) l->data;
          mailbox_interface = mm_bearer_get_interface (modem_bearer);
          vvm_service_set_interface (modem->service, mailbox_interface);
        }

      if (g_strcmp0 (modem->vvm_type, "vvm3") == 0)
        if (modem->provision_status == VVM_PROVISION_STATUS_NOT_SET ||
            modem->provision_status == VVM_PROVISION_STATUS_UNKNOWN)
          {
            if (!modem->enable_vvm)
              {
                DBG ("VVM is not enabled!");
                return;
              }
            vvmd_check_subscription_status ();
            return;
          }
    }

  // Automatically retrieve VVM messages when the modem is connected
  mm_sync_vvm_imap_server ();
  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;
  vvmd_mm_get_modem_state ();
}

static void
vvmd_mm_state (int state)
{
  switch (state)
    {
    case VVMD_MM_STATE_MODEM_FOUND:
      DBG ("VVMD_MM_STATE_MODEM_FOUND");
      if (!modem->modem_available)
        vvmd_modem_available ();
      break;
    case VVMD_MM_STATE_NO_MODEM:
      if (modem->modem_available)
        {
          vvmd_modem_unavailable ();
          DBG ("Modem vanished, Disabling plugin");
        }
      else
        DBG ("Could not connect to modem");
      modem->modem_available = FALSE;
      modem->modem_ready = FALSE;
      DBG ("VVMD_MM_STATE_NO_MODEM");
      break;

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

    case VVMD_MM_STATE_MODEM_DISABLED:
      DBG ("VVMD_MM_STATE_MODEM_DISABLED");
      modem->modem_ready = FALSE;
      break;

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

    case VVMD_MM_STATE_NO_MANAGER:
      if (modem->modem_available)
        {
          vvmd_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 {
          g_critical ("Could not connect to ModemManager");
          DBG ("Could not connect to ModemManager");
        }
      modem->manager_available = FALSE;
      DBG ("VVMD_MM_STATE_NO_MANAGER");
      break;

    case VVMD_MM_STATE_READY:
      DBG ("VVMD_MM_STATE_READY");
      modem->modem_ready = TRUE;
      DBG ("Setting Bearer Handler");
      vvmd_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));
  DBG ("Modem Manager appeared");

  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");
  vvmd_mm_state (VVMD_MM_STATE_NO_MANAGER);
}

static void
cb_sms_send_finish (MMSms        *sms,
                    GAsyncResult *result,
                    gpointer      user_data)
{
  g_autoptr(GError)  error = NULL;
  gboolean           fin;

  fin = mm_sms_send_finish (sms, result, &error);

  if (!fin)
    g_critical ("Couldn't send SMS - error: %s", error ? error->message : "unknown");
  else {
      DBG ("Successfully sent SMS: %s", mm_sms_get_path (sms));
      const char      *path;
      path = mm_sms_get_path (sms);
      if (path)
        mm_modem_messaging_delete (modem->modem_messaging,
                                   path,
                                   NULL,
                                   (GAsyncReadyCallback)cb_sms_delete_finish,
                                   sms);
      else
        g_critical ("vvmd cannot process SMS at this time!");
    }
}

static gboolean
vvmd_mm_create_sms (const gchar *message)
{
  MMSmsProperties *properties;
  gboolean         delivery_report = 0;
  MMSms             *sms;
  g_autoptr(GError)  error = NULL;

  properties = mm_sms_properties_new ();

  mm_sms_properties_set_text (properties, message);
  mm_sms_properties_set_number (properties, modem->vvm_destination_number);
  mm_sms_properties_set_delivery_report_request (properties, delivery_report);

  DBG ("Creating new SMS");

  sms = mm_modem_messaging_create_sync (modem->modem_messaging,
                                        properties,
                                        NULL,
                                        &error);

  g_object_unref (properties);

  if (!sms)
    DBG ("Couldn't create new SMS - error: %s", error ? error->message : "unknown");
  else {
      DBG ("Successfully created new SMS: %s", mm_sms_get_path (sms));

      mm_sms_send (sms,
                   NULL,
                   (GAsyncReadyCallback)cb_sms_send_finish,
                   NULL);

      g_object_unref (sms);
    }

  return TRUE;
}


static void
vvmd_subscribe_service (void)
{
  g_autofree char *message = NULL;

  if (modem->provision_status == VVM_PROVISION_STATUS_NOT_SET)
    {
      DBG ("Your provisioning status is unknown");
      DBG ("Checking Subscription Status");
      vvmd_check_subscription_status ();
      return;
    }
  else if (g_strcmp0 (modem->vvm_type, "vvm3") == 0)
    {
      DBG ("Checking vvm3 subscription status");
      vvmd_check_subscription_status ();
      return;
    }
  else
    DBG ("You are not be subscribed to your carrier's VVM service");

  if (modem->modem_available == FALSE)
    {
      DBG ("Modem not available");
      return;
    }

  if (modem->modem_ready == FALSE)
    {
      DBG ("Modem not ready");
      return;
    }

  message = vvm_util_create_activate_sms (modem->carrier_prefix, modem->vvm_type);

  if (message)
    {
      DBG ("Message: %s", message);
      vvmd_mm_create_sms (message);
    }
}

static void
vvmd_unsubscribe_service (void)
{
  g_autofree char *message = NULL;

  DBG ("Unsubscribing from your carrier's VVM service");
  if (modem->provision_status == VVM_PROVISION_STATUS_NEW ||
      modem->provision_status == VVM_PROVISION_STATUS_READY)
    if (modem->plugin_registered == TRUE)
      vvm_service_deactivate_vvm_imap_server (modem->service);

  if (modem->modem_available == FALSE)
    {
      DBG ("Modem not available");
      return;
    }

  if (modem->modem_ready == FALSE)
    {
      DBG ("Modem not ready");
      return;
    }

  message = vvm_util_create_deactivate_sms (modem->carrier_prefix, modem->vvm_type);

  if (message)
    {
      DBG ("Message: %s", message);
      vvmd_mm_create_sms (message);
    }
}

static void
vvmd_check_subscription_status (void)
{
  g_autofree char *message = NULL;
  DBG ("Checking your carrier's VVM service");
  if (g_strcmp0 (modem->vvm_type, "vvm3") == 0)
    if (modem->provision_status == VVM_PROVISION_STATUS_NOT_SET ||
        modem->provision_status == VVM_PROVISION_STATUS_UNKNOWN)
      {
        if (modem->state == MM_MODEM_STATE_CONNECTED)
          // Give the connection a second so it is usable
          sleep (1);
        else {
            DBG ("Modem is disconnected, cannot attempt to provision vvm3");
            return;
          }
      }

  if (modem->modem_available == FALSE)
    {
      DBG ("Modem not available");
      return;
    }

  if (modem->modem_ready == FALSE)
    {
      DBG ("Modem not ready");
      return;
    }

  message = vvm_util_create_status_sms (modem->carrier_prefix, modem->vvm_type);

  if (message)
    {
      DBG ("Message: %s", message);
      vvmd_mm_create_sms (message);
    }
}

static void
find_settings_cb (const char *carrier,
                  const char *vvm_std_to_copy,
                  const char *dest_num,
                  const char *carrier_prefix_to_copy,
                  GError     *error,
                  gpointer    user_data)
{
  g_autofree char *vvm_std = NULL;
  g_autofree char *carrier_prefix = NULL;
  GDBusConnection *connection = vvm_dbus_get_connection ();
  if (error)
    {
      g_warning ("Could not find settings: %s", error->message);
      g_warning ("Your VVM settings for 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");
      return;
    }

  DBG ("Autopopulating VVM Carrier Settings");

  if (g_strcmp0 ("AT&T", carrier) == 0 && g_strcmp0 ("proprietary", vvm_std_to_copy) == 0)
    {
      const gchar *const *modem_number_ref;
      const char *country_code;
      modem_number_ref = mm_modem_get_own_numbers (modem->modem);
      country_code = get_country_iso_for_mcc (modem->imsi);
      if (g_strcmp0 (country_code, "US") != 0)
        {
          g_warning ("you are parsing AT&T USA but the country code is not US");
          return;
        }
      else
        vvm_std = g_strdup ("AT&TUSAProprietary");
      if (modem_number_ref != NULL)
        {
          g_autofree char *formatted_number = NULL;
          formatted_number = phone_utils_format_number_e164 (*modem_number_ref,
                                                             country_code,
                                                             FALSE);
          if (formatted_number)
            {
              char *stripped = NULL;
              if (g_str_has_prefix (formatted_number, "+1"))
                stripped = formatted_number + strlen ("+1");
              else
                stripped = formatted_number;

              carrier_prefix = g_strdup (stripped);
            }
          else {
              g_critical ("Could not format modem number!");
              carrier_prefix = g_strdup (*modem_number_ref);
            }
        }
      else {
          g_critical ("Could not get modem number!");
          carrier_prefix = g_strdup ("invalid");
        }
    }
  else if (g_strcmp0 ("Free Mobile", carrier) == 0 && g_strcmp0 ("proprietary", vvm_std_to_copy) == 0)
    {
      const char *country_code;
      country_code = get_country_iso_for_mcc (modem->imsi);
      if (g_strcmp0 (country_code, "FR") != 0)
        {
          g_warning ("you are parsing French Free mobile but the country code is not FR");
          return;
        }
      else
        vvm_std = g_strdup ("FreeMobileProprietary");
    }
  else {
      carrier_prefix = g_strdup (carrier_prefix_to_copy);
      vvm_std = g_strdup (vvm_std_to_copy);
    }

  g_free (modem->vvm_type);
  modem->vvm_type = g_strdup (vvm_std);

  g_key_file_set_string (modem->modemsettings, SETTINGS_GROUP_MODEMMANAGER,
                         "VVMType", modem->vvm_type);

  g_free (modem->vvm_destination_number);
  modem->vvm_destination_number = g_strdup (dest_num);

  g_key_file_set_string (modem->modemsettings, SETTINGS_GROUP_MODEMMANAGER,
                         "VVMDestinationNumber", modem->vvm_destination_number);

  g_free (modem->carrier_prefix);
  modem->carrier_prefix = g_strdup (carrier_prefix);

  g_key_file_set_string (modem->modemsettings, SETTINGS_GROUP_MODEMMANAGER,
                         "CarrierPrefix", modem->carrier_prefix);

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

  g_dbus_connection_emit_signal (connection,
                                 NULL,
                                 VVM_PATH,
                                 VVM_MODEMMANAGER_INTERFACE,
                                 "SettingsChanged",
                                 g_variant_new ("(sss)", modem->vvm_type, modem->vvm_destination_number, modem->carrier_prefix),
                                 NULL);
}

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

  /* Settings are default, no use in saving them */
  if (g_strcmp0 (modem->vvm_type, "type_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,
                         "VVMType",
                         modem->vvm_type);

  g_key_file_set_string (modem->modemsettings,
                         imsi_hash,
                         "VVMDestinationNumber",
                         modem->vvm_destination_number);

  g_key_file_set_string (modem->modemsettings,
                         imsi_hash,
                         "CarrierPrefix",
                         modem->carrier_prefix);

  vvm_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->vvm_type = g_key_file_get_string (modem->modemsettings,
                                           imsi_hash,
                                           "VVMType", &error);
  if (error)
    return FALSE;

  modem->vvm_destination_number = g_key_file_get_string (modem->modemsettings,
                                                         imsi_hash,
                                                         "VVMDestinationNumber", &error);
  if (error)
    {
      g_clear_pointer (&modem->vvm_type, g_free);
      return FALSE;
    }

  modem->carrier_prefix = g_key_file_get_string (modem->modemsettings,
                                                 imsi_hash,
                                                 "CarrierPrefix", &error);
  if (error)
    {
      g_clear_pointer (&modem->vvm_type, g_free);
      g_clear_pointer (&modem->vvm_destination_number, g_free);
      return FALSE;
    }

  return TRUE;
}

static void
vvmd_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 VVM Service");
          vvm_service_register (modem->service);
          modem->modemsettings = vvm_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);

          if (g_strcmp0 (modem->imsi, modem->registered_imsi) != 0)
            {
              if (g_strcmp0 (modem->registered_imsi, "invalid") != 0)
                {
                  GDBusConnection *connection = vvm_dbus_get_connection ();
                  DBG ("IMSI different from registered, Disabling VVM and resetting settings");

                  mm_save_settings ();

                  g_free (modem->vvm_type);
                  g_free (modem->carrier_prefix);
                  g_free (modem->vvm_destination_number);

                  if (mm_retrieve_settings ())
                    DBG ("Reloading settings");
                  else {
                      DBG ("Resetting settings");
                      modem->vvm_type = g_strdup ("type_invalid");
                      modem->carrier_prefix = g_strdup ("//VVM");
                      modem->vvm_destination_number = g_strdup ("number_invalid");
                    }


                  g_key_file_set_string (modem->modemsettings,
                                         SETTINGS_GROUP_MODEMMANAGER,
                                         "VVMType",
                                         modem->vvm_type);

                  g_key_file_set_string (modem->modemsettings,
                                         SETTINGS_GROUP_MODEMMANAGER,
                                         "CarrierPrefix",
                                         modem->carrier_prefix);

                  g_key_file_set_string (modem->modemsettings,
                                         SETTINGS_GROUP_MODEMMANAGER,
                                         "VVMDestinationNumber",
                                         modem->vvm_destination_number);

                  modem->enable_vvm = FALSE;
                  g_key_file_set_boolean (modem->modemsettings,
                                          SETTINGS_GROUP_MODEMMANAGER,
                                          "VVMEnabled",
                                          modem->enable_vvm);

                  vvm_service_deactivate_vvm_imap_server (modem->service);

                  modem->provision_status = VVM_PROVISION_STATUS_NOT_SET;
                  emit_provision_status_changed ("Not Set");
                  g_key_file_set_integer (modem->modemsettings,
                                          SETTINGS_GROUP_MODEMMANAGER,
                                          "ProvisionStatus",
                                          modem->provision_status);

                  g_dbus_connection_emit_signal (connection,
                                                 NULL,
                                                 VVM_PATH,
                                                 VVM_MODEMMANAGER_INTERFACE,
                                                 "SettingsChanged",
                                                 g_variant_new ("(sss)", modem->vvm_type, modem->vvm_destination_number, modem->carrier_prefix),
                                                 NULL);
                }
              g_free (modem->registered_imsi);
              modem->registered_imsi = g_strdup (modem->imsi);
              g_key_file_set_string (modem->modemsettings, SETTINGS_GROUP_MODEMMANAGER,
                                     "IMSI", modem->registered_imsi);

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

          vvm_service_set_country_code (modem->service,
                                        mm_sim_get_imsi (modem->sim));
          if (strstr (modem->vvm_type, "invalid"))
            {
              g_free (modem->mccmnc);
              modem->mccmnc = mm_sim_dup_operator_identifier (modem->sim);
              if (!modem->mccmnc)
                modem->mccmnc = g_strndup (modem->imsi, 6);

              DBG ("Attempting to autopopulate VVM carrier settings");
              vvmd_service_providers_find_settings (MOBILE_BROADBAND_PROVIDER_INFO_DATABASE,
                                                    modem->mccmnc,
                                                    find_settings_cb,
                                                    NULL);
            }
          else
            DBG ("VVM carrier settings already set");
        }
      else
        g_warning ("IMSI is NULL!");

      modem_number_ref = mm_modem_get_own_numbers (modem->modem);
      if (modem_number_ref != NULL)
        vvm_service_set_own_number (modem->service,
                                    *modem_number_ref);
      else
        g_critical ("Could not get modem number!");

      vvmd_connect_to_sms_wap ();

      if (modem->provision_status == VVM_PROVISION_STATUS_NOT_SET ||
          modem->provision_status == VVM_PROVISION_STATUS_UNKNOWN ||
          modem->provision_status == VVM_PROVISION_STATUS_BLOCKED)
        {
          DBG ("Checking status of carrier subscription");
          vvmd_check_subscription_status ();
        }
      else if (modem->provision_status == VVM_PROVISION_STATUS_PROVISIONED)
        {
          if (modem->enable_vvm)
            {
              DBG ("Subscribing to your carrier's VVM service");
              vvmd_subscribe_service ();
            }
          else
            DBG ("VVM is disabled, not subscribing to your carrier's VVM service");
        }
      else if (modem->provision_status == VVM_PROVISION_STATUS_NEW ||
               modem->provision_status == VVM_PROVISION_STATUS_READY)
        {
          if (!modem->enable_vvm)
            {
              vvmd_unsubscribe_service ();
              DBG ("Unsubscribing to your carrier's VVM service");
            }
          else
            DBG ("VVM is enabled and subscribed!");
        }

      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);
      modem->state = mm_modem_get_state (modem->modem);
      vvmd_mm_get_modem_state ();
    }
  else
    g_critical ("Something very bad happened at vvmd_modem_available()!");
}

static void
vvmd_modem_unavailable (void)
{
  MmGdbusModem *gdbus_modem;

  gdbus_modem = MM_GDBUS_MODEM (modem->modem);
  vvmd_disconnect_from_sms_wap ();
  g_signal_handler_disconnect (gdbus_modem,
                               modem->modem_state_watch_id);
  g_object_unref (modem->sim);
  g_clear_pointer (&modem->mccmnc, g_free);
  g_clear_pointer (&modem->imsi, g_free);
  g_clear_pointer (&modem->path, g_free);
  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 = vvm_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 = vvm_service_create ();
  vvm_service_set_identity (modem->service, IDENTIFIER);

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

  modem->enable_vvm = g_key_file_get_boolean (modem->modemsettings,
                                              SETTINGS_GROUP_MODEMMANAGER,
                                              "VVMEnabled",
                                              &error);
  if (error)
    {
      g_critical ("Enabling Visual Voicemail was not configured! Setting to FALSE.");
      modem->enable_vvm = FALSE;

      g_key_file_set_boolean (modem->modemsettings, SETTINGS_GROUP_MODEMMANAGER,
                              "VVMEnabled", modem->enable_vvm);
      g_clear_error (&error);
    }

  modem->vvm_type = g_key_file_get_string (modem->modemsettings,
                                           SETTINGS_GROUP_MODEMMANAGER,
                                           "VVMType", &error);
  if (error)
    {
      modem->vvm_type = g_strdup ("type_invalid");

      g_key_file_set_string (modem->modemsettings, SETTINGS_GROUP_MODEMMANAGER,
                             "VVMType", modem->vvm_type);
      g_clear_error (&error);
    }

  modem->vvm_destination_number = g_key_file_get_string (modem->modemsettings,
                                                         SETTINGS_GROUP_MODEMMANAGER,
                                                         "VVMDestinationNumber", &error);
  if (error)
    {
      modem->vvm_destination_number = g_strdup ("number_invalid");

      g_key_file_set_string (modem->modemsettings, SETTINGS_GROUP_MODEMMANAGER,
                             "VVMDestinationNumber", modem->vvm_destination_number);
      g_clear_error (&error);
    }

  modem->carrier_prefix = g_key_file_get_string (modem->modemsettings,
                                                 SETTINGS_GROUP_MODEMMANAGER,
                                                 "CarrierPrefix", &error);
  if (error)
    {
      modem->carrier_prefix = g_strdup ("//VVM");

      g_key_file_set_string (modem->modemsettings, SETTINGS_GROUP_MODEMMANAGER,
                             "CarrierPrefix", modem->carrier_prefix);
      g_clear_error (&error);
    }

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

      g_key_file_set_string (modem->modemsettings, SETTINGS_GROUP_MODEMMANAGER,
                             "DefaultModemNumber", modem->default_number);
      g_clear_error (&error);
    }
  if (g_strcmp0 (modem->default_number, "NULL") == 0)
    {
      g_free (modem->default_number);
      modem->default_number = NULL;
    }

  modem->provision_status = g_key_file_get_integer (modem->modemsettings,
                                                    SETTINGS_GROUP_MODEMMANAGER,
                                                    "ProvisionStatus", &error);

  //If the user did not enable VVM, set provision status to "Not Set"
  if (error)
    {
      modem->provision_status = VVM_PROVISION_STATUS_NOT_SET;

      g_key_file_set_integer (modem->modemsettings, SETTINGS_GROUP_MODEMMANAGER,
                              "ProvisionStatus", modem->provision_status);
      g_clear_error (&error);
    }

  modem->registered_imsi = g_key_file_get_string (modem->modemsettings,
                                                  SETTINGS_GROUP_MODEMMANAGER,
                                                  "IMSI", &error);
  if (error)
    {
      modem->registered_imsi = g_strdup ("invalid");

      g_key_file_set_string (modem->modemsettings, SETTINGS_GROUP_MODEMMANAGER,
                             "IMSI", modem->registered_imsi);
      g_clear_error (&error);
    }

  modem->strict_number_check = g_key_file_get_boolean (modem->modemsettings,
                                                       SETTINGS_GROUP_MODEMMANAGER,
                                                       "StrictNumberCheck",
                                                       &error);
  if (error)
    {
      modem->strict_number_check = TRUE;

      g_key_file_set_boolean (modem->modemsettings, SETTINGS_GROUP_MODEMMANAGER,
                              "StrictNumberCheck", modem->strict_number_check);
      g_clear_error (&error);
    }

  vvm_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,
                                                              VVM_PATH,
                                                              introspection_data->interfaces[0],
                                                              &interface_vtable,
                                                              NULL,
                                                              NULL,
                                                              &error);

  if (error)
    {
      g_critical ("Error registering VVMD 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 = vvm_dbus_get_connection ();

  if (modem->modem_available)
    vvmd_mm_state (VVMD_MM_STATE_NO_MODEM);
  if (modem->manager_available)
    vvmd_mm_state (VVMD_MM_STATE_NO_MANAGER);
  if (modem->plugin_registered == TRUE)
    {
      vvm_service_unregister (modem->service);
      modem->modemsettings = NULL;
    }
  vvm_service_unref (modem->service);
  g_dbus_connection_unregister_object (connection,
                                       modem->registration_id);

  g_free (modem->registered_imsi);
  g_free (modem->carrier_prefix);
  g_free (modem->default_number);
  g_free (modem->vvm_type);
  g_free (modem->vvm_destination_number);
  g_bus_unwatch_name (modem->mm_watch_id);
  g_free (modem);
  g_dbus_node_info_unref (introspection_data);
}

VVM_PLUGIN_DEFINE (modemmanager, modemmanager_init, modemmanager_exit)
