/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * mmcli -- Control modem status & access information from the command line
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 2 of the License, or
 * (at your option) any later version.
 *
 * 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, see <http://www.gnu.org/licenses/>.
 *
 * Copyright (C) 2018 Aleksander Morgado <aleksander@aleksander.es>
 */

#include <stdio.h>
#include <string.h>

#define _LIBMM_INSIDE_MMCLI
#include <libmm-glib.h>
#include "mm-common-helpers.h"
#include "mmcli-output.h"

/******************************************************************************/
/* List of sections (grouped fields) displayed in the human-friendly output */

typedef struct {
    const gchar *name;
} SectionInfo;

static SectionInfo section_infos[] = {
    [MMC_S_MODEM_GENERAL]              = { "General"              },
    [MMC_S_MODEM_HARDWARE]             = { "Hardware"             },
    [MMC_S_MODEM_SYSTEM]               = { "System"               },
    [MMC_S_MODEM_NUMBERS]              = { "Numbers"              },
    [MMC_S_MODEM_STATUS]               = { "Status"               },
    [MMC_S_MODEM_MODES]                = { "Modes"                },
    [MMC_S_MODEM_BANDS]                = { "Bands"                },
    [MMC_S_MODEM_IP]                   = { "IP"                   },
    [MMC_S_MODEM_CELL_INFO]            = { "Cell info"            },
    [MMC_S_MODEM_3GPP]                 = { "3GPP"                 },
    [MMC_S_MODEM_3GPP_EPS]             = { "3GPP EPS"             },
    [MMC_S_MODEM_3GPP_5GNR]            = { "3GPP 5GNR"            },
    [MMC_S_MODEM_3GPP_SCAN]            = { "3GPP scan"            },
    [MMC_S_MODEM_3GPP_USSD]            = { "3GPP USSD"            },
    [MMC_S_MODEM_3GPP_PROFILE_MANAGER] = { "3GPP profile manager" },
    [MMC_S_MODEM_CDMA]                 = { "CDMA"                 },
    [MMC_S_MODEM_SIM]                  = { "SIM"                  },
    [MMC_S_MODEM_BEARER]               = { "Bearer"               },
    [MMC_S_MODEM_TIME]                 = { "Time"                 },
    [MMC_S_MODEM_TIMEZONE]             = { "Timezone"             },
    [MMC_S_MODEM_MESSAGING]            = { "Messaging"            },
    [MMC_S_MODEM_SIGNAL]               = { "Signal"               },
    [MMC_S_MODEM_SIGNAL_CDMA1X]        = { "CDMA1x"               },
    [MMC_S_MODEM_SIGNAL_EVDO]          = { "EV-DO"                },
    [MMC_S_MODEM_SIGNAL_GSM]           = { "GSM"                  },
    [MMC_S_MODEM_SIGNAL_UMTS]          = { "UMTS"                 },
    [MMC_S_MODEM_SIGNAL_LTE]           = { "LTE"                  },
    [MMC_S_MODEM_SIGNAL_5G]            = { "5G"                   },
    [MMC_S_MODEM_OMA]                  = { "OMA"                  },
    [MMC_S_MODEM_OMA_CURRENT]          = { "Current session"      },
    [MMC_S_MODEM_OMA_PENDING]          = { "Pending sessions"     },
    [MMC_S_MODEM_LOCATION]             = { "Location"             },
    [MMC_S_MODEM_LOCATION_3GPP]        = { "3GPP"                 },
    [MMC_S_MODEM_LOCATION_GPS]         = { "GPS"                  },
    [MMC_S_MODEM_LOCATION_CDMABS]      = { "CDMA BS"              },
    [MMC_S_MODEM_FIRMWARE]             = { "Firmware"             },
    [MMC_S_MODEM_FIRMWARE_FASTBOOT]    = { "Fastboot settings"    },
    [MMC_S_MODEM_VOICE]                = { "Voice"                },
    [MMC_S_MODEM_SAR]                  = { "SAR"                  },
    [MMC_S_BEARER_GENERAL]             = { "General"              },
    [MMC_S_BEARER_STATUS]              = { "Status"               },
    [MMC_S_BEARER_PROPERTIES]          = { "Properties"           },
    [MMC_S_BEARER_IPV4_CONFIG]         = { "IPv4 configuration"   },
    [MMC_S_BEARER_IPV6_CONFIG]         = { "IPv6 configuration"   },
    [MMC_S_BEARER_STATS]               = { "Statistics"           },
    [MMC_S_CALL_GENERAL]               = { "General"              },
    [MMC_S_CALL_PROPERTIES]            = { "Properties"           },
    [MMC_S_CALL_AUDIO_FORMAT]          = { "Audio format"         },
    [MMC_S_SMS_GENERAL]                = { "General"              },
    [MMC_S_SMS_CONTENT]                = { "Content"              },
    [MMC_S_SMS_PROPERTIES]             = { "Properties"           },
    [MMC_S_SIM_GENERAL]                = { "General"              },
    [MMC_S_SIM_PROPERTIES]             = { "Properties"           },
};

/******************************************************************************/
/* List of fields */

typedef struct {
    const gchar  *key;
    const gchar  *name;
    MmcS          section;
} FieldInfo;

static FieldInfo field_infos[] = {
    [MMC_F_GENERAL_DBUS_PATH]                        = { "modem.dbus-path",                                 "path",                     MMC_S_MODEM_GENERAL,              },
    [MMC_F_GENERAL_DEVICE_ID]                        = { "modem.generic.device-identifier",                 "device id",                MMC_S_MODEM_GENERAL,              },
    [MMC_F_HARDWARE_MANUFACTURER]                    = { "modem.generic.manufacturer",                      "manufacturer",             MMC_S_MODEM_HARDWARE,             },
    [MMC_F_HARDWARE_MODEL]                           = { "modem.generic.model",                             "model",                    MMC_S_MODEM_HARDWARE,             },
    [MMC_F_HARDWARE_REVISION]                        = { "modem.generic.revision",                          "firmware revision",        MMC_S_MODEM_HARDWARE,             },
    [MMC_F_HARDWARE_CARRIER_CONF]                    = { "modem.generic.carrier-configuration",             "carrier config",           MMC_S_MODEM_HARDWARE,             },
    [MMC_F_HARDWARE_CARRIER_CONF_REV]                = { "modem.generic.carrier-configuration-revision",    "carrier config revision",  MMC_S_MODEM_HARDWARE,             },
    [MMC_F_HARDWARE_HW_REVISION]                     = { "modem.generic.hardware-revision",                 "h/w revision",             MMC_S_MODEM_HARDWARE,             },
    [MMC_F_HARDWARE_SUPPORTED_CAPABILITIES]          = { "modem.generic.supported-capabilities",            "supported",                MMC_S_MODEM_HARDWARE,             },
    [MMC_F_HARDWARE_CURRENT_CAPABILITIES]            = { "modem.generic.current-capabilities",              "current",                  MMC_S_MODEM_HARDWARE,             },
    [MMC_F_HARDWARE_EQUIPMENT_ID]                    = { "modem.generic.equipment-identifier",              "equipment id",             MMC_S_MODEM_HARDWARE,             },
    [MMC_F_SYSTEM_DEVICE]                            = { "modem.generic.device",                            "device",                   MMC_S_MODEM_SYSTEM,               },
    [MMC_F_SYSTEM_DRIVERS]                           = { "modem.generic.drivers",                           "drivers",                  MMC_S_MODEM_SYSTEM,               },
    [MMC_F_SYSTEM_PLUGIN]                            = { "modem.generic.plugin",                            "plugin",                   MMC_S_MODEM_SYSTEM,               },
    [MMC_F_SYSTEM_PRIMARY_PORT]                      = { "modem.generic.primary-port",                      "primary port",             MMC_S_MODEM_SYSTEM,               },
    [MMC_F_SYSTEM_PORTS]                             = { "modem.generic.ports",                             "ports",                    MMC_S_MODEM_SYSTEM,               },
    [MMC_F_NUMBERS_OWN]                              = { "modem.generic.own-numbers",                       "own",                      MMC_S_MODEM_NUMBERS,              },
    [MMC_F_STATUS_LOCK]                              = { "modem.generic.unlock-required",                   "lock",                     MMC_S_MODEM_STATUS,               },
    [MMC_F_STATUS_UNLOCK_RETRIES]                    = { "modem.generic.unlock-retries",                    "unlock retries",           MMC_S_MODEM_STATUS,               },
    [MMC_F_STATUS_STATE]                             = { "modem.generic.state",                             "state",                    MMC_S_MODEM_STATUS,               },
    [MMC_F_STATUS_FAILED_REASON]                     = { "modem.generic.state-failed-reason",               "failed reason",            MMC_S_MODEM_STATUS,               },
    [MMC_F_STATUS_POWER_STATE]                       = { "modem.generic.power-state",                       "power state",              MMC_S_MODEM_STATUS,               },
    [MMC_F_STATUS_ACCESS_TECH]                       = { "modem.generic.access-technologies",               "access tech",              MMC_S_MODEM_STATUS,               },
    [MMC_F_STATUS_SIGNAL_QUALITY_VALUE]              = { "modem.generic.signal-quality.value",              "signal quality",           MMC_S_MODEM_STATUS,               },
    [MMC_F_STATUS_SIGNAL_QUALITY_RECENT]             = { "modem.generic.signal-quality.recent",             NULL,                       MMC_S_UNKNOWN,                    },
    [MMC_F_MODES_SUPPORTED]                          = { "modem.generic.supported-modes",                   "supported",                MMC_S_MODEM_MODES,                },
    [MMC_F_MODES_CURRENT]                            = { "modem.generic.current-modes",                     "current",                  MMC_S_MODEM_MODES,                },
    [MMC_F_BANDS_SUPPORTED]                          = { "modem.generic.supported-bands",                   "supported",                MMC_S_MODEM_BANDS,                },
    [MMC_F_BANDS_CURRENT]                            = { "modem.generic.current-bands",                     "current",                  MMC_S_MODEM_BANDS,                },
    [MMC_F_IP_SUPPORTED]                             = { "modem.generic.supported-ip-families",             "supported",                MMC_S_MODEM_IP,                   },
    [MMC_F_CELL_INFO]                                = { "modem.generic.cell-info",                         "cells",                    MMC_S_MODEM_CELL_INFO,            },
    [MMC_F_3GPP_IMEI]                                = { "modem.3gpp.imei",                                 "imei",                     MMC_S_MODEM_3GPP,                 },
    [MMC_F_3GPP_ENABLED_LOCKS]                       = { "modem.3gpp.enabled-locks",                        "enabled locks",            MMC_S_MODEM_3GPP,                 },
    [MMC_F_3GPP_OPERATOR_ID]                         = { "modem.3gpp.operator-code",                        "operator id",              MMC_S_MODEM_3GPP,                 },
    [MMC_F_3GPP_OPERATOR_NAME]                       = { "modem.3gpp.operator-name",                        "operator name",            MMC_S_MODEM_3GPP,                 },
    [MMC_F_3GPP_REGISTRATION]                        = { "modem.3gpp.registration-state",                   "registration",             MMC_S_MODEM_3GPP,                 },
    [MMC_F_3GPP_PACKET_SERVICE_STATE]                = { "modem.3gpp.packet-service-state",                 "packet service state",     MMC_S_MODEM_3GPP,                 },
    [MMC_F_3GPP_PCO]                                 = { "modem.3gpp.pco",                                  "pco",                      MMC_S_MODEM_3GPP,                 },
    [MMC_F_3GPP_EPS_UE_MODE]                         = { "modem.3gpp.eps.ue-mode-operation",                "ue mode of operation",     MMC_S_MODEM_3GPP_EPS,             },
    [MMC_F_3GPP_EPS_INITIAL_BEARER_PATH]             = { "modem.3gpp.eps.initial-bearer.dbus-path",         "initial bearer path",      MMC_S_MODEM_3GPP_EPS,             },
    [MMC_F_3GPP_EPS_BEARER_SETTINGS_APN]             = { "modem.3gpp.eps.initial-bearer.settings.apn",      "initial bearer apn",       MMC_S_MODEM_3GPP_EPS,             },
    [MMC_F_3GPP_EPS_BEARER_SETTINGS_IP_TYPE]         = { "modem.3gpp.eps.initial-bearer.settings.ip-type",  "initial bearer ip type",   MMC_S_MODEM_3GPP_EPS,             },
    [MMC_F_3GPP_EPS_BEARER_SETTINGS_USER]            = { "modem.3gpp.eps.initial-bearer.settings.user",     "initial bearer user",      MMC_S_MODEM_3GPP_EPS,             },
    [MMC_F_3GPP_EPS_BEARER_SETTINGS_PASSWORD]        = { "modem.3gpp.eps.initial-bearer.settings.password", "initial bearer password",  MMC_S_MODEM_3GPP_EPS,             },
    [MMC_F_3GPP_5GNR_REGISTRATION_MICO_MODE]         = { "modem.3gpp.5gnr.registration-settings.mico-mode", "mico mode",                MMC_S_MODEM_3GPP_5GNR,            },
    [MMC_F_3GPP_5GNR_REGISTRATION_DRX_CYCLE]         = { "modem.3gpp.5gnr.registration-settings.drx-cycle", "drx cycle",                MMC_S_MODEM_3GPP_5GNR,            },
    [MMC_F_3GPP_SCAN_NETWORKS]                       = { "modem.3gpp.scan-networks",                        "networks",                 MMC_S_MODEM_3GPP_SCAN,            },
    [MMC_F_3GPP_USSD_STATUS]                         = { "modem.3gpp.ussd.status",                          "status",                   MMC_S_MODEM_3GPP_USSD,            },
    [MMC_F_3GPP_USSD_NETWORK_REQUEST]                = { "modem.3gpp.ussd.network-request",                 "network request",          MMC_S_MODEM_3GPP_USSD,            },
    [MMC_F_3GPP_USSD_NETWORK_NOTIFICATION]           = { "modem.3gpp.ussd.network-notification",            "network notification",     MMC_S_MODEM_3GPP_USSD,            },
    [MMC_F_3GPP_PROFILE_MANAGER_INDEX_FIELD]         = { "modem.3gpp.profile-manager.index-field",          "index field",              MMC_S_MODEM_3GPP_PROFILE_MANAGER, },
    [MMC_F_3GPP_PROFILE_MANAGER_LIST]                = { "modem.3gpp.profile-manager.list",                 "list",                     MMC_S_MODEM_3GPP_PROFILE_MANAGER, },
    [MMC_F_3GPP_PROFILE_MANAGER_SET]                 = { "modem.3gpp.profile-manager.set",                  "set",                      MMC_S_MODEM_3GPP_PROFILE_MANAGER, },
    [MMC_F_CDMA_MEID]                                = { "modem.cdma.meid",                                 "meid",                     MMC_S_MODEM_CDMA,                 },
    [MMC_F_CDMA_ESN]                                 = { "modem.cdma.esn",                                  "esn",                      MMC_S_MODEM_CDMA,                 },
    [MMC_F_CDMA_SID]                                 = { "modem.cdma.sid",                                  "sid",                      MMC_S_MODEM_CDMA,                 },
    [MMC_F_CDMA_NID]                                 = { "modem.cdma.nid",                                  "nid",                      MMC_S_MODEM_CDMA,                 },
    [MMC_F_CDMA_REGISTRATION_CDMA1X]                 = { "modem.cdma.cdma1x-registration-state",            "registration cdma1x",      MMC_S_MODEM_CDMA,                 },
    [MMC_F_CDMA_REGISTRATION_EVDO]                   = { "modem.cdma.evdo-registration-state",              "registration evdo",        MMC_S_MODEM_CDMA,                 },
    [MMC_F_CDMA_ACTIVATION]                          = { "modem.cdma.activation-state",                     "activation",               MMC_S_MODEM_CDMA,                 },
    [MMC_F_SIM_PATH]                                 = { "modem.generic.sim",                               "primary sim path",         MMC_S_MODEM_SIM,                  },
    [MMC_F_SIM_PRIMARY_SLOT]                         = { "modem.generic.primary-sim-slot",                  NULL,                       MMC_S_MODEM_SIM,                  },
    [MMC_F_SIM_SLOT_PATHS]                           = { "modem.generic.sim-slots",                         "sim slot paths",           MMC_S_MODEM_SIM,                  },
    [MMC_F_BEARER_PATHS]                             = { "modem.generic.bearers",                           "paths",                    MMC_S_MODEM_BEARER,               },
    [MMC_F_TIME_CURRENT]                             = { "modem.time.current",                              "current",                  MMC_S_MODEM_TIME,                 },
    [MMC_F_TIMEZONE_CURRENT]                         = { "modem.timezone.current",                          "current",                  MMC_S_MODEM_TIMEZONE,             },
    [MMC_F_TIMEZONE_DST_OFFSET]                      = { "modem.time.dst-offset",                           "dst offset",               MMC_S_MODEM_TIMEZONE,             },
    [MMC_F_TIMEZONE_LEAP_SECONDS]                    = { "modem.time.leap-seconds",                         "leap seconds",             MMC_S_MODEM_TIMEZONE,             },
    [MMC_F_MESSAGING_SUPPORTED_STORAGES]             = { "modem.messaging.supported-storages",              "supported storages",       MMC_S_MODEM_MESSAGING,            },
    [MMC_F_MESSAGING_DEFAULT_STORAGES]               = { "modem.messaging.default-storages",                "default storages",         MMC_S_MODEM_MESSAGING,            },
    [MMC_F_MESSAGING_CREATED_SMS]                    = { "modem.messaging.created-sms",                     "created sms",              MMC_S_MODEM_MESSAGING,            },
    [MMC_F_SIGNAL_REFRESH_RATE]                      = { "modem.signal.refresh.rate",                       "refresh rate",             MMC_S_MODEM_SIGNAL,               },
    [MMC_F_SIGNAL_RSSI_THRESHOLD]                    = { "modem.signal.threshold.rssi",                     "rssi threshold",           MMC_S_MODEM_SIGNAL,               },
    [MMC_F_SIGNAL_ERROR_RATE_THRESHOLD]              = { "modem.signal.threshold.error-rate",               "error rate threshold",     MMC_S_MODEM_SIGNAL,               },
    [MMC_F_SIGNAL_CDMA1X_RSSI]                       = { "modem.signal.cdma1x.rssi",                        "rssi",                     MMC_S_MODEM_SIGNAL_CDMA1X,        },
    [MMC_F_SIGNAL_CDMA1X_ECIO]                       = { "modem.signal.cdma1x.ecio",                        "ecio",                     MMC_S_MODEM_SIGNAL_CDMA1X,        },
    [MMC_F_SIGNAL_CDMA1X_ERROR_RATE]                 = { "modem.signal.cdma1x.error-rate",                  "error rate",               MMC_S_MODEM_SIGNAL_CDMA1X,        },
    [MMC_F_SIGNAL_EVDO_RSSI]                         = { "modem.signal.evdo.rssi",                          "rssi",                     MMC_S_MODEM_SIGNAL_EVDO,          },
    [MMC_F_SIGNAL_EVDO_ECIO]                         = { "modem.signal.evdo.ecio",                          "ecio",                     MMC_S_MODEM_SIGNAL_EVDO,          },
    [MMC_F_SIGNAL_EVDO_SINR]                         = { "modem.signal.evdo.sinr",                          "sinr",                     MMC_S_MODEM_SIGNAL_EVDO,          },
    [MMC_F_SIGNAL_EVDO_IO]                           = { "modem.signal.evdo.io",                            "io",                       MMC_S_MODEM_SIGNAL_EVDO,          },
    [MMC_F_SIGNAL_EVDO_ERROR_RATE]                   = { "modem.signal.evdo.error-rate",                    "error rate",               MMC_S_MODEM_SIGNAL_EVDO,          },
    [MMC_F_SIGNAL_GSM_RSSI]                          = { "modem.signal.gsm.rssi",                           "rssi",                     MMC_S_MODEM_SIGNAL_GSM,           },
    [MMC_F_SIGNAL_GSM_ERROR_RATE]                    = { "modem.signal.gsm.error-rate",                     "error rate",               MMC_S_MODEM_SIGNAL_GSM,           },
    [MMC_F_SIGNAL_UMTS_RSSI]                         = { "modem.signal.umts.rssi",                          "rssi",                     MMC_S_MODEM_SIGNAL_UMTS,          },
    [MMC_F_SIGNAL_UMTS_RSCP]                         = { "modem.signal.umts.rscp",                          "rscp",                     MMC_S_MODEM_SIGNAL_UMTS,          },
    [MMC_F_SIGNAL_UMTS_ECIO]                         = { "modem.signal.umts.ecio",                          "ecio",                     MMC_S_MODEM_SIGNAL_UMTS,          },
    [MMC_F_SIGNAL_UMTS_ERROR_RATE]                   = { "modem.signal.umts.error-rate",                    "error rate",               MMC_S_MODEM_SIGNAL_UMTS,          },
    [MMC_F_SIGNAL_LTE_RSSI]                          = { "modem.signal.lte.rssi",                           "rssi",                     MMC_S_MODEM_SIGNAL_LTE,           },
    [MMC_F_SIGNAL_LTE_RSRQ]                          = { "modem.signal.lte.rsrq",                           "rsrq",                     MMC_S_MODEM_SIGNAL_LTE,           },
    [MMC_F_SIGNAL_LTE_RSRP]                          = { "modem.signal.lte.rsrp",                           "rsrp",                     MMC_S_MODEM_SIGNAL_LTE,           },
    [MMC_F_SIGNAL_LTE_SNR]                           = { "modem.signal.lte.snr",                            "s/n",                      MMC_S_MODEM_SIGNAL_LTE,           },
    [MMC_F_SIGNAL_LTE_ERROR_RATE]                    = { "modem.signal.lte.error-rate",                     "error rate",               MMC_S_MODEM_SIGNAL_LTE,           },
    [MMC_F_SIGNAL_5G_RSRQ]                           = { "modem.signal.5g.rsrq",                            "rsrq",                     MMC_S_MODEM_SIGNAL_5G,            },
    [MMC_F_SIGNAL_5G_RSRP]                           = { "modem.signal.5g.rsrp",                            "rsrp",                     MMC_S_MODEM_SIGNAL_5G,            },
    [MMC_F_SIGNAL_5G_SNR]                            = { "modem.signal.5g.snr",                             "s/n",                      MMC_S_MODEM_SIGNAL_5G,            },
    [MMC_F_SIGNAL_5G_ERROR_RATE]                     = { "modem.signal.5g.error-rate",                      "error rate",               MMC_S_MODEM_SIGNAL_5G,            },
    [MMC_F_OMA_FEATURES]                             = { "modem.oma.features",                              "features",                 MMC_S_MODEM_OMA,                  },
    [MMC_F_OMA_CURRENT_TYPE]                         = { "modem.oma.current.type",                          "type",                     MMC_S_MODEM_OMA_CURRENT,          },
    [MMC_F_OMA_CURRENT_STATE]                        = { "modem.oma.current.state",                         "state",                    MMC_S_MODEM_OMA_CURRENT,          },
    [MMC_F_OMA_PENDING_SESSIONS]                     = { "modem.oma.pending-sessions",                      "sessions",                 MMC_S_MODEM_OMA_PENDING,          },
    [MMC_F_LOCATION_CAPABILITIES]                    = { "modem.location.capabilities",                     "capabilities",             MMC_S_MODEM_LOCATION,             },
    [MMC_F_LOCATION_ENABLED]                         = { "modem.location.enabled",                          "enabled",                  MMC_S_MODEM_LOCATION,             },
    [MMC_F_LOCATION_SIGNALS]                         = { "modem.location.signals",                          "signals",                  MMC_S_MODEM_LOCATION,             },
    [MMC_F_LOCATION_GPS_REFRESH_RATE]                = { "modem.location.gps.refresh-rate",                 "refresh rate",             MMC_S_MODEM_LOCATION_GPS,         },
    [MMC_F_LOCATION_GPS_SUPL_SERVER]                 = { "modem.location.gps.supl-server",                  "a-gps supl server",        MMC_S_MODEM_LOCATION_GPS,         },
    [MMC_F_LOCATION_GPS_ASSISTANCE]                  = { "modem.location.gps.assistance",                   "supported assistance",     MMC_S_MODEM_LOCATION_GPS,         },
    [MMC_F_LOCATION_GPS_ASSISTANCE_SERVERS]          = { "modem.location.gps.assistance-servers",           "assistance servers",       MMC_S_MODEM_LOCATION_GPS,         },
    [MMC_F_LOCATION_3GPP_MCC]                        = { "modem.location.3gpp.mcc",                         "operator mcc",             MMC_S_MODEM_LOCATION_3GPP,        },
    [MMC_F_LOCATION_3GPP_MNC]                        = { "modem.location.3gpp.mnc",                         "operator mnc",             MMC_S_MODEM_LOCATION_3GPP,        },
    [MMC_F_LOCATION_3GPP_LAC]                        = { "modem.location.3gpp.lac",                         "location area code",       MMC_S_MODEM_LOCATION_3GPP,        },
    [MMC_F_LOCATION_3GPP_TAC]                        = { "modem.location.3gpp.tac",                         "tracking area code",       MMC_S_MODEM_LOCATION_3GPP,        },
    [MMC_F_LOCATION_3GPP_CID]                        = { "modem.location.3gpp.cid",                         "cell id",                  MMC_S_MODEM_LOCATION_3GPP,        },
    [MMC_F_LOCATION_GPS_NMEA]                        = { "modem.location.gps.nmea",                         "nmea",                     MMC_S_MODEM_LOCATION_GPS,         },
    [MMC_F_LOCATION_GPS_UTC]                         = { "modem.location.gps.utc",                          "utc",                      MMC_S_MODEM_LOCATION_GPS,         },
    [MMC_F_LOCATION_GPS_LONG]                        = { "modem.location.gps.longitude",                    "longitude",                MMC_S_MODEM_LOCATION_GPS,         },
    [MMC_F_LOCATION_GPS_LAT]                         = { "modem.location.gps.latitude",                     "latitude",                 MMC_S_MODEM_LOCATION_GPS,         },
    [MMC_F_LOCATION_GPS_ALT]                         = { "modem.location.gps.altitude",                     "altitude",                 MMC_S_MODEM_LOCATION_GPS,         },
    [MMC_F_LOCATION_CDMABS_LONG]                     = { "modem.location.cdma-bs.longitude",                "longitude",                MMC_S_MODEM_LOCATION_CDMABS,      },
    [MMC_F_LOCATION_CDMABS_LAT]                      = { "modem.location.cdma-bs.latitude",                 "latitude",                 MMC_S_MODEM_LOCATION_CDMABS,      },
    [MMC_F_FIRMWARE_LIST]                            = { "modem.firmware.list",                             "list",                     MMC_S_MODEM_FIRMWARE,             },
    [MMC_F_FIRMWARE_METHOD]                          = { "modem.firmware.method",                           "method",                   MMC_S_MODEM_FIRMWARE,             },
    [MMC_F_FIRMWARE_DEVICE_IDS]                      = { "modem.firmware.device-ids",                       "device ids",               MMC_S_MODEM_FIRMWARE,             },
    [MMC_F_FIRMWARE_VERSION]                         = { "modem.firmware.version",                          "version",                  MMC_S_MODEM_FIRMWARE,             },
    [MMC_F_FIRMWARE_FASTBOOT_AT]                     = { "modem.firmware.fastboot.at",                      "at command",               MMC_S_MODEM_FIRMWARE_FASTBOOT,    },
    [MMC_F_VOICE_EMERGENCY_ONLY]                     = { "modem.voice.emergency-only",                      "emergency only",           MMC_S_MODEM_VOICE,                },
    [MMC_F_BEARER_GENERAL_DBUS_PATH]                 = { "bearer.dbus-path",                                "path",                     MMC_S_BEARER_GENERAL,             },
    [MMC_F_BEARER_GENERAL_TYPE]                      = { "bearer.type",                                     "type",                     MMC_S_BEARER_GENERAL,             },
    [MMC_F_BEARER_STATUS_CONNECTED]                  = { "bearer.status.connected",                         "connected",                MMC_S_BEARER_STATUS,              },
    [MMC_F_BEARER_STATUS_CONNECTION_ERROR_NAME]      = { "bearer.status.connection-error.name",             "connection error name",    MMC_S_BEARER_STATUS,              },
    [MMC_F_BEARER_STATUS_CONNECTION_ERROR_MESSAGE]   = { "bearer.status.connection-error.message",          "connection error message", MMC_S_BEARER_STATUS,              },
    [MMC_F_BEARER_STATUS_SUSPENDED]                  = { "bearer.status.suspended",                         "suspended",                MMC_S_BEARER_STATUS,              },
    [MMC_F_BEARER_STATUS_MULTIPLEXED]                = { "bearer.status.multiplexed",                       "multiplexed",              MMC_S_BEARER_STATUS,              },
    [MMC_F_BEARER_STATUS_INTERFACE]                  = { "bearer.status.interface",                         "interface",                MMC_S_BEARER_STATUS,              },
    [MMC_F_BEARER_STATUS_IP_TIMEOUT]                 = { "bearer.status.ip-timeout",                        "ip timeout",               MMC_S_BEARER_STATUS,              },
    [MMC_F_BEARER_STATUS_PROFILE_ID]                 = { "bearer.status.profile-id",                        "profile id",               MMC_S_BEARER_STATUS,              },
    [MMC_F_BEARER_PROPERTIES_APN]                    = { "bearer.properties.apn",                           "apn",                      MMC_S_BEARER_PROPERTIES,          },
    [MMC_F_BEARER_PROPERTIES_APN_TYPE]               = { "bearer.properties.apn-type",                      "apn type",                 MMC_S_BEARER_PROPERTIES,          },
    [MMC_F_BEARER_PROPERTIES_ROAMING]                = { "bearer.properties.roaming",                       "roaming",                  MMC_S_BEARER_PROPERTIES,          },
    [MMC_F_BEARER_PROPERTIES_IP_TYPE]                = { "bearer.properties.ip-type",                       "ip type",                  MMC_S_BEARER_PROPERTIES,          },
    [MMC_F_BEARER_PROPERTIES_ALLOWED_AUTH]           = { "bearer.properties.allowed-auth",                  "allowed-auth",             MMC_S_BEARER_PROPERTIES,          },
    [MMC_F_BEARER_PROPERTIES_USER]                   = { "bearer.properties.user",                          "user",                     MMC_S_BEARER_PROPERTIES,          },
    [MMC_F_BEARER_PROPERTIES_PASSWORD]               = { "bearer.properties.password",                      "password",                 MMC_S_BEARER_PROPERTIES,          },
    [MMC_F_BEARER_PROPERTIES_PROFILE_ID]             = { "bearer.properties.profile-id",                    "profile id",               MMC_S_BEARER_PROPERTIES,          },
    [MMC_F_BEARER_PROPERTIES_NUMBER]                 = { "bearer.properties.number",                        "number",                   MMC_S_BEARER_PROPERTIES,          },
    [MMC_F_BEARER_PROPERTIES_RM_PROTOCOL]            = { "bearer.properties.rm-protocol",                   "rm protocol",              MMC_S_BEARER_PROPERTIES,          },
    [MMC_F_BEARER_PROPERTIES_ACCESS_TYPE_PREFERENCE] = { "bearer.properties.access-type-preference",        "access type preference",   MMC_S_BEARER_PROPERTIES,          },
    [MMC_F_BEARER_PROPERTIES_ROAMING_ALLOWANCE]      = { "bearer.properties.roaming-allowance",             "roaming allowance",        MMC_S_BEARER_PROPERTIES,          },
    [MMC_F_BEARER_IPV4_CONFIG_METHOD]                = { "bearer.ipv4-config.method",                       "method",                   MMC_S_BEARER_IPV4_CONFIG,         },
    [MMC_F_BEARER_IPV4_CONFIG_ADDRESS]               = { "bearer.ipv4-config.address",                      "address",                  MMC_S_BEARER_IPV4_CONFIG,         },
    [MMC_F_BEARER_IPV4_CONFIG_PREFIX]                = { "bearer.ipv4-config.prefix",                       "prefix",                   MMC_S_BEARER_IPV4_CONFIG,         },
    [MMC_F_BEARER_IPV4_CONFIG_GATEWAY]               = { "bearer.ipv4-config.gateway",                      "gateway",                  MMC_S_BEARER_IPV4_CONFIG,         },
    [MMC_F_BEARER_IPV4_CONFIG_DNS]                   = { "bearer.ipv4-config.dns",                          "dns",                      MMC_S_BEARER_IPV4_CONFIG,         },
    [MMC_F_BEARER_IPV4_CONFIG_MTU]                   = { "bearer.ipv4-config.mtu",                          "mtu",                      MMC_S_BEARER_IPV4_CONFIG,         },
    [MMC_F_BEARER_IPV6_CONFIG_METHOD]                = { "bearer.ipv6-config.method",                       "method",                   MMC_S_BEARER_IPV6_CONFIG,         },
    [MMC_F_BEARER_IPV6_CONFIG_ADDRESS]               = { "bearer.ipv6-config.address",                      "address",                  MMC_S_BEARER_IPV6_CONFIG,         },
    [MMC_F_BEARER_IPV6_CONFIG_PREFIX]                = { "bearer.ipv6-config.prefix",                       "prefix",                   MMC_S_BEARER_IPV6_CONFIG,         },
    [MMC_F_BEARER_IPV6_CONFIG_GATEWAY]               = { "bearer.ipv6-config.gateway",                      "gateway",                  MMC_S_BEARER_IPV6_CONFIG,         },
    [MMC_F_BEARER_IPV6_CONFIG_DNS]                   = { "bearer.ipv6-config.dns",                          "dns",                      MMC_S_BEARER_IPV6_CONFIG,         },
    [MMC_F_BEARER_IPV6_CONFIG_MTU]                   = { "bearer.ipv6-config.mtu",                          "mtu",                      MMC_S_BEARER_IPV6_CONFIG,         },
    [MMC_F_BEARER_STATS_START_DATE]                  = { "bearer.stats.start-date",                         "start date",               MMC_S_BEARER_STATS,               },
    [MMC_F_BEARER_STATS_DURATION]                    = { "bearer.stats.duration",                           "duration",                 MMC_S_BEARER_STATS,               },
    [MMC_F_BEARER_STATS_UPLINK_SPEED]                = { "bearer.stats.uplink-speed",                       "uplink-speed",             MMC_S_BEARER_STATS,               },
    [MMC_F_BEARER_STATS_DOWNLINK_SPEED]              = { "bearer.stats.downlink-speed",                     "downlink-speed",           MMC_S_BEARER_STATS,               },
    [MMC_F_BEARER_STATS_BYTES_RX]                    = { "bearer.stats.bytes-rx",                           "bytes rx",                 MMC_S_BEARER_STATS,               },
    [MMC_F_BEARER_STATS_BYTES_TX]                    = { "bearer.stats.bytes-tx",                           "bytes tx",                 MMC_S_BEARER_STATS,               },
    [MMC_F_BEARER_STATS_ATTEMPTS]                    = { "bearer.stats.attempts",                           "attempts",                 MMC_S_BEARER_STATS,               },
    [MMC_F_BEARER_STATS_FAILED_ATTEMPTS]             = { "bearer.stats.failed-attempts",                    "attempts",                 MMC_S_BEARER_STATS,               },
    [MMC_F_BEARER_STATS_TOTAL_DURATION]              = { "bearer.stats.total-duration",                     "total-duration",           MMC_S_BEARER_STATS,               },
    [MMC_F_BEARER_STATS_TOTAL_BYTES_RX]              = { "bearer.stats.total-bytes-rx",                     "total-bytes rx",           MMC_S_BEARER_STATS,               },
    [MMC_F_BEARER_STATS_TOTAL_BYTES_TX]              = { "bearer.stats.total-bytes-tx",                     "total-bytes tx",           MMC_S_BEARER_STATS,               },
    [MMC_F_CALL_GENERAL_DBUS_PATH]                   = { "call.dbus-path",                                  "path",                     MMC_S_CALL_GENERAL,               },
    [MMC_F_CALL_PROPERTIES_NUMBER]                   = { "call.properties.number",                          "number",                   MMC_S_CALL_PROPERTIES,            },
    [MMC_F_CALL_PROPERTIES_DIRECTION]                = { "call.properties.direction",                       "direction",                MMC_S_CALL_PROPERTIES,            },
    [MMC_F_CALL_PROPERTIES_MULTIPARTY]               = { "call.properties.multiparty",                      "multiparty",               MMC_S_CALL_PROPERTIES,            },
    [MMC_F_CALL_PROPERTIES_STATE]                    = { "call.properties.state",                           "state",                    MMC_S_CALL_PROPERTIES,            },
    [MMC_F_CALL_PROPERTIES_STATE_REASON]             = { "call.properties.state-reason",                    "state reason",             MMC_S_CALL_PROPERTIES,            },
    [MMC_F_CALL_PROPERTIES_AUDIO_PORT]               = { "call.properties.audio-port",                      "audio port",               MMC_S_CALL_PROPERTIES,            },
    [MMC_F_CALL_AUDIO_FORMAT_ENCODING]               = { "call.audio-format.encoding",                      "encoding",                 MMC_S_CALL_AUDIO_FORMAT,          },
    [MMC_F_CALL_AUDIO_FORMAT_RESOLUTION]             = { "call.audio-format.resolution",                    "resolution",               MMC_S_CALL_AUDIO_FORMAT,          },
    [MMC_F_CALL_AUDIO_FORMAT_RATE]                   = { "call.audio-format.rate",                          "rate",                     MMC_S_CALL_AUDIO_FORMAT,          },
    [MMC_F_SMS_GENERAL_DBUS_PATH]                    = { "sms.dbus-path",                                   "path",                     MMC_S_SMS_GENERAL,                },
    [MMC_F_SMS_CONTENT_NUMBER]                       = { "sms.content.number",                              "number",                   MMC_S_SMS_CONTENT,                },
    [MMC_F_SMS_CONTENT_TEXT]                         = { "sms.content.text",                                "text",                     MMC_S_SMS_CONTENT,                },
    [MMC_F_SMS_CONTENT_DATA]                         = { "sms.content.data",                                "data",                     MMC_S_SMS_CONTENT,                },
    [MMC_F_SMS_PROPERTIES_PDU_TYPE]                  = { "sms.properties.pdu-type",                         "pdu type",                 MMC_S_SMS_PROPERTIES,             },
    [MMC_F_SMS_PROPERTIES_STATE]                     = { "sms.properties.state",                            "state",                    MMC_S_SMS_PROPERTIES,             },
    [MMC_F_SMS_PROPERTIES_VALIDITY]                  = { "sms.properties.validity",                         "validity",                 MMC_S_SMS_PROPERTIES,             },
    [MMC_F_SMS_PROPERTIES_STORAGE]                   = { "sms.properties.storage",                          "storage",                  MMC_S_SMS_PROPERTIES,             },
    [MMC_F_SMS_PROPERTIES_SMSC]                      = { "sms.properties.smsc",                             "smsc",                     MMC_S_SMS_PROPERTIES,             },
    [MMC_F_SMS_PROPERTIES_CLASS]                     = { "sms.properties.class",                            "class",                    MMC_S_SMS_PROPERTIES,             },
    [MMC_F_SMS_PROPERTIES_TELESERVICE_ID]            = { "sms.properties.teleservice-id",                   "teleservice id",           MMC_S_SMS_PROPERTIES,             },
    [MMC_F_SMS_PROPERTIES_SERVICE_CATEGORY]          = { "sms.properties.service-category",                 "service category",         MMC_S_SMS_PROPERTIES,             },
    [MMC_F_SMS_PROPERTIES_DELIVERY_REPORT]           = { "sms.properties.delivery-report",                  "delivery report",          MMC_S_SMS_PROPERTIES,             },
    [MMC_F_SMS_PROPERTIES_MSG_REFERENCE]             = { "sms.properties.message-reference",                "message reference",        MMC_S_SMS_PROPERTIES,             },
    [MMC_F_SMS_PROPERTIES_TIMESTAMP]                 = { "sms.properties.timestamp",                        "timestamp",                MMC_S_SMS_PROPERTIES,             },
    [MMC_F_SMS_PROPERTIES_DELIVERY_STATE]            = { "sms.properties.delivery-state",                   "delivery state",           MMC_S_SMS_PROPERTIES,             },
    [MMC_F_SMS_PROPERTIES_DISCH_TIMESTAMP]           = { "sms.properties.discharge-timestamp",              "discharge timestamp",      MMC_S_SMS_PROPERTIES,             },
    [MMC_F_SIM_GENERAL_DBUS_PATH]                    = { "sim.dbus-path",                                   "path",                     MMC_S_SIM_GENERAL,                },
    [MMC_F_SIM_PROPERTIES_ACTIVE]                    = { "sim.properties.active",                           "active",                   MMC_S_SIM_PROPERTIES,             },
    [MMC_F_SIM_PROPERTIES_IMSI]                      = { "sim.properties.imsi",                             "imsi",                     MMC_S_SIM_PROPERTIES,             },
    [MMC_F_SIM_PROPERTIES_ICCID]                     = { "sim.properties.iccid",                            "iccid",                    MMC_S_SIM_PROPERTIES,             },
    [MMC_F_SIM_PROPERTIES_EID]                       = { "sim.properties.eid",                              "eid",                      MMC_S_SIM_PROPERTIES,             },
    [MMC_F_SIM_PROPERTIES_GID1]                      = { "sim.properties.gid1",                             "gid1",                     MMC_S_SIM_PROPERTIES,             },
    [MMC_F_SIM_PROPERTIES_GID2]                      = { "sim.properties.gid2",                             "gid2",                     MMC_S_SIM_PROPERTIES,             },
    [MMC_F_SIM_PROPERTIES_OPERATOR_ID]               = { "sim.properties.operator-code",                    "operator id",              MMC_S_SIM_PROPERTIES,             },
    [MMC_F_SIM_PROPERTIES_OPERATOR_NAME]             = { "sim.properties.operator-name",                    "operator name",            MMC_S_SIM_PROPERTIES,             },
    [MMC_F_SIM_PROPERTIES_EMERGENCY_NUMBERS]         = { "sim.properties.emergency-numbers",                "emergency numbers",        MMC_S_SIM_PROPERTIES,             },
    [MMC_F_SIM_PROPERTIES_PREFERRED_NETWORKS]        = { "sim.properties.preferred-networks",               "preferred networks",       MMC_S_SIM_PROPERTIES,             },
    [MMC_F_SIM_PROPERTIES_SIM_TYPE]                  = { "sim.properties.sim-type",                         "type",                     MMC_S_SIM_PROPERTIES,             },
    [MMC_F_SIM_PROPERTIES_ESIM_STATUS]               = { "sim.properties.esim-status",                      "esim status",              MMC_S_SIM_PROPERTIES,             },
    [MMC_F_SIM_PROPERTIES_REMOVABILITY]              = { "sim.properties.removability",                     "removability",             MMC_S_SIM_PROPERTIES,             },
    [MMC_F_SAR_STATE]                                = { "modem.sar.state",                                 "enabled",                  MMC_S_MODEM_SAR,                  },
    [MMC_F_SAR_POWER_LEVEL]                          = { "modem.sar.power-level",                           "power level",              MMC_S_MODEM_SAR,                  },
    [MMC_F_MODEM_LIST_DBUS_PATH]                     = { "modem-list",                                      "modems",                   MMC_S_UNKNOWN,                    },
    [MMC_F_SMS_LIST_DBUS_PATH]                       = { "modem.messaging.sms",                             "sms messages",             MMC_S_UNKNOWN,                    },
    [MMC_F_CALL_LIST_DBUS_PATH]                      = { "modem.voice.call",                                "calls",                    MMC_S_UNKNOWN,                    },
};

/******************************************************************************/
/* Output type selection */

static MmcOutputType selected_type = MMC_OUTPUT_TYPE_NONE;

void
mmcli_output_set (MmcOutputType type)
{
    selected_type = type;
}

MmcOutputType
mmcli_output_get (void)
{
    return selected_type;
}

/******************************************************************************/
/* Generic output management */

typedef enum {
    VALUE_TYPE_SINGLE,
    VALUE_TYPE_MULTIPLE,
    VALUE_TYPE_LISTITEM,
} ValueType;

typedef struct {
    MmcF      field;
    ValueType type;
} OutputItem;

typedef struct {
    OutputItem  base;
    gchar      *value;
} OutputItemSingle;

typedef struct {
    OutputItem   base;
    gchar      **values;
    gboolean     multiline;
    gboolean     is_object;
} OutputItemMultiple;

typedef struct {
    OutputItem   base;
    gchar       *prefix;
    gchar       *value;
    gchar       *extra;
} OutputItemListitem;

static GList *output_items;

static void
output_item_free (OutputItem *item)
{
    switch (item->type) {
        case VALUE_TYPE_SINGLE:
            g_free (((OutputItemSingle *)item)->value);
            g_slice_free (OutputItemSingle, (OutputItemSingle *)item);
            break;
        case VALUE_TYPE_MULTIPLE:
            g_strfreev (((OutputItemMultiple *)item)->values);
            g_slice_free (OutputItemMultiple, (OutputItemMultiple *)item);
            break;
        case VALUE_TYPE_LISTITEM:
            g_free (((OutputItemListitem *)item)->prefix);
            g_free (((OutputItemListitem *)item)->value);
            g_free (((OutputItemListitem *)item)->extra);
            g_slice_free (OutputItemListitem, (OutputItemListitem *)item);
            break;
        default:
            g_assert_not_reached ();
    }
}

static gboolean
filter_out_value (const gchar *value)
{
    return (!g_strcmp0 (value, "unknown") || !g_strcmp0 (value, "none"));
}

static void
output_item_new_take_single (MmcF   field,
                             gchar *value)
{
    OutputItemSingle *item;

    item = g_slice_new0 (OutputItemSingle);
    item->base.field = field;
    item->base.type = VALUE_TYPE_SINGLE;

    if (filter_out_value (value))
        g_free (value);
    else
        item->value = value;

    output_items = g_list_prepend (output_items, item);
}

static void
output_item_new_take_multiple (MmcF       field,
                               gchar    **values,
                               gboolean   multiline,
                               gboolean   is_object)
{
    OutputItemMultiple *item;

    item = g_slice_new0 (OutputItemMultiple);
    item->base.field = field;
    item->base.type = VALUE_TYPE_MULTIPLE;
    item->multiline = multiline;
    item->is_object = is_object;

    if (values && (g_strv_length (values) == 1) && filter_out_value (values[0]))
        g_strfreev (values);
    else
        item->values = values;

    output_items = g_list_prepend (output_items, item);
}

static void
output_item_new_take_listitem (MmcF   field,
                               gchar *prefix,
                               gchar *value,
                               gchar *extra)
{
    OutputItemListitem *item;

    item = g_slice_new0 (OutputItemListitem);
    item->base.field = field;
    item->base.type = VALUE_TYPE_LISTITEM;
    item->prefix = prefix;
    item->value = value;
    item->extra = extra;

    output_items = g_list_prepend (output_items, item);
}

void
mmcli_output_string_list (MmcF         field,
                          const gchar *str)
{
    gchar **split;

    split = str ? g_strsplit (str, ",", -1) : NULL;
    if (split) {
        guint i;
        for (i = 0; split[i]; i++)
            g_strstrip (split[i]);
    }

    output_item_new_take_multiple (field, split, FALSE, FALSE);
}

void
mmcli_output_string_list_take (MmcF   field,
                               gchar *str)
{
    mmcli_output_string_list (field, str);
    g_free (str);
}

void
mmcli_output_string_multiline (MmcF         field,
                               const gchar *str)
{
    gchar **split;

    split = str ? g_strsplit (str, "\n", -1) : NULL;
    if (split) {
        guint i;
        for (i = 0; split[i]; i++)
            g_strstrip (split[i]);
    }

    output_item_new_take_multiple (field, split, TRUE, FALSE);
}

void
mmcli_output_string_multiline_take (MmcF   field,
                                    gchar *str)
{
    mmcli_output_string_multiline (field, str);
    g_free (str);
}

void
mmcli_output_string_array (MmcF          field,
                           const gchar **strv,
                           gboolean      multiline)
{
    output_item_new_take_multiple (field, g_strdupv ((gchar **)strv), multiline, FALSE);
}

void
mmcli_output_string_array_take (MmcF       field,
                                gchar    **strv,
                                gboolean   multiline)
{
    output_item_new_take_multiple (field, strv, multiline, FALSE);
}

void
mmcli_output_string_array_multiline_take (MmcF       field,
                                          gchar    **strv)
{
    gchar    **merged;
    GPtrArray *pointers;

    merged = NULL;
    if (strv) {
        guint i;

        pointers = g_ptr_array_new ();
        for (i = 0; strv[i]; i++) {
            gchar **split;

            split = strv[i] ? g_strsplit (strv[i], "\n", -1) : NULL;
            if (split) {
                guint n;

                for (n = 0; split[n]; n++) {
                    if (split[n][0]) {
                        g_strstrip (split[n]);
                        g_ptr_array_add (pointers, g_strdup (split[n]));
                    }
                }
                g_strfreev (split);
            }
        }
        g_strfreev (strv);
        g_ptr_array_add (pointers, NULL);
        merged = (gchar **)g_ptr_array_free (pointers, FALSE);
    }

    output_item_new_take_multiple (field, merged, TRUE, FALSE);
}

void
mmcli_output_string (MmcF         field,
                     const gchar *str)
{
    output_item_new_take_single (field, g_strdup (str));
}

void
mmcli_output_string_take (MmcF   field,
                          gchar *str)
{
    output_item_new_take_single (field, str);
}

void
mmcli_output_string_take_typed (MmcF         field,
                                gchar       *value,
                                const gchar *type)
{
    if (value && selected_type == MMC_OUTPUT_TYPE_HUMAN) {
        gchar *aux;

        aux = g_strdup_printf ("%s %s", value, type);
        g_free (value);
        output_item_new_take_single (field, aux);
        return;
    }

    output_item_new_take_single (field, value);
}

void
mmcli_output_listitem (MmcF         field,
                       const gchar *prefix,
                       const gchar *value,
                       const gchar *extra)
{
    output_item_new_take_listitem (field, g_strdup (prefix), g_strdup (value), g_strdup (extra));
}

/******************************************************************************/
/* (Custom) Signal quality output */

void
mmcli_output_signal_quality (MMModemState state,
                             guint        value,
                             gboolean     recent)
{
    /* Merge value and recent flag in a single item in human output */
    if (selected_type == MMC_OUTPUT_TYPE_HUMAN) {
        if (state >= MM_MODEM_STATE_ENABLED)
            output_item_new_take_single (MMC_F_STATUS_SIGNAL_QUALITY_VALUE,
                                         g_strdup_printf ("%u%% (%s)", value, recent ? "recent" : "cached"));
        else
            output_item_new_take_single (MMC_F_STATUS_SIGNAL_QUALITY_VALUE, NULL);
        return;
    }

    output_item_new_take_single (MMC_F_STATUS_SIGNAL_QUALITY_VALUE,
                                 g_strdup_printf ("%u", value));
    output_item_new_take_single (MMC_F_STATUS_SIGNAL_QUALITY_RECENT,
                                 g_strdup_printf ("%s", recent ? "yes" : "no"));
}

/******************************************************************************/
/* (Custom) Bearer start date output */

void
mmcli_output_start_date (guint64 value)
{
    /* Merge value and recent flag in a single item in human output */
    if (selected_type == MMC_OUTPUT_TYPE_HUMAN) {
        output_item_new_take_single (MMC_F_BEARER_STATS_START_DATE, mm_new_iso8601_time_from_unix_time (value, NULL));
        return;
    }

    output_item_new_take_single (MMC_F_BEARER_STATS_START_DATE,
                                 g_strdup_printf ("%" G_GUINT64_FORMAT, value));
}

/******************************************************************************/
/* (Custom) State output */

void
mmcli_output_state (MMModemState             state,
                    MMModemStateFailedReason reason)
{
#define KNRM "\x1B[0m"
#define KRED "\x1B[31m"
#define KGRN "\x1B[32m"
#define KYEL "\x1B[33m"

    if (selected_type == MMC_OUTPUT_TYPE_HUMAN) {
        if (state == MM_MODEM_STATE_FAILED)
            output_item_new_take_single (MMC_F_STATUS_STATE, g_strdup_printf (KRED "%s" KNRM, mm_modem_state_get_string (state)));
        else if (state == MM_MODEM_STATE_CONNECTED)
            output_item_new_take_single (MMC_F_STATUS_STATE, g_strdup_printf (KGRN "%s" KNRM, mm_modem_state_get_string (state)));
        else if (state == MM_MODEM_STATE_CONNECTING)
            output_item_new_take_single (MMC_F_STATUS_STATE, g_strdup_printf (KYEL "%s" KNRM, mm_modem_state_get_string (state)));
        else
            output_item_new_take_single (MMC_F_STATUS_STATE, g_strdup (mm_modem_state_get_string (state))) ;

        if (state == MM_MODEM_STATE_FAILED)
            output_item_new_take_single (MMC_F_STATUS_FAILED_REASON,
                                         g_strdup_printf (KRED "%s" KNRM, mm_modem_state_failed_reason_get_string (reason)));
        return;
    }

    output_item_new_take_single (MMC_F_STATUS_STATE, g_strdup (mm_modem_state_get_string (state)));
    output_item_new_take_single (MMC_F_STATUS_FAILED_REASON,
                                 (state == MM_MODEM_STATE_FAILED) ?
                                 g_strdup (mm_modem_state_failed_reason_get_string (reason)) :
                                 NULL);
}

/******************************************************************************/
/* (Custom) SIM slots output */

void
mmcli_output_sim_slots (gchar **sim_slot_paths,
                        guint   primary_sim_slot)
{
    guint i;

    if (selected_type != MMC_OUTPUT_TYPE_HUMAN || !sim_slot_paths) {
        mmcli_output_string_take       (MMC_F_SIM_PRIMARY_SLOT, primary_sim_slot ? g_strdup_printf ("%u", primary_sim_slot) : NULL);
        mmcli_output_string_array_take (MMC_F_SIM_SLOT_PATHS, sim_slot_paths ? sim_slot_paths : NULL, TRUE);
        return;
    }

    /* Include SIM slot number in each item */
    for (i = 0; sim_slot_paths[i]; i++) {
        gchar *aux;
        guint  slot_number = i + 1;

        aux = g_strdup_printf ("slot %u: %s%s",
                               slot_number,
                               g_str_equal (sim_slot_paths[i], "/") ? "none" : sim_slot_paths[i],
                               (primary_sim_slot == slot_number) ? " (active)" : "");
        g_free (sim_slot_paths[i]);
        sim_slot_paths[i] = aux;
    }

    mmcli_output_string_array_take (MMC_F_SIM_SLOT_PATHS, sim_slot_paths, TRUE);
}

/******************************************************************************/
/* (Custom) Network scan output */

static gchar *json_strescape (const gchar *str);

static gchar *
build_network_info (MMModem3gppNetwork *network)
{
    const gchar *operator_code;
    const gchar *operator_name;
    gchar       *access_technologies;
    const gchar *availability;
    gchar       *out;

    operator_code = mm_modem_3gpp_network_get_operator_code (network);
    operator_name = mm_modem_3gpp_network_get_operator_long (network);
    if (!operator_name)
        operator_name = mm_modem_3gpp_network_get_operator_short (network);
    access_technologies = mm_modem_access_technology_build_string_from_mask (mm_modem_3gpp_network_get_access_technology (network));
    availability = mm_modem_3gpp_network_availability_get_string (mm_modem_3gpp_network_get_availability (network));

    if (selected_type == MMC_OUTPUT_TYPE_HUMAN) {
        out = g_strdup_printf ("%s - %s (%s, %s)",
                               operator_code ? operator_code : "code n/a",
                               operator_name ? operator_name : "name n/a",
                               access_technologies,
                               availability);
    } else if (selected_type == MMC_OUTPUT_TYPE_JSON) {
        GString *str;
        gchar *escape;

        str = g_string_new (NULL);

        g_string_append_printf (str, "{");

        escape = json_strescape (operator_code);
        g_string_append_printf (str, "\"operator-code\":\"%s\"", escape ? escape: "--");
        g_free (escape);
        g_string_append_printf (str, ",");

        escape = json_strescape (operator_name);
        g_string_append_printf (str, "\"operator-name\":\"%s\"", escape ? escape: "--");
        g_free (escape);
        g_string_append_printf (str, ",");

        escape = json_strescape (access_technologies);
        g_string_append_printf (str, "\"access-technologies\":\"%s\"", escape);
        g_free (escape);
        g_string_append_printf (str, ",");

        escape = json_strescape (availability);
        g_string_append_printf (str, "\"availability\":\"%s\"", escape);
        g_free (escape);

        g_string_append_printf (str, "}");

        out = g_string_free (str, FALSE);
    } else {
        out = g_strdup_printf ("operator-code: %s, operator-name: %s, access-technologies: %s, availability: %s",
                               operator_code ? operator_code : "--",
                               operator_name ? operator_name : "--",
                               access_technologies,
                               availability);
    }
    g_free (access_technologies);

    return out;
}

void
mmcli_output_scan_networks (GList *network_list)
{
    gchar **networks = NULL;

    if (network_list) {
        GPtrArray *aux;
        GList     *l;

        aux = g_ptr_array_new ();
        for (l = network_list; l; l = g_list_next (l))
            g_ptr_array_add (aux, build_network_info ((MMModem3gppNetwork *)(l->data)));
        g_ptr_array_add (aux, NULL);
        networks = (gchar **) g_ptr_array_free (aux, FALSE);
    }

    /* When printing human result, we want to show some result even if no networks
     * are found, so we force a explicit string result. */
    if (selected_type == MMC_OUTPUT_TYPE_HUMAN && !networks)
        output_item_new_take_single (MMC_F_3GPP_SCAN_NETWORKS, g_strdup ("n/a"));
    else
        output_item_new_take_multiple (MMC_F_3GPP_SCAN_NETWORKS, networks, TRUE, TRUE);
}

/******************************************************************************/
/* (Custom) Firmware list output */

static void
build_firmware_info_human (GPtrArray            *array,
                           MMFirmwareProperties *props,
                           gboolean              selected)
{
    g_ptr_array_add (array, g_strdup (mm_firmware_properties_get_unique_id (props)));
    g_ptr_array_add (array, g_strdup_printf ("    current: %s", selected ? "yes" : "no"));

    if (mm_firmware_properties_get_image_type (props) == MM_FIRMWARE_IMAGE_TYPE_GOBI) {
        const gchar *aux;

        if ((aux = mm_firmware_properties_get_gobi_pri_version (props)) != NULL)
            g_ptr_array_add (array, g_strdup_printf ("    gobi pri version: %s", aux));
        if ((aux = mm_firmware_properties_get_gobi_pri_info (props)) != NULL)
            g_ptr_array_add (array, g_strdup_printf ("    gobi pri info: %s", aux));
        if ((aux = mm_firmware_properties_get_gobi_boot_version (props)) != NULL)
            g_ptr_array_add (array, g_strdup_printf ("    gobi boot version: %s", aux));
        if ((aux = mm_firmware_properties_get_gobi_pri_unique_id (props)) != NULL)
            g_ptr_array_add (array, g_strdup_printf ("    gobi pri unique id: %s", aux));
        if ((aux = mm_firmware_properties_get_gobi_modem_unique_id (props)) != NULL)
            g_ptr_array_add (array, g_strdup_printf ("    gobi modem unique id: %s", aux));
    }
}

static void
build_firmware_info_keyvalue (GPtrArray            *array,
                              MMFirmwareProperties *props,
                              gboolean              selected)
{
    GString *str;

    str = g_string_new ("");
    g_string_append_printf (str, "unique-id: %s", mm_firmware_properties_get_unique_id (props));
    g_string_append_printf (str, ", current: %s",   selected ? "yes" : "no");

    if (mm_firmware_properties_get_image_type (props) == MM_FIRMWARE_IMAGE_TYPE_GOBI) {
        const gchar *aux;

        if ((aux = mm_firmware_properties_get_gobi_pri_version (props)) != NULL)
            g_string_append_printf (str, ", gobi-pri-version: %s", aux);
        if ((aux = mm_firmware_properties_get_gobi_pri_info (props)) != NULL)
            g_string_append_printf (str, ", gobi-pri-info: %s", aux);
        if ((aux = mm_firmware_properties_get_gobi_boot_version (props)) != NULL)
            g_string_append_printf (str, ", gobi-boot-version: %s", aux);
        if ((aux = mm_firmware_properties_get_gobi_pri_unique_id (props)) != NULL)
            g_string_append_printf (str, ", gobi-pri-unique id: %s", aux);
        if ((aux = mm_firmware_properties_get_gobi_modem_unique_id (props)) != NULL)
            g_string_append_printf (str, ", gobi-modem-unique id: %s", aux);
    }

    g_ptr_array_add (array, g_string_free (str, FALSE));
}

void
mmcli_output_firmware_list (GList                *firmware_list,
                            MMFirmwareProperties *selected)
{
    gchar **firmwares = NULL;

    if (firmware_list) {
        GPtrArray *aux;
        GList     *l;

        aux = g_ptr_array_new ();
        for (l = firmware_list; l; l = g_list_next (l)) {
            MMFirmwareProperties *props = (MMFirmwareProperties *)(l->data);
            gboolean              current_selected;

            current_selected = (selected &&
                                g_str_equal (mm_firmware_properties_get_unique_id (props),
                                             mm_firmware_properties_get_unique_id (selected)));

            if (selected_type == MMC_OUTPUT_TYPE_HUMAN)
                build_firmware_info_human (aux, props, current_selected);
            else
                build_firmware_info_keyvalue (aux, props, current_selected);
        }
        g_ptr_array_add (aux, NULL);
        firmwares = (gchar **) g_ptr_array_free (aux, FALSE);
    }

    /* When printing human result, we want to show some result even if no firmwares
     * are found, so we force a explicit string result. */
    if (selected_type == MMC_OUTPUT_TYPE_HUMAN && !firmwares)
        output_item_new_take_single (MMC_F_FIRMWARE_LIST, g_strdup ("n/a"));
    else
        output_item_new_take_multiple (MMC_F_FIRMWARE_LIST, firmwares, TRUE, FALSE);
}

/******************************************************************************/
/* (Custom) PCO list output */

void
mmcli_output_pco_list (GList *pco_list)
{
    GPtrArray *aux;
    GList     *l;

    if (!pco_list) {
        output_item_new_take_single (MMC_F_3GPP_PCO, NULL);
        return;
    }

    aux = g_ptr_array_new ();
    for (l = pco_list; l; l = g_list_next (l)) {
        MMPco        *pco;
        gchar        *pco_data_hex;
        const guint8 *pco_data;
        gsize         pco_data_size;

        pco = MM_PCO (l->data);
        pco_data = mm_pco_get_data (pco, &pco_data_size);
        pco_data_hex = (pco_data ? mm_utils_bin2hexstr (pco_data, pco_data_size) : NULL);

        if (selected_type == MMC_OUTPUT_TYPE_HUMAN)
            g_ptr_array_add (aux, g_strdup_printf ("%u: (%s) '%s'\n",
                                                   mm_pco_get_session_id (pco),
                                                   mm_pco_is_complete (pco) ? "complete" : "partial",
                                                   pco_data_hex ? pco_data_hex : ""));
        else
            g_ptr_array_add (aux, g_strdup_printf ("session-id: %u, complete: %s, data: %s\n",
                                                   mm_pco_get_session_id (pco),
                                                   mm_pco_is_complete (pco) ? "yes" : "no",
                                                   pco_data_hex ? pco_data_hex : ""));
        g_free (pco_data_hex);
    }
    g_ptr_array_add (aux, NULL);

    output_item_new_take_multiple (MMC_F_3GPP_PCO, (gchar **) g_ptr_array_free (aux, FALSE), TRUE, FALSE);
}

/******************************************************************************/
/* (Custom) Preferred networks output */

void
mmcli_output_preferred_networks (GList *preferred_nets_list)
{
    if (preferred_nets_list) {
        GPtrArray *aux;

        aux = g_ptr_array_new ();
        for (;preferred_nets_list; preferred_nets_list = g_list_next (preferred_nets_list)) {
            const MMSimPreferredNetwork *preferred_net;
            gchar       *access_technologies;
            gchar       *out;
            const gchar *operator_code;

            preferred_net = (const MMSimPreferredNetwork *)preferred_nets_list->data;
            operator_code = mm_sim_preferred_network_get_operator_code (preferred_net);
            access_technologies = mm_modem_access_technology_build_string_from_mask (mm_sim_preferred_network_get_access_technology (preferred_net));

            if (selected_type == MMC_OUTPUT_TYPE_HUMAN)
                out = g_strdup_printf ("%s (%s)",
                                       operator_code,
                                       access_technologies);
            else
                out = g_strdup_printf ("operator-code: %s, access-technologies: %s",
                                       operator_code,
                                       access_technologies);

            g_ptr_array_add (aux, out);
            g_free (access_technologies);
        }
        g_ptr_array_add (aux, NULL);

        mmcli_output_string_array_take (MMC_F_SIM_PROPERTIES_PREFERRED_NETWORKS, (gchar **) g_ptr_array_free (aux, FALSE), TRUE);
    }
}

/******************************************************************************/
/* (Custom) Profile list output */

static void
build_profile_human (GPtrArray     *array,
                     MM3gppProfile *profile)
{
    const gchar                  *aux;
    MMBearerAllowedAuth           allowed_auth;
    MMBearerIpFamily              ip_type;
    MMBearerApnType               apn_type;
    MMBearerAccessTypePreference  access_type_preference;
    MMBearerRoamingAllowance      roaming_allowance;
    MMBearerProfileSource         profile_source;

    g_ptr_array_add (array, g_strdup_printf ("profile-id: %u", mm_3gpp_profile_get_profile_id (profile)));
    g_ptr_array_add (array, g_strdup_printf ("  profile enabled: %s",
                                             mm_3gpp_profile_get_enabled (profile) ? "yes" : "no"));

    if ((aux = mm_3gpp_profile_get_profile_name (profile)) != NULL)
        g_ptr_array_add (array, g_strdup_printf ("  profile name: %s", aux));

    if ((aux = mm_3gpp_profile_get_apn (profile)) != NULL)
        g_ptr_array_add (array, g_strdup_printf ("  apn: %s", aux));

    allowed_auth = mm_3gpp_profile_get_allowed_auth (profile);
    if (allowed_auth != MM_BEARER_ALLOWED_AUTH_NONE) {
        g_autofree gchar *allowed_auth_str = NULL;

        allowed_auth_str = mm_bearer_allowed_auth_build_string_from_mask (allowed_auth);
        g_ptr_array_add (array, g_strdup_printf ("  allowed auth: %s", allowed_auth_str));
    }

    if ((aux = mm_3gpp_profile_get_user (profile)) != NULL)
        g_ptr_array_add (array, g_strdup_printf ("  user: %s", aux));

    if ((aux = mm_3gpp_profile_get_password (profile)) != NULL)
        g_ptr_array_add (array, g_strdup_printf ("  password: %s", aux));

    ip_type = mm_3gpp_profile_get_ip_type (profile);
    if (ip_type != MM_BEARER_IP_FAMILY_NONE) {
        g_autofree gchar *ip_type_str = NULL;

        ip_type_str = mm_bearer_ip_family_build_string_from_mask (ip_type);
        g_ptr_array_add (array, g_strdup_printf ("  ip type: %s", ip_type_str));
    }

    apn_type = mm_3gpp_profile_get_apn_type (profile);
    if (apn_type != MM_BEARER_APN_TYPE_NONE) {
        g_autofree gchar *apn_type_str = NULL;

        apn_type_str = mm_bearer_apn_type_build_string_from_mask (apn_type);
        g_ptr_array_add (array, g_strdup_printf ("  apn type: %s", apn_type_str));
    }

    access_type_preference = mm_3gpp_profile_get_access_type_preference (profile);
    if (access_type_preference != MM_BEARER_ACCESS_TYPE_PREFERENCE_NONE) {
        aux = mm_bearer_access_type_preference_get_string (access_type_preference);
        g_ptr_array_add (array, g_strdup_printf ("  access type preference: %s", aux));
    }

    roaming_allowance = mm_3gpp_profile_get_roaming_allowance (profile);
    if (roaming_allowance != MM_BEARER_ROAMING_ALLOWANCE_NONE) {
        g_autofree gchar *roaming_allowance_str = NULL;

        roaming_allowance_str = mm_bearer_roaming_allowance_build_string_from_mask (roaming_allowance);
        g_ptr_array_add (array, g_strdup_printf ("  roaming allowance: %s", roaming_allowance_str));
    }

    profile_source = mm_3gpp_profile_get_profile_source (profile);
    if (profile_source != MM_BEARER_PROFILE_SOURCE_UNKNOWN) {
        aux = mm_bearer_profile_source_get_string (profile_source);
        g_ptr_array_add (array, g_strdup_printf ("  profile source: %s", aux));
    }
}

static void
build_profile_keyvalue (GPtrArray     *array,
                        MM3gppProfile *profile)
{
    GString                      *str;
    const gchar                  *aux;
    MMBearerAllowedAuth           allowed_auth;
    MMBearerIpFamily              ip_type;
    MMBearerApnType               apn_type;
    MMBearerAccessTypePreference  access_type_preference;
    MMBearerRoamingAllowance      roaming_allowance;

    str = g_string_new ("");
    g_string_append_printf (str, "profile-id: %u", mm_3gpp_profile_get_profile_id (profile));

    if ((aux = mm_3gpp_profile_get_profile_name (profile)) != NULL)
        g_string_append_printf (str, ", profile-name: %s", aux);

    if ((aux = mm_3gpp_profile_get_apn (profile)) != NULL)
        g_string_append_printf (str, ", apn: %s", aux);

    allowed_auth = mm_3gpp_profile_get_allowed_auth (profile);
    if (allowed_auth != MM_BEARER_ALLOWED_AUTH_NONE) {
        g_autofree gchar *allowed_auth_str = NULL;

        allowed_auth_str = mm_bearer_allowed_auth_build_string_from_mask (allowed_auth);
        g_string_append_printf (str, ", allowed-auth: %s", allowed_auth_str);
    }

    if ((aux = mm_3gpp_profile_get_user (profile)) != NULL)
        g_string_append_printf (str, ", user: %s", aux);

    if ((aux = mm_3gpp_profile_get_password (profile)) != NULL)
        g_string_append_printf (str, ", password: %s", aux);

    ip_type = mm_3gpp_profile_get_ip_type (profile);
    if (ip_type != MM_BEARER_IP_FAMILY_NONE) {
        g_autofree gchar *ip_type_str = NULL;

        ip_type_str = mm_bearer_ip_family_build_string_from_mask (ip_type);
        g_string_append_printf (str, ", ip-type: %s", ip_type_str);
    }

    apn_type = mm_3gpp_profile_get_apn_type (profile);
    if (apn_type != MM_BEARER_APN_TYPE_NONE) {
        g_autofree gchar *apn_type_str = NULL;

        apn_type_str = mm_bearer_apn_type_build_string_from_mask (apn_type);
        g_string_append_printf (str, ", apn-type: %s", apn_type_str);
    }

    access_type_preference = mm_3gpp_profile_get_access_type_preference (profile);
    if (access_type_preference != MM_BEARER_ACCESS_TYPE_PREFERENCE_NONE) {
        aux = mm_bearer_access_type_preference_get_string (access_type_preference);
        g_string_append_printf (str, ", access-type-preference: %s", aux);
    }

    roaming_allowance = mm_3gpp_profile_get_roaming_allowance (profile);
    if (roaming_allowance != MM_BEARER_ROAMING_ALLOWANCE_NONE) {
        g_autofree gchar *roaming_allowance_str = NULL;

        roaming_allowance_str = mm_bearer_roaming_allowance_build_string_from_mask (roaming_allowance);
        g_string_append_printf (str, ", roaming-allowance: %s", roaming_allowance_str);
    }

    g_ptr_array_add (array, g_string_free (str, FALSE));
}

static void
output_profile_list (MmcF   field,
                     GList *profile_list)
{
    gchar **profiles = NULL;

    if (profile_list) {
        GPtrArray *aux;
        GList     *l;

        aux = g_ptr_array_new ();
        for (l = profile_list; l; l = g_list_next (l)) {
            MM3gppProfile *profile = (MM3gppProfile *)(l->data);

            if (selected_type == MMC_OUTPUT_TYPE_HUMAN)
                build_profile_human (aux, profile);
            else
                build_profile_keyvalue (aux, profile);
        }
        g_ptr_array_add (aux, NULL);
        profiles = (gchar **) g_ptr_array_free (aux, FALSE);
    }

    /* When printing human result, we want to show some result even if no profiles
     * are found, so we force a explicit string result. */
    if (selected_type == MMC_OUTPUT_TYPE_HUMAN && !profiles)
        output_item_new_take_single (field, g_strdup ("n/a"));
    else
        output_item_new_take_multiple (field, profiles, TRUE, FALSE);
}

void
mmcli_output_profile_list (GList *profile_list)
{
    output_profile_list (MMC_F_3GPP_PROFILE_MANAGER_LIST, profile_list);
}

void
mmcli_output_profile_set (MM3gppProfile *profile)
{
    GList *profile_list = NULL;

    profile_list = g_list_append (profile_list, profile);
    output_profile_list (MMC_F_3GPP_PROFILE_MANAGER_SET, profile_list);
    g_list_free (profile_list);
}

/******************************************************************************/
/* (Custom) Cell info output */

void
mmcli_output_cell_info (GList *cell_info_list)
{
    gchar **cell_infos = NULL;

    if (cell_info_list) {
        GPtrArray *aux;
        GList     *l;

        aux = g_ptr_array_new ();
        for (l = cell_info_list; l; l = g_list_next (l))
            g_ptr_array_add (aux, mm_cell_info_build_string (MM_CELL_INFO (l->data)));
        g_ptr_array_add (aux, NULL);
        cell_infos = (gchar **) g_ptr_array_free (aux, FALSE);
    }

    /* When printing human result, we want to show some result even if no networks
     * are found, so we force a explicit string result. */
    if (selected_type == MMC_OUTPUT_TYPE_HUMAN && !cell_infos)
        output_item_new_take_single (MMC_F_CELL_INFO, g_strdup ("n/a"));
    else
        output_item_new_take_multiple (MMC_F_CELL_INFO, cell_infos, TRUE, FALSE);
}

/******************************************************************************/
/* Human-friendly output */

#define HUMAN_MAX_VALUE_LENGTH 60

static gint
list_sort_human (const OutputItem *item_a,
                 const OutputItem *item_b)
{
    if (field_infos[item_a->field].section < field_infos[item_b->field].section)
        return -1;
    if (field_infos[item_a->field].section > field_infos[item_b->field].section)
        return 1;
    return item_a->field - item_b->field;
}

static void
dump_output_human (void)
{
    GList *l;
    MmcS   current_section = MMC_S_UNKNOWN;
    guint  longest_section_name = 0;
    guint  longest_field_name = 0;

    output_items = g_list_sort (output_items, (GCompareFunc) list_sort_human);

    /* First pass to process */
    for (l = output_items; l; l = g_list_next (l)) {
        OutputItem *item_l;
        guint       aux;
        gboolean    ignore = FALSE;

        item_l = (OutputItem *)(l->data);

        /* Post-process values */
        if (item_l->type == VALUE_TYPE_SINGLE) {
            OutputItemSingle *single = (OutputItemSingle *)item_l;

            if (!single->value)
                ignore = TRUE;
        } else if (item_l->type == VALUE_TYPE_MULTIPLE) {
            OutputItemMultiple *multiple = (OutputItemMultiple *)item_l;

            if (!multiple->values)
                ignore = TRUE;
        }

        /* Compute max lengths */
        if (!ignore) {
            aux = strlen (section_infos[field_infos[item_l->field].section].name);
            if (aux > longest_section_name)
                longest_section_name = aux;
            aux = strlen (field_infos[item_l->field].name);
            if (aux > longest_field_name)
                longest_field_name = aux;
        }
    }

    /* Second pass to print */
    for (l = output_items; l; l = g_list_next (l)) {
        OutputItem         *item_l;
        OutputItemSingle   *single = NULL;
        OutputItemMultiple *multiple = NULL;

        item_l = (OutputItem *)(l->data);
        if (item_l->type == VALUE_TYPE_SINGLE)
            single = (OutputItemSingle *)item_l;
        else if (item_l->type == VALUE_TYPE_MULTIPLE)
            multiple = (OutputItemMultiple *)item_l;
        else
            g_assert_not_reached ();

        /* Ignore items without a value set */
        if ((single && (!single->value || !single->value[0])) ||
            (multiple && (!multiple->values || !g_strv_length (multiple->values))))
            continue;

        /* Section change? */
        if (field_infos[item_l->field].section != current_section) {
            current_section = field_infos[item_l->field].section;
            g_print ("  %.*s\n",
                     longest_section_name + longest_field_name + 4,
                     "------------------------------------------------------------");
            g_print ("  %-*.*s | ",
                     longest_section_name,
                     longest_section_name,
                     section_infos[field_infos[item_l->field].section].name);
        } else
            g_print ("  %*.*s | ",
                     longest_section_name,
                     longest_section_name,
                     "");

        g_print ("%*.*s: ",
                 longest_field_name, longest_field_name, field_infos[item_l->field].name);

        if (single) {
            gchar **split_lines;
            guint   i;

            split_lines = g_strsplit (single->value, "\n", -1);
            for (i = 0; split_lines[i]; i++) {
                if (i != 0) {
                    g_print ("  %*.*s | %*.*s  ",
                             longest_section_name, longest_section_name, "",
                             longest_field_name, longest_field_name, "");
                }
                g_print ("%s\n", split_lines[i]);
            }
            g_strfreev (split_lines);
        } else if (multiple) {
            guint line_length = 0;
            guint n, i;

            n = multiple->values ? g_strv_length (multiple->values) : 0;
            for (i = 0; i < n; i++) {
                const gchar *value;
                guint        value_length;

                value = multiple->values[i];
                value_length = strlen (value) + ((i < (n - 1)) ? 2 : 0);
                if ((multiple->multiline && i != 0) || ((line_length + value_length) > HUMAN_MAX_VALUE_LENGTH)) {
                    line_length = 0;
                    g_print ("\n"
                             "  %*.*s | %*.*s  ",
                             longest_section_name, longest_section_name, "",
                             longest_field_name, longest_field_name, "");
                } else
                    line_length += value_length;
                g_print ("%s%s", value, (!multiple->multiline && i < (n - 1)) ? ", " : "");
            }
            g_print ("\n");
        }
    }
}

static void
dump_output_list_human (MmcF field)
{
    GList *l;
    guint  n;

    g_assert (field != MMC_F_UNKNOWN);

    /* First pass to process */
    for (n = 0, l = output_items; l; l = g_list_next (l), n++) {
        OutputItem         *item_l;
        OutputItemListitem *listitem;

        item_l = (OutputItem *)(l->data);
        g_assert (item_l->type == VALUE_TYPE_LISTITEM);
        listitem = (OutputItemListitem *)item_l;
        g_assert (listitem->value);

        /* All items must be of same type */
        g_assert_cmpint (item_l->field, ==, field);
    }

    /* Second pass to print */
    if (n == 0) {
        g_print ("No %s were found\n", field_infos[field].name);
        return;
    }
    for (l = output_items; l; l = g_list_next (l)) {
        OutputItemListitem *listitem;

        listitem = (OutputItemListitem *)(l->data);
        g_print ("%s%s %s\n", listitem->prefix, listitem->value, listitem->extra);
    }
}

/******************************************************************************/
/* Key-value output */

#define KEY_ARRAY_LENGTH_SUFFIX ".length"
#define KEY_ARRAY_VALUE_SUFFIX  ".value"

static gint
list_sort_keyvalue (const OutputItem *item_a,
                    const OutputItem *item_b)
{
    return item_a->field - item_b->field;
}

static void
dump_output_keyvalue (void)
{
    GList *l;
    guint  longest_field_key = 0;

    output_items = g_list_sort (output_items, (GCompareFunc) list_sort_keyvalue);

    /* First pass to process */
    for (l = output_items; l; l = g_list_next (l)) {
        OutputItem         *item_l;
        OutputItemMultiple *multiple = NULL;
        guint               key_length;

        item_l = (OutputItem *)(l->data);
        if (item_l->type == VALUE_TYPE_MULTIPLE)
            multiple = (OutputItemMultiple *)item_l;

        key_length = strlen (field_infos[item_l->field].key);

        /* when printing array contents, each item is given with an index,
         *   e.g.: something.value[1]
         * The max length of the field will need to consider the array length
         * in order to accommodate the length of the index.
         */
        if (multiple) {
            guint n;

            n = multiple->values ? g_strv_length (multiple->values) : 0;
            if (n > 0) {
                guint aux = n;

                key_length += ((strlen (KEY_ARRAY_VALUE_SUFFIX)) + 3);
                while ((aux /= 10) > 0)
                    key_length++;
            }
        }

        if (key_length > longest_field_key)
            longest_field_key = key_length;
    }

    /* Second pass to print */
    for (l = output_items; l; l = g_list_next (l)) {
        OutputItem         *item_l;
        OutputItemSingle   *single = NULL;
        OutputItemMultiple *multiple = NULL;

        item_l = (OutputItem *)(l->data);
        if (item_l->type == VALUE_TYPE_SINGLE)
            single = (OutputItemSingle *)item_l;
        else if (item_l->type == VALUE_TYPE_MULTIPLE)
            multiple = (OutputItemMultiple *)item_l;
        else
            g_assert_not_reached ();

        if (single) {
            gchar *escaped = NULL;

            if (single->value)
            escaped = g_strescape (single->value, NULL);
            g_print ("%-*.*s : %s\n",
                     longest_field_key, longest_field_key, field_infos[item_l->field].key,
                     escaped ? escaped : "--");
            g_free (escaped);
        } else if (multiple) {
            guint  n;

            n = multiple->values ? g_strv_length (multiple->values) : 0;
            if (n > 0) {
                guint  i;
                gchar *new_key;

                new_key = g_strdup_printf ("%s" KEY_ARRAY_LENGTH_SUFFIX, field_infos[item_l->field].key);
                g_print ("%-*.*s : %u\n", longest_field_key, longest_field_key, new_key, n);
                g_free (new_key);

                for (i = 0; i < n; i++) {
                    gchar *escaped = NULL;

                    /* Printed indices start at 1 */
                    new_key = g_strdup_printf ("%s" KEY_ARRAY_VALUE_SUFFIX "[%u]", field_infos[item_l->field].key, i + 1);
                    escaped = g_strescape (multiple->values[i], NULL);
                    g_print ("%-*.*s : %s\n", longest_field_key, longest_field_key, new_key, escaped);
                    g_free (escaped);
                    g_free (new_key);
                }
            } else
                g_print ("%-*.*s : --\n",
                         longest_field_key, longest_field_key, field_infos[item_l->field].key);
        }
    }
}

static void
dump_output_list_keyvalue (MmcF field)
{
    GList *l;
    guint  key_length;
    guint  n;
    gchar *new_key;

    g_assert (field != MMC_F_UNKNOWN);
    key_length = strlen (field_infos[field].key);

    /* First pass to process */
    for (n = 0, l = output_items; l; l = g_list_next (l), n++) {
        OutputItem         *item_l;
        OutputItemListitem *listitem;

        item_l = (OutputItem *)(l->data);
        g_assert (item_l->type == VALUE_TYPE_LISTITEM);
        listitem = (OutputItemListitem *)item_l;
        g_assert (listitem->value);

        /* All items must be of same type */
        g_assert_cmpint (item_l->field, ==, field);
    }

    if (n > 0) {
        guint aux = n;

        key_length += ((strlen (KEY_ARRAY_VALUE_SUFFIX)) + 3);
        while ((aux /= 10) > 0)
            key_length++;
    }

    new_key = g_strdup_printf ("%s" KEY_ARRAY_LENGTH_SUFFIX, field_infos[field].key);
    g_print ("%-*.*s : %u\n", key_length, key_length, new_key, n);
    g_free (new_key);

    /* Second pass to print */
    for (n = 0, l = output_items; l; l = g_list_next (l), n++) {
        OutputItemListitem *listitem;

        listitem = (OutputItemListitem *)(l->data);
        new_key = g_strdup_printf ("%s" KEY_ARRAY_VALUE_SUFFIX "[%u]", field_infos[field].key, n + 1);
        g_print ("%-*.*s : %s\n",
                 key_length, key_length, new_key,
                 listitem->value);
        g_free (new_key);
    }
}

/******************************************************************************/
/* JSON-friendly output */

static gchar *
json_strescape (const gchar *str)
{
    const gchar *p;
    const gchar *end;
    GString *output;
    gsize len;

    len = strlen (str);
    end = str + len;
    output = g_string_sized_new (len);

    for (p = str; p < end; p++) {
        if (*p == '\\' || *p == '"') {
            g_string_append_c (output, '\\');
            g_string_append_c (output, *p);
        } else if ((*p > 0 && *p < 0x1f) || *p == 0x7f) {
            switch (*p) {
                case '\b':
                    g_string_append (output, "\\b");
                    break;
                case '\f':
                    g_string_append (output, "\\f");
                    break;
                case '\n':
                    g_string_append (output, "\\n");
                    break;
                case '\r':
                    g_string_append (output, "\\r");
                    break;
                case '\t':
                    g_string_append (output, "\\t");
                    break;
                default:
                    g_string_append_printf (output, "\\u00%02x", (guint)*p);
                    break;
            }
        } else
            g_string_append_c (output, *p);
    }
    return g_string_free (output, FALSE);
}

static gint
list_sort_by_keys (const OutputItem *item_a,
                   const OutputItem *item_b)
{
    return g_strcmp0 (field_infos[item_a->field].key, field_infos[item_b->field].key);
}

static void
dump_output_json (void)
{
    GList   *l;
    MmcF     current_field = MMC_F_UNKNOWN;
    gchar  **current_path = NULL;
    guint    cur_dlen = 0;

    output_items = g_list_sort (output_items, (GCompareFunc) list_sort_by_keys);

    g_print ("{");
    for (l = output_items; l; l = g_list_next (l)) {
        OutputItem *item_l = (OutputItem *)(l->data);

        if (current_field != item_l->field) {
            guint   new_dlen;
            guint   iter = 0;
            gchar **new_path;

            new_path = g_strsplit (field_infos[item_l->field].key, ".", -1);
            new_dlen = g_strv_length (new_path) - 1;
            if (current_path) {
                guint min_dlen;

                min_dlen = MIN (cur_dlen, new_dlen);
                while (iter < min_dlen && g_strcmp0 (current_path[iter], new_path[iter]) == 0)
                    iter++;

                g_strfreev (current_path);

                if (iter < min_dlen || new_dlen < cur_dlen)
                    for (min_dlen = iter; min_dlen < cur_dlen; min_dlen++)
                        g_print ("}");

                g_print (",");
            }

            while (iter < new_dlen)
                g_print ("\"%s\":{", new_path[iter++]);

            cur_dlen = new_dlen;
            current_path = new_path;
            current_field = item_l->field;
        } else {
            g_print (",");
        }

        if (item_l->type == VALUE_TYPE_SINGLE) {
            OutputItemSingle *single = (OutputItemSingle *) item_l;
            gchar            *escaped = NULL;

            if (single->value)
                escaped = json_strescape (single->value);

            g_print ("\"%s\":\"%s\"", current_path[cur_dlen], escaped ? escaped : "--");
            g_free (escaped);
        } else if (item_l->type == VALUE_TYPE_MULTIPLE) {
            OutputItemMultiple *multiple = (OutputItemMultiple *) item_l;
            guint               i, n;

            n = multiple->values ? g_strv_length (multiple->values) : 0;

            g_print ("\"%s\":[", current_path[cur_dlen]);
            for (i = 0; i < n; i++) {
                if (multiple->is_object) {
                    g_print ("%s", multiple->values[i]);
                } else {
                    gchar *escaped;

                    escaped = json_strescape (multiple->values[i]);
                    g_print ("\"%s\"", escaped);
                    g_free (escaped);
                }
                if (i < n - 1)
                    g_print (",");
            }
            g_print ("]");
        } else
            g_assert_not_reached ();
    }

    while (cur_dlen--)
        g_print ("}");
    g_print ("}\n");

    g_strfreev (current_path);
}

static void
dump_output_list_json (MmcF field)
{
    GList *l;

    g_assert (field != MMC_F_UNKNOWN);

    g_print ("{\"%s\":[", field_infos[field].key);

    for (l = output_items; l; l = g_list_next (l)) {
        OutputItem         *item_l;
        OutputItemListitem *listitem;

        item_l = (OutputItem *)(l->data);
        g_assert (item_l->type == VALUE_TYPE_LISTITEM);
        listitem = (OutputItemListitem *)item_l;
        g_assert (listitem->value);

        /* All items must be of same type */
        g_assert_cmpint (item_l->field, ==, field);
        g_print ("\"%s\"", listitem->value);

        if (g_list_next (l))
            g_print (",");
    }

    g_print ("]}\n");
}

/******************************************************************************/
/* Dump output */

void
mmcli_output_dump (void)
{
    switch (selected_type) {
    case MMC_OUTPUT_TYPE_NONE:
        break;
    case MMC_OUTPUT_TYPE_HUMAN:
        dump_output_human ();
        break;
    case MMC_OUTPUT_TYPE_KEYVALUE:
        dump_output_keyvalue ();
        break;
    case MMC_OUTPUT_TYPE_JSON:
        dump_output_json ();
        break;
    default:
        g_assert_not_reached ();
    }

    g_list_free_full (output_items, (GDestroyNotify) output_item_free);
    output_items = NULL;

    fflush (stdout);
}

void
mmcli_output_list_dump (MmcF field)
{
    switch (selected_type) {
    case MMC_OUTPUT_TYPE_NONE:
        break;
    case MMC_OUTPUT_TYPE_HUMAN:
        dump_output_list_human (field);
        break;
    case MMC_OUTPUT_TYPE_KEYVALUE:
        dump_output_list_keyvalue (field);
        break;
    case MMC_OUTPUT_TYPE_JSON:
        dump_output_list_json (field);
        break;
    default:
        g_assert_not_reached ();
    }

    g_list_free_full (output_items, (GDestroyNotify) output_item_free);
    output_items = NULL;

    fflush (stdout);
}
