/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
  *
  * Copyright (C) 2004-2005 James M. Cape <jcape@ignore-your.tv>.
  * Copyright (C) 2007-2008 William Jon McCann <mccann@jhu.edu>
  * Copyright (C) 2009 Red Hat, Inc.
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  */

#define _XOPEN_SOURCE

#include "config.h"

#include <float.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>

#include <glib.h>
#include <glib/gi18n.h>
#include <glib/gstdio.h>
#include <gio/gio.h>
#include <gtk/gtk.h>

#include <gio/gunixoutputstream.h>

#include "um-user.h"
#include "um-account-type.h"
#include "um-utils.h"


 #define UM_USER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), UM_TYPE_USER, UmUserClass))
 #define UM_IS_USER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), UM_TYPE_USER))
#define UM_USER_GET_CLASS(object) (G_TYPE_INSTANCE_GET_CLASS ((object), UM_TYPE_USER, UmUserClass))

#define MAX_FILE_SIZE     65536

typedef struct {
        uid_t           uid;
        gchar          *user_name;
        gchar          *real_name;
        gint            account_type;
        gint            password_mode;
        gchar          *password_hint;
        gchar          *email;
        gchar          *language;
        gchar          *location;
        guint64         login_frequency;
        gchar          *icon_file;
        gboolean        locked;
        gboolean        automatic_login;
        gboolean        system_account;
} UserProperties;

static void
user_properties_free (UserProperties *props)
{
        g_free (props->user_name);
        g_free (props->real_name);
        g_free (props->password_hint);
        g_free (props->email);
        g_free (props->language);
        g_free (props->location);
        g_free (props->icon_file);
        g_free (props);
}

static UserProperties *
user_properties_get (GDBusConnection *bus,
                     const gchar     *object_path)
{
        GVariant *result;
        GVariantIter *iter;
        gchar *key;
        GVariant *value;
        UserProperties *props;
        GError *error = NULL;

        result = g_dbus_connection_call_sync (bus,
                                              "org.freedesktop.Accounts",
                                              object_path,
                                              "org.freedesktop.DBus.Properties",
                                              "GetAll",
                                              g_variant_new ("(s)", "org.freedesktop.Accounts.User"),
                                              G_VARIANT_TYPE ("(a{sv})"),
                                              G_DBUS_CALL_FLAGS_NONE,
                                              -1,
                                              NULL,
                                              &error);
        if (!result) {
                g_debug ("Error calling GetAll() when retrieving properties for %s: %s", object_path, error->message);
                g_error_free (error);
                return NULL;
        }

        props = g_new0 (UserProperties, 1);
        g_variant_get (result, "(a{sv})", &iter);
        while (g_variant_iter_loop (iter, "{&sv}", &key, &value)) {
                if (strcmp (key, "Uid") == 0) {
                        g_variant_get (value, "t", &props->uid);
                }
                else if (strcmp (key, "UserName") == 0) {
                        g_variant_get (value, "s", &props->user_name);
                }
                else if (strcmp (key, "RealName") == 0) {
                        g_variant_get (value, "s", &props->real_name);
                }
                else if (strcmp (key, "AccountType") == 0) {
                        g_variant_get (value, "i", &props->account_type);
                }
                else if (strcmp (key, "Email") == 0) {
                        g_variant_get (value, "s", &props->email);
                }
                else if (strcmp (key, "Language") == 0) {
                        g_variant_get (value, "s", &props->language);
                }
                else if (strcmp (key, "Location") == 0) {
                        g_variant_get (value, "s", &props->location);
                }
                else if (strcmp (key, "LoginFrequency") == 0) {
                        g_variant_get (value, "t", &props->login_frequency);
                }
                else if (strcmp (key, "IconFile") == 0) {
                        g_variant_get (value, "s", &props->icon_file);
                }
                else if (strcmp (key, "Locked") == 0) {
                        g_variant_get (value, "b", &props->locked);
                }
                else if (strcmp (key, "AutomaticLogin") == 0) {
                        g_variant_get (value, "b", &props->automatic_login);
                }
                else if (strcmp (key, "SystemAccount") == 0) {
                        g_variant_get (value, "b", &props->system_account);
                }
                else if (strcmp (key, "PasswordMode") == 0) {
                        g_variant_get (value, "i", &props->password_mode);
                }
                else if (strcmp (key, "PasswordHint") == 0) {
                        g_variant_get (value, "s", &props->password_hint);
                }
                else if (strcmp (key, "HomeDirectory") == 0) {
                        /* ignore */
                }
                else if (strcmp (key, "Shell") == 0) {
                        /* ignore */
                }
                else {
                        g_debug ("unhandled property %s", key);
                }
        }
  
        g_variant_iter_free (iter);
        g_variant_unref (result);

        return props;
}


struct _UmUser {
        GObject         parent;

        GDBusConnection *bus;
        GDBusProxy      *proxy;
        gchar           *object_path;

        UserProperties  *props;

        gchar           *display_name;
};

typedef struct _UmUserClass
{
        GObjectClass parent_class;
} UmUserClass;

enum {
        CHANGED,
        LAST_SIGNAL
};

static guint signals[LAST_SIGNAL] = { 0 };

static void um_user_finalize (GObject *object);

G_DEFINE_TYPE (UmUser, um_user, G_TYPE_OBJECT)

static void
um_user_class_init (UmUserClass *class)
{
        GObjectClass *gobject_class;

        gobject_class = G_OBJECT_CLASS (class);

        gobject_class->finalize = um_user_finalize;

        signals[CHANGED] = g_signal_new ("changed",
                                         G_TYPE_FROM_CLASS (class),
                                         G_SIGNAL_RUN_LAST,
                                         0,
                                         NULL, NULL,
                                         g_cclosure_marshal_VOID__VOID,
                                         G_TYPE_NONE, 0);
}


static void
um_user_init (UmUser *user)
{
}

static void
um_user_finalize (GObject *object)
{
        UmUser *user;

        user = UM_USER (object);

        g_free (user->display_name);

        g_object_unref (user->bus);
        g_free (user->object_path);

        if (user->proxy != NULL)
                g_object_unref (user->proxy);

        if (user->props != NULL)
                user_properties_free (user->props);

        (*G_OBJECT_CLASS (um_user_parent_class)->finalize) (object);
}

uid_t
um_user_get_uid (UmUser *user)
{
        g_return_val_if_fail (UM_IS_USER (user), -1);

        return user->props->uid;
}

const gchar *
um_user_get_real_name (UmUser *user)
{
        g_return_val_if_fail (UM_IS_USER (user), NULL);

        return user->props->real_name;
}

const gchar *
um_user_get_display_name (UmUser *user)
{
        g_return_val_if_fail (UM_IS_USER (user), NULL);

        if (user->display_name)
                return user->display_name;
        if (user->props->real_name &&
            *user->props->real_name != '\0')
                return user->props->real_name;

        return user->props->user_name;
}

const gchar *
um_user_get_user_name (UmUser *user)
{
        g_return_val_if_fail (UM_IS_USER (user), NULL);

        return user->props->user_name;
}

gint
um_user_get_account_type (UmUser *user)
{
        g_return_val_if_fail (UM_IS_USER (user), UM_ACCOUNT_TYPE_STANDARD);

        return user->props->account_type;
}

gulong
um_user_get_login_frequency (UmUser *user)
{
        g_return_val_if_fail (UM_IS_USER (user), 0);

        return user->props->login_frequency;
}

gint
um_user_collate (UmUser *user1,
                  UmUser *user2)
{
        const char *str1;
        const char *str2;
        gulong      num1;
        gulong      num2;

        g_return_val_if_fail (UM_IS_USER (user1), 0);
        g_return_val_if_fail (UM_IS_USER (user2), 0);

        num1 = user1->props->login_frequency;
        num2 = user2->props->login_frequency;
        if (num1 > num2) {
                return -1;
        }

        if (num1 < num2) {
                return 1;
        }

        /* if login frequency is equal try names */
        if (user1->props->real_name != NULL) {
                str1 = user1->props->real_name;
        } else {
                str1 = user1->props->user_name;
        }

        if (user2->props->real_name != NULL) {
                str2 = user2->props->real_name;
        } else {
                str2 = user2->props->user_name;
        }

        if (str1 == NULL && str2 != NULL) {
                return -1;
        }

        if (str1 != NULL && str2 == NULL) {
                return 1;
        }

        if (str1 == NULL && str2 == NULL) {
                return 0;
        }

        return g_utf8_collate (str1, str2);
}

static gboolean
check_user_file (const char *filename,
                 gssize      max_file_size)
{
        struct stat fileinfo;

        if (max_file_size < 0) {
                max_file_size = G_MAXSIZE;
        }

        /* Exists/Readable? */
        if (stat (filename, &fileinfo) < 0) {
                g_debug ("File does not exist");
                return FALSE;
        }

        /* Is a regular file */
        if (G_UNLIKELY (!S_ISREG (fileinfo.st_mode))) {
                g_debug ("File is not a regular file");
                return FALSE;
        }

        /* Size is kosher? */
        if (G_UNLIKELY (fileinfo.st_size > max_file_size)) {
                g_debug ("File is too large");
                return FALSE;
        }

        return TRUE;
}

static cairo_surface_t *
surface_from_pixbuf (GdkPixbuf *pixbuf)
{
        cairo_surface_t *surface;
        cairo_t         *cr;

        surface = cairo_image_surface_create (gdk_pixbuf_get_has_alpha (pixbuf) ?
                                              CAIRO_FORMAT_ARGB32 : CAIRO_FORMAT_RGB24,
                                              gdk_pixbuf_get_width (pixbuf),
                                              gdk_pixbuf_get_height (pixbuf));
        cr = cairo_create (surface);
        gdk_cairo_set_source_pixbuf (cr, pixbuf, 0, 0);
        cairo_paint (cr);
        cairo_destroy (cr);

        return surface;
}

/**
 * go_cairo_convert_data_to_pixbuf:
 * @src: a pointer to pixel data in cairo format
 * @dst: a pointer to pixel data in pixbuf format
 * @width: image width
 * @height: image height
 * @rowstride: data rowstride
 *
 * Converts the pixel data stored in @src in CAIRO_FORMAT_ARGB32 cairo format
 * to GDK_COLORSPACE_RGB pixbuf format and move them
 * to @dst. If @src == @dst, pixel are converted in place.
 **/

static void
go_cairo_convert_data_to_pixbuf (unsigned char *dst,
                                 unsigned char const *src,
                                 int width,
                                 int height,
                                 int rowstride)
{
        int i,j;
        unsigned int t;
        unsigned char a, b, c;

        g_return_if_fail (dst != NULL);

#define MULT(d,c,a,t) G_STMT_START { t = (a)? c * 255 / a: 0; d = t;} G_STMT_END

        if (src == dst || src == NULL) {
                for (i = 0; i < height; i++) {
                        for (j = 0; j < width; j++) {
#if G_BYTE_ORDER == G_LITTLE_ENDIAN
                                MULT(a, dst[2], dst[3], t);
                                MULT(b, dst[1], dst[3], t);
                                MULT(c, dst[0], dst[3], t);
                                dst[0] = a;
                                dst[1] = b;
                                dst[2] = c;
#else
                                MULT(a, dst[1], dst[0], t);
                                MULT(b, dst[2], dst[0], t);
                                MULT(c, dst[3], dst[0], t);
                                dst[3] = dst[0];
                                dst[0] = a;
                                dst[1] = b;
                                dst[2] = c;
#endif
                                dst += 4;
                        }
                        dst += rowstride - width * 4;
                }
        } else {
                for (i = 0; i < height; i++) {
                        for (j = 0; j < width; j++) {
#if G_BYTE_ORDER == G_LITTLE_ENDIAN
                                MULT(dst[0], src[2], src[3], t);
                                MULT(dst[1], src[1], src[3], t);
                                MULT(dst[2], src[0], src[3], t);
                                dst[3] = src[3];
#else
                                MULT(dst[0], src[1], src[0], t);
                                MULT(dst[1], src[2], src[0], t);
                                MULT(dst[2], src[3], src[0], t);
                                dst[3] = src[0];
#endif
                                src += 4;
                                dst += 4;
                        }
                        src += rowstride - width * 4;
                        dst += rowstride - width * 4;
                }
        }
#undef MULT
}

static void
cairo_to_pixbuf (guint8    *src_data,
                 GdkPixbuf *dst_pixbuf)
{
        unsigned char *src;
        unsigned char *dst;
        guint          w;
        guint          h;
        guint          rowstride;

        w = gdk_pixbuf_get_width (dst_pixbuf);
        h = gdk_pixbuf_get_height (dst_pixbuf);
        rowstride = gdk_pixbuf_get_rowstride (dst_pixbuf);

        dst = gdk_pixbuf_get_pixels (dst_pixbuf);
        src = src_data;

        go_cairo_convert_data_to_pixbuf (dst, src, w, h, rowstride);
}

static GdkPixbuf *
frame_pixbuf (GdkPixbuf *source)
{
        GdkPixbuf       *dest;
        cairo_t         *cr;
        cairo_surface_t *surface;
        guint            w;
        guint            h;
        guint            rowstride;
        int              frame_width;
        double           radius;
        guint8          *data;

        frame_width = 2;

        w = gdk_pixbuf_get_width (source) + frame_width * 2;
        h = gdk_pixbuf_get_height (source) + frame_width * 2;
        radius = w / 10;

        dest = gdk_pixbuf_new (GDK_COLORSPACE_RGB,
                               TRUE,
                               8,
                               w,
                               h);
        rowstride = gdk_pixbuf_get_rowstride (dest);


        data = g_new0 (guint8, h * rowstride);

        surface = cairo_image_surface_create_for_data (data,
                                                       CAIRO_FORMAT_ARGB32,
                                                       w,
                                                       h,
                                                       rowstride);
        cr = cairo_create (surface);
        cairo_surface_destroy (surface);

        /* set up image */
        cairo_rectangle (cr, 0, 0, w, h);
        cairo_set_source_rgba (cr, 1.0, 1.0, 1.0, 0.0);
        cairo_fill (cr);

        rounded_rectangle (cr, 1.0, 0.5, 0.5, radius, w - 1, h - 1);
        cairo_set_source_rgba (cr, 0.5, 0.5, 0.5, 0.3);
        cairo_fill_preserve (cr);

        surface = surface_from_pixbuf (source);
        cairo_set_source_surface (cr, surface, frame_width, frame_width);
        cairo_fill (cr);
        cairo_surface_destroy (surface);

        cairo_to_pixbuf (data, dest);

        cairo_destroy (cr);
        g_free (data);

        return dest;
}

GdkPixbuf *
um_user_render_icon (UmUser   *user,
                     gboolean  with_frame,
                     gint      icon_size)
{
        GdkPixbuf    *pixbuf;
        GdkPixbuf    *framed;
        gboolean      res;
        GError       *error;

        g_return_val_if_fail (UM_IS_USER (user), NULL);
        g_return_val_if_fail (icon_size > 12, NULL);

        pixbuf = NULL;
        if (user->props->icon_file) {
                res = check_user_file (user->props->icon_file,
                                       MAX_FILE_SIZE);
                if (res) {
                        pixbuf = gdk_pixbuf_new_from_file_at_size (user->props->icon_file,
                                                                   icon_size,
                                                                   icon_size,
                                                                   NULL);
                }
                else {
                        pixbuf = NULL;
                }
        }

        if (pixbuf != NULL) {
                goto out;
        }

        error = NULL;
        pixbuf = gtk_icon_theme_load_icon (gtk_icon_theme_get_default (),

                                           "avatar-default",
                                           icon_size,
                                           GTK_ICON_LOOKUP_FORCE_SIZE,
                                           &error);
        if (error) {
                g_warning ("%s", error->message);
                g_error_free (error);
        }

 out:

        if (pixbuf != NULL && with_frame) {
                framed = frame_pixbuf (pixbuf);
                if (framed != NULL) {
                        g_object_unref (pixbuf);
                        pixbuf = framed;
                }
        }

        return pixbuf;
}

const gchar *
um_user_get_email (UmUser *user)
{
        g_return_val_if_fail (UM_IS_USER (user), NULL);

        return user->props->email;
}

const gchar *
um_user_get_language (UmUser *user)
{
        g_return_val_if_fail (UM_IS_USER (user), NULL);

        if (*user->props->language == '\0')
                return NULL;
        return user->props->language;
}

const gchar *
um_user_get_location (UmUser *user)
{
        g_return_val_if_fail (UM_IS_USER (user), NULL);

        return user->props->location;
}

gint
um_user_get_password_mode (UmUser *user)
{
        g_return_val_if_fail (UM_IS_USER (user), UM_PASSWORD_MODE_NONE);

        return user->props->password_mode;
}

const char *
um_user_get_password_hint (UmUser *user)
{
        g_return_val_if_fail (UM_IS_USER (user), NULL);

        return user->props->password_hint;
}

const char *
um_user_get_icon_file (UmUser *user)
{
        g_return_val_if_fail (UM_IS_USER (user), NULL);

        return user->props->icon_file;
}

gboolean
um_user_get_locked (UmUser *user)
{
        g_return_val_if_fail (UM_IS_USER (user), FALSE);

        return user->props->locked;
}
gboolean
um_user_get_automatic_login (UmUser *user)
{
        g_return_val_if_fail (UM_IS_USER (user), FALSE);

        return user->props->automatic_login;
}

gboolean
um_user_is_system_account (UmUser *user)
{
        g_return_val_if_fail (UM_IS_USER (user), FALSE);

        return user->props->system_account;
}

const gchar *
um_user_get_object_path (UmUser *user)
{
        g_return_val_if_fail (UM_IS_USER (user), NULL);

        return user->object_path;
}

static gboolean
update_info (UmUser *user)
{
        UserProperties *props;

        props = user_properties_get (user->bus, user->object_path);
        if (props != NULL) {
                if (user->props != NULL)
                        user_properties_free (user->props);
                user->props = props;
                return TRUE;
        }
        else {
                return FALSE;
        }
}

static void
user_signal_cb (GDBusProxy *proxy, gchar *sender_name, gchar *signal_name, GVariant *parameters, UmUser *user)
{
        if (strcmp (signal_name, "Changed") == 0) {
                if (update_info (user)) {
                        if (user->display_name != NULL) {
                                um_user_show_full_display_name (user);
                        }
                        g_signal_emit (user, signals[CHANGED], 0);
                }
        }
}

UmUser *
um_user_new_from_object_path (const gchar *object_path)
{
        UmUser *user;
        GError *error = NULL;

        user = (UmUser *)g_object_new (UM_TYPE_USER, NULL);
        user->object_path = g_strdup (object_path);

        user->bus = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, &error);
        if (user->bus == NULL) {
                g_warning ("Couldn't connect to system bus: %s", error->message);
                g_error_free (error);
                goto error;
        }

        user->proxy = g_dbus_proxy_new_sync (user->bus,
                                             G_DBUS_PROXY_FLAGS_NONE,
                                             NULL,
                                             "org.freedesktop.Accounts",
                                             user->object_path,
                                             "org.freedesktop.Accounts.User",
                                             NULL,
                                             &error);
        if (user->proxy == NULL) {
                g_warning ("Couldn't get user proxy: %s", error->message);
                g_error_free (error);
                goto error;     
        }
        g_dbus_proxy_set_default_timeout (user->proxy, INT_MAX);
        g_signal_connect (user->proxy, "g-signal", G_CALLBACK (user_signal_cb), user);

        if (!update_info (user))
                goto error;

        return user;

 error:
        g_object_unref (user);
        return NULL;
}

void
um_user_set_email (UmUser      *user,
                   const gchar *email)
{
        GVariant *result;
        GError *error = NULL;

        result = g_dbus_proxy_call_sync (user->proxy, "SetEmail", g_variant_new ("(s)", email), G_DBUS_CALL_FLAGS_NONE, -1, NULL, &error);
        if (!result) {
                g_warning ("SetEmail call failed: %s", error->message);
                g_error_free (error);
                return;
        }
        g_variant_unref (result);
}

void
um_user_set_language (UmUser      *user,
                      const gchar *language)
{
        GVariant *result;
        GError *error = NULL;

        result = g_dbus_proxy_call_sync (user->proxy, "SetLanguage", g_variant_new ("(s)", language), G_DBUS_CALL_FLAGS_NONE, -1, NULL, &error);
        if (!result) {
                g_warning ("SetLanguage call failed: %s", error->message);
                g_error_free (error);
                return;
        }
        g_variant_unref (result);
}

void
um_user_set_location (UmUser      *user,
                      const gchar *location)
{
        GVariant *result;
        GError *error = NULL;

        result = g_dbus_proxy_call_sync (user->proxy, "SetLocation", g_variant_new ("(s)", location), G_DBUS_CALL_FLAGS_NONE, -1, NULL, &error);
        if (!result) {
                g_warning ("SetLocation call failed: %s", error->message);
                g_error_free (error);
                return;
        }
        g_variant_unref (result);
}

void
um_user_set_user_name (UmUser      *user,
                       const gchar *user_name)
{
        GVariant *result;
        GError *error = NULL;

        result = g_dbus_proxy_call_sync (user->proxy, "SetUserName", g_variant_new ("(s)", user_name), G_DBUS_CALL_FLAGS_NONE, -1, NULL, &error);
        if (!result) {
                g_warning ("SetUserName call failed: %s", error->message);
                g_error_free (error);
                return;
        }
        g_variant_unref (result);
}

void
um_user_set_real_name (UmUser      *user,
                       const gchar *real_name)
{
        GVariant *result;
        GError *error = NULL;

        result = g_dbus_proxy_call_sync (user->proxy, "SetRealName", g_variant_new ("(s)", real_name), G_DBUS_CALL_FLAGS_NONE, -1, NULL, &error);
        if (!result) {
                g_warning ("SetRealName call failed: %s", error->message);
                g_error_free (error);
                return;
        }
        g_variant_unref (result);
}

void
um_user_set_icon_file (UmUser      *user,
                       const gchar *icon_file)
{
        GVariant *result;
        GError *error = NULL;

        result = g_dbus_proxy_call_sync (user->proxy, "SetIconFile", g_variant_new ("(s)", icon_file), G_DBUS_CALL_FLAGS_NONE, -1, NULL, &error);
        if (!result) {
                g_warning ("SetIconFile call failed: %s", error->message);
                g_error_free (error);
                return;
        }
        g_variant_unref (result);
}

void
um_user_set_icon_data (UmUser    *user,
                       GdkPixbuf *pixbuf)
{
        gchar *path;
        gint fd;
        GOutputStream *stream;
        GError *error;

        path = g_build_filename (g_get_tmp_dir (), "usericonXXXXXX", NULL);
        fd = g_mkstemp (path);

        if (fd == -1) {
                g_warning ("failed to create temporary file for image data");
                g_free (path);
                return;
        }

        stream = g_unix_output_stream_new (fd, TRUE);

        error = NULL;
        if (!gdk_pixbuf_save_to_stream (pixbuf, stream, "png", NULL, &error, NULL)) {
                g_warning ("failed to save image: %s", error->message);
                g_error_free (error);
                g_object_unref (stream);
                return;
        }

        g_object_unref (stream);

        um_user_set_icon_file (user, path);

        /* if we ever make the dbus call async, the g_remove call needs
         * to wait for its completion
         */
        g_remove (path);

        g_free (path);
}

void
um_user_set_account_type (UmUser *user,
                          gint    account_type)
{
        GVariant *result;  
        GError *error = NULL;

        result = g_dbus_proxy_call_sync (user->proxy, "SetAccountType", g_variant_new ("(i)", account_type), G_DBUS_CALL_FLAGS_NONE, -1, NULL, &error);
        if (!result) {
                g_warning ("SetAccountType call failed: %s", error->message);
                g_error_free (error);
                return;
        }
        g_variant_unref (result);  
}

static gchar
salt_char (GRand *rand)
{
        gchar salt[] = "ABCDEFGHIJKLMNOPQRSTUVXYZ"
                       "abcdefghijklmnopqrstuvxyz"
                       "./0123456789";

        return salt[g_rand_int_range (rand, 0, G_N_ELEMENTS (salt))];
}

static gchar *
make_crypted (const gchar *plain)
{
        GString *salt;
        gchar *result;
        GRand *rand;
        gint i;

        rand = g_rand_new ();
        salt = g_string_sized_new (21);

        /* SHA 256 */
        g_string_append (salt, "$6$");
        for (i = 0; i < 16; i++) {
                g_string_append_c (salt, salt_char (rand));
        }
        g_string_append_c (salt, '$');

        result = g_strdup (crypt (plain, salt->str));

        g_string_free (salt, TRUE);
        g_rand_free (rand);

        return result;
}

void
um_user_set_password (UmUser      *user,
                      gint         password_mode,
                      const gchar *password,
                      const gchar *hint)
{
        GError *error = NULL;
        gchar *crypted;

        if (password_mode == 0) {
                GVariant *result;

                crypted = make_crypted (password);
                result = g_dbus_proxy_call_sync (user->proxy, "SetPassword", g_variant_new ("(ss)", crypted, hint), G_DBUS_CALL_FLAGS_NONE, -1, NULL, &error);
                if (!result) {
                        g_warning ("SetPassword call failed: %s", error->message);
                        g_error_free (error);
                }
                else
                        g_variant_unref (result);
                memset (crypted, 0, strlen (crypted));
                g_free (crypted);
        }
        else if (password_mode == 3 || password_mode == 4) {
                GVariant *result;

                /* FIXME: this is a slightly odd side-effect:
                 * you disable the account, and autologin flips
                 * we should remove that once gdm knows to
                 * ignore autologin for disabled accounts
                 */
                if (password_mode == 3 &&
                    um_user_get_automatic_login (user)) {
                        um_user_set_automatic_login (user, FALSE);
                }
                result = g_dbus_proxy_call_sync (user->proxy, "SetLocked", g_variant_new ("(b)", password_mode == 3), G_DBUS_CALL_FLAGS_NONE, -1, NULL, &error);
                if (!result) {
                        g_warning ("SetLocked call failed: %s", error->message);
                        g_error_free (error);
                }
                else
                        g_variant_unref (result);
        }
        else {
                GVariant *result;

                result = g_dbus_proxy_call_sync (user->proxy, "SetPasswordMode", g_variant_new ("(i)", password_mode), G_DBUS_CALL_FLAGS_NONE, -1, NULL, &error);
                if (!result) {
                        g_warning ("SetPasswordMode call failed: %s", error->message);
                        g_error_free (error);
                }
                else
                        g_variant_unref (result);
        }
}

#ifdef HAVE_SYSTEMD

#include <systemd/sd-login.h>

gboolean
um_user_is_logged_in (UmUser *user)
{
  int n_sessions;

  n_sessions = sd_uid_get_sessions (um_user_get_uid (user), 0, NULL) > 0;

  return n_sessions > 0;
}

#else

gboolean
um_user_is_logged_in (UmUser *user)
{
        GVariant *result;
        GVariantIter *iter;
        gint n_sessions;
        GError *error = NULL;

        result = g_dbus_connection_call_sync (user->bus,
                                              "org.freedesktop.ConsoleKit",
                                              "/org/freedesktop/ConsoleKit/Manager",
                                              "org.freedesktop.ConsoleKit.Manager",
                                              "GetSessionsForUnixUser",
                                              g_variant_new ("(u)", um_user_get_uid (user)),
                                              G_VARIANT_TYPE ("(ao)"),
                                              G_DBUS_CALL_FLAGS_NONE,
                                              -1,
                                              NULL,
                                              &error);
        if (!result) {
                g_warning ("GetSessionsForUnixUser failed: %s", error->message);
                g_error_free (error);
                return FALSE;
        }
  
        g_variant_get (result, "(ao)", &iter);
        n_sessions = g_variant_iter_n_children (iter);
        g_variant_iter_free (iter);
        g_variant_unref (result);

        return n_sessions > 0;
}

#endif

void
um_user_set_automatic_login (UmUser   *user,
                             gboolean  enabled)
{
        GVariant *result;
        GError *error = NULL;

        result = g_dbus_proxy_call_sync (user->proxy, "SetAutomaticLogin", g_variant_new ("(b)", enabled), G_DBUS_CALL_FLAGS_NONE, -1, NULL, &error);
        if (!result) {
                g_warning ("SetAutomaticLogin call failed: %s", error->message);
                g_error_free (error);
                return;
        }
        g_variant_unref (result);
}

void
um_user_show_full_display_name (UmUser *user)
{
        char *uniq_name;

        g_return_if_fail (UM_IS_USER (user));

        if (user->props->real_name != NULL) {
                uniq_name = g_strdup_printf ("%s (%s)",
                                             user->props->real_name,
                                             user->props->user_name);
        } else {
                uniq_name = NULL;
        }

        if (uniq_name && g_strcmp0 (uniq_name, user->display_name) != 0) {
                g_free (user->display_name);
                user->display_name = uniq_name;
        }
        else {
                g_free (uniq_name);
        }
}

void
um_user_show_short_display_name (UmUser *user)
{
        g_return_if_fail (UM_IS_USER (user));

        if (user->display_name) {
                g_free (user->display_name);
                user->display_name = NULL;
        }
}

