/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
 *
 * Copyright (C) 2012 Red Hat, Inc
 *
 * Licensed under the GNU General Public License Version 2
 *
 * 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, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

#include "config.h"

#include <string.h>

#include <net/if_arp.h>
#include <netinet/ether.h>

#include <nm-utils.h>

#include <glib/gi18n-lib.h>

#include "ce-page.h"


G_DEFINE_ABSTRACT_TYPE (CEPage, ce_page, G_TYPE_OBJECT)

enum {
        PROP_0,
        PROP_CONNECTION,
        PROP_INITIALIZED,
};

enum {
        CHANGED,
        INITIALIZED,
        LAST_SIGNAL
};

static guint signals[LAST_SIGNAL] = { 0 };

gboolean
ce_page_validate (CEPage *page, NMConnection *connection, GError **error)
{
        g_return_val_if_fail (CE_IS_PAGE (page), FALSE);
        g_return_val_if_fail (NM_IS_CONNECTION (connection), FALSE);

        if (CE_PAGE_GET_CLASS (page)->validate)
                return CE_PAGE_GET_CLASS (page)->validate (page, connection, error);

        return TRUE;
}

static void
dispose (GObject *object)
{
        CEPage *self = CE_PAGE (object);

        g_clear_object (&self->page);
        g_clear_object (&self->builder);
        g_clear_object (&self->connection);

        G_OBJECT_CLASS (ce_page_parent_class)->dispose (object);
}

static void
finalize (GObject *object)
{
        CEPage *self = CE_PAGE (object);

        g_free (self->title);
        if (self->cancellable) {
                g_cancellable_cancel (self->cancellable);
                g_object_unref (self->cancellable);
        }

        G_OBJECT_CLASS (ce_page_parent_class)->finalize (object);
}

GtkWidget *
ce_page_get_page (CEPage *self)
{
        g_return_val_if_fail (CE_IS_PAGE (self), NULL);

        return self->page;
}

const char *
ce_page_get_title (CEPage *self)
{
        g_return_val_if_fail (CE_IS_PAGE (self), NULL);

        return self->title;
}

gboolean
ce_page_get_initialized (CEPage *self)
{
        g_return_val_if_fail (CE_IS_PAGE (self), FALSE);

        return self->initialized;
}

void
ce_page_changed (CEPage *self)
{
        g_return_if_fail (CE_IS_PAGE (self));

        g_signal_emit (self, signals[CHANGED], 0);
}

static void
get_property (GObject    *object,
              guint       prop_id,
              GValue     *value,
              GParamSpec *pspec)
{
        CEPage *self = CE_PAGE (object);

        switch (prop_id) {
        case PROP_CONNECTION:
                g_value_set_object (value, self->connection);
                break;
        case PROP_INITIALIZED:
                g_value_set_boolean (value, self->initialized);
                break;
        default:
                G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
                break;
        }
}

static void
set_property (GObject      *object,
              guint         prop_id,
              const GValue *value,
              GParamSpec   *pspec)
{
        CEPage *self = CE_PAGE (object);

        switch (prop_id) {
        case PROP_CONNECTION:
                if (self->connection)
                        g_object_unref (self->connection);
                self->connection = g_value_dup_object (value);
                break;
        default:
                G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
                break;
        }
}

static void
ce_page_init (CEPage *self)
{
        self->builder = gtk_builder_new ();
        gtk_builder_set_translation_domain (self->builder, GETTEXT_PACKAGE);
        self->cancellable = g_cancellable_new ();
}

static void
ce_page_class_init (CEPageClass *page_class)
{
        GObjectClass *object_class = G_OBJECT_CLASS (page_class);

        /* virtual methods */
        object_class->dispose      = dispose;
        object_class->finalize     = finalize;
        object_class->get_property = get_property;
        object_class->set_property = set_property;

        /* Properties */
        g_object_class_install_property
                (object_class, PROP_CONNECTION,
                 g_param_spec_object ("connection",
                                      "Connection",
                                      "Connection",
                                      NM_TYPE_CONNECTION,
                                      G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));

        g_object_class_install_property
                (object_class, PROP_INITIALIZED,
                 g_param_spec_boolean ("initialized",
                                       "Initialized",
                                       "Initialized",
                                       FALSE,
                                       G_PARAM_READABLE));

        signals[CHANGED] =
                g_signal_new ("changed",
                              G_OBJECT_CLASS_TYPE (object_class),
                              G_SIGNAL_RUN_FIRST,
                              G_STRUCT_OFFSET (CEPageClass, changed),
                              NULL, NULL,
                              g_cclosure_marshal_VOID__VOID,
                              G_TYPE_NONE, 0);

        signals[INITIALIZED] =
                g_signal_new ("initialized",
                              G_OBJECT_CLASS_TYPE (object_class),
                              G_SIGNAL_RUN_FIRST,
                              G_STRUCT_OFFSET (CEPageClass, initialized),
                              NULL, NULL,
                              g_cclosure_marshal_VOID__POINTER,
                              G_TYPE_NONE, 1, G_TYPE_POINTER);
}

CEPage *
ce_page_new (GType             type,
             NMConnection     *connection,
             NMClient         *client,
             NMRemoteSettings *settings,
             const gchar      *ui_resource,
             const gchar      *title)
{
        CEPage *page;
        GError *error = NULL;

        page = CE_PAGE (g_object_new (type,
                                      "connection", connection,
                                      NULL));
        page->title = g_strdup (title);
        page->client = client;
        page->settings= settings;

        if (ui_resource) {
                if (!gtk_builder_add_from_resource (page->builder, ui_resource, &error)) {
                        g_warning ("Couldn't load builder file: %s", error->message);
                        g_error_free (error);
                        g_object_unref (page);
                        return NULL;
                }
                page->page = GTK_WIDGET (gtk_builder_get_object (page->builder, "page"));
                if (!page->page) {
                        g_warning ("Couldn't load page widget from %s", ui_resource);
                        g_object_unref (page);
                        return NULL;
                }

                g_object_ref_sink (page->page);
        }

        return page;
}

static void
emit_initialized (CEPage *page,
                  GError *error)
{
        page->initialized = TRUE;
        g_signal_emit (page, signals[INITIALIZED], 0, error);
}

void
ce_page_complete_init (CEPage      *page,
                       const gchar *setting_name,
                       GHashTable  *secrets,
                       GError      *error)
{
        GHashTable *setting_hash;
        GError *update_error = NULL;

        if (error
            && !dbus_g_error_has_name (error, "org.freedesktop.NetworkManager.Settings.InvalidSetting")
            && !dbus_g_error_has_name (error, "org.freedesktop.NetworkManager.AgentManager.NoSecrets")) {
                emit_initialized (page, error);
                return;
        } else if (!setting_name || !secrets || !g_hash_table_size (secrets)) {
                /* Success, no secrets */
                emit_initialized (page, NULL);
                return;
        }

        setting_hash = g_hash_table_lookup (secrets, setting_name);
        if (!setting_hash) {
                /* Success, no secrets */
                emit_initialized (page, NULL);
                return;
        }

        if (nm_connection_update_secrets (page->connection,
                                          setting_name,
                                          secrets,
                                          &update_error)) {
                emit_initialized (page, NULL);
                return;
        }

        if (!update_error) {
                g_set_error_literal (&update_error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_UNKNOWN,
                                     "Failed to update connection secrets due to an unknown error.");
        }

        emit_initialized (page, update_error);
        g_clear_error (&update_error);
}

gchar **
ce_page_get_mac_list (NMClient    *client,
                      GType        device_type,
                      const gchar *mac_property)
{
        const GPtrArray *devices;
        GPtrArray *macs;
        int i;

        macs = g_ptr_array_new ();
        devices = nm_client_get_devices (client);
        for (i = 0; devices && (i < devices->len); i++) {
                NMDevice *dev = g_ptr_array_index (devices, i);
                const char *iface;
                char *mac, *item;

                if (!G_TYPE_CHECK_INSTANCE_TYPE (dev, device_type))
                        continue;

                g_object_get (G_OBJECT (dev), mac_property, &mac, NULL);
                iface = nm_device_get_iface (NM_DEVICE (dev));
                item = g_strdup_printf ("%s (%s)", mac, iface);
                g_free (mac);
                g_ptr_array_add (macs, item);
        }

        g_ptr_array_add (macs, NULL);
        return (char **)g_ptr_array_free (macs, FALSE);
}

void
ce_page_setup_mac_combo (GtkComboBoxText  *combo,
                         const gchar      *current_mac,
                         gchar           **mac_list)
{
        gchar **m, *active_mac = NULL;
        gint current_mac_len;
        GtkWidget *entry;

        if (current_mac)
                current_mac_len = strlen (current_mac);
        else
                current_mac_len = -1;

        for (m= mac_list; m && *m; m++) {
                gtk_combo_box_text_append_text (combo, *m);
                if (current_mac &&
                    g_ascii_strncasecmp (*m, current_mac, current_mac_len) == 0
                    && ((*m)[current_mac_len] == '\0' || (*m)[current_mac_len] == ' '))
                        active_mac = *m;
        }

        if (current_mac) {
                if (!active_mac) {
                        gtk_combo_box_text_prepend_text (combo, current_mac);
                }

                entry = gtk_bin_get_child (GTK_BIN (combo));
                if (entry)
                        gtk_entry_set_text (GTK_ENTRY (entry), active_mac ? active_mac : current_mac);
        }
}

void
ce_page_mac_to_entry (const GByteArray *mac,
                      gint              type,
                      GtkEntry         *entry)
{
        char *str_addr;

        g_return_if_fail (entry != NULL);
        g_return_if_fail (GTK_IS_ENTRY (entry));

        if (!mac || !mac->len)
                return;

        if (mac->len != nm_utils_hwaddr_len (type))
                return;

        str_addr = nm_utils_hwaddr_ntoa (mac->data, type);
        gtk_entry_set_text (entry, str_addr);
        g_free (str_addr);
}

static gboolean
utils_ether_addr_valid (const struct ether_addr *test_addr)
{
        guint8 invalid_addr1[ETH_ALEN] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
        guint8 invalid_addr2[ETH_ALEN] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
        guint8 invalid_addr3[ETH_ALEN] = {0x44, 0x44, 0x44, 0x44, 0x44, 0x44};
        guint8 invalid_addr4[ETH_ALEN] = {0x00, 0x30, 0xb4, 0x00, 0x00, 0x00}; /* prism54 dummy MAC */

        g_return_val_if_fail (test_addr != NULL, FALSE);

        /* Compare the AP address the card has with invalid ethernet MAC addresses. */
        if (!memcmp (test_addr->ether_addr_octet, &invalid_addr1, ETH_ALEN))
                return FALSE;

        if (!memcmp (test_addr->ether_addr_octet, &invalid_addr2, ETH_ALEN))
                return FALSE;

        if (!memcmp (test_addr->ether_addr_octet, &invalid_addr3, ETH_ALEN))
                return FALSE;
        if (!memcmp (test_addr->ether_addr_octet, &invalid_addr4, ETH_ALEN))
                return FALSE;

        if (test_addr->ether_addr_octet[0] & 1)  /* Multicast addresses */
                return FALSE;
        
        return TRUE;
}

GByteArray *
ce_page_entry_to_mac (GtkEntry *entry,
                      gint      type,
                      gboolean *invalid)
{
        const char *temp, *sp;
        char *buf = NULL;
        GByteArray *mac;

        g_return_val_if_fail (entry != NULL, NULL);
        g_return_val_if_fail (GTK_IS_ENTRY (entry), NULL);

        if (invalid)
                *invalid = FALSE;

        temp = gtk_entry_get_text (entry);
        if (!temp || !strlen (temp))
                return NULL;

        sp = strchr (temp, ' ');
        if (sp)
                temp = buf = g_strndup (temp, sp - temp);

        mac = nm_utils_hwaddr_atoba (temp, type);
        g_free (buf);
        if (!mac) {
                if (invalid)
                        *invalid = TRUE;
                return NULL;
        }

        if (type == ARPHRD_ETHER && !utils_ether_addr_valid ((struct ether_addr *)mac->data)) {
                g_byte_array_free (mac, TRUE);
                if (invalid)
                        *invalid = TRUE;
                return NULL;
        }

        return mac;
}

const gchar *
ce_page_get_security_setting (CEPage *page)
{
        return page->security_setting;
}

gint
ce_get_property_default (NMSetting *setting, const gchar *property_name)
{
        GParamSpec *spec;
        GValue value = { 0, };

        spec = g_object_class_find_property (G_OBJECT_GET_CLASS (setting), property_name);
        g_return_val_if_fail (spec != NULL, -1);

        g_value_init (&value, spec->value_type);
        g_param_value_set_default (spec, &value);

        if (G_VALUE_HOLDS_CHAR (&value))
                return (int) g_value_get_schar (&value);
        else if (G_VALUE_HOLDS_INT (&value))
                return g_value_get_int (&value);
        else if (G_VALUE_HOLDS_INT64 (&value))
                return (int) g_value_get_int64 (&value);
        else if (G_VALUE_HOLDS_LONG (&value))
                return (int) g_value_get_long (&value);
        else if (G_VALUE_HOLDS_UINT (&value))
                return (int) g_value_get_uint (&value);
        else if (G_VALUE_HOLDS_UINT64 (&value))
                return (int) g_value_get_uint64 (&value);
        else if (G_VALUE_HOLDS_ULONG (&value))
                return (int) g_value_get_ulong (&value);
        else if (G_VALUE_HOLDS_UCHAR (&value))
                return (int) g_value_get_uchar (&value);
        g_return_val_if_fail (FALSE, 0);
        return 0;
}

gint
ce_spin_output_with_default (GtkSpinButton *spin, gpointer user_data)
{
        gint defvalue = GPOINTER_TO_INT (user_data);
        gint val;
        gchar *buf = NULL;

        val = gtk_spin_button_get_value_as_int (spin);
        if (val == defvalue)
                buf = g_strdup (_("automatic"));
        else
                buf = g_strdup_printf ("%d", val);

        if (strcmp (buf, gtk_entry_get_text (GTK_ENTRY (spin))))
                gtk_entry_set_text (GTK_ENTRY (spin), buf);

        g_free (buf);
        return TRUE;
}

gchar *
ce_page_get_next_available_name (GSList *connections, const gchar *format)
{
        GSList *names = NULL, *l;
        gchar *cname = NULL;
        gint i = 0;

        for (l = connections; l; l = l->next) {
                const gchar *id;

                id = nm_connection_get_id (NM_CONNECTION (l->data));
                g_assert (id);
                names = g_slist_append (names, (gpointer) id);
        }

        /* Find the next available unique connection name */
        while (!cname && (i++ < 10000)) {
                gchar *temp;
                gboolean found = FALSE;

                temp = g_strdup_printf (format, i);
                for (l = names; l; l = l->next) {
                        if (!strcmp (l->data, temp)) {
                                found = TRUE;
                                break;
                        }
                }
                if (!found)
                        cname = temp;
                else
                        g_free (temp);
        }
        g_slist_free (names);

        return cname;
}
