#include "DmGreeterHelper.h"
#include "Common.h"
#include <lightdm.h>

#include <glib.h>
#include <glib/gi18n.h>
#include <glibmm.h>

#include <cairo-xlib.h>
#include <cairo-svg.h>
#include <gtk/gtk.h>
#include <gdk/gdkx.h>
#include <gdk-pixbuf/gdk-pixbuf.h>
#include <string.h>
#include <X11/Xatom.h>

#include <sys/types.h>
#include <pwd.h>

#include "config.h"

DmGreeterHelper* DmGreeterHelper::s_inst = NULL;

static void userListReader (gpointer data, gpointer user_data);

static void show_prompt_cb (LightDMGreeter *greeter, const gchar *text, LightDMPromptType type, gpointer user_data);
static void show_message_cb (LightDMGreeter *greeter, const gchar *text, LightDMMessageType type, gpointer user_data);
static void authentication_complete_cb (LightDMGreeter *greeter, gpointer user_data);
static void timed_autologin_cb (LightDMGreeter *greeter, gpointer user_data);

class DmGreeterHelperPrivate {
public:
    DmGreeterHelperPrivate(DmGreeterHelper* dmHelper);

    void loadUserList();
    bool checkShell(const gchar *usersh);
    bool checkHomeDir(const gchar *homedir);

    bool isValidSession (const char* session);

    cairo_surface_t* createRootSurface(GdkScreen* screen);

    void setRootPixmapId(GdkScreen* screen,
                       Display* display,
                       Pixmap xpixmap);

    void setSurfaceAsRoot(GdkScreen* screen,
                        cairo_surface_t* surface);

    void drawBackgroundToScreen();
    void startSession ();

    LightDMUserList * m_userList;
    LightDMGreeter* m_greeter;
    DmGreeterHelper* m_dmHelper;
    std::vector<Glib::ustring> m_userNameList;
    bool m_isCanceling;
    Glib::ustring m_inAuthName;
};

DmGreeterHelper* DmGreeterHelper::inst() {
    if (NULL == s_inst) {
        s_inst = new DmGreeterHelper();
    }
    return s_inst;
}

bool DmGreeterHelper::connectSyncDm() {
    return lightdm_greeter_connect_sync(m_priv->m_greeter, NULL);
}

int DmGreeterHelper::getUserCount() {
    return m_priv->m_userNameList.size();
}

std::vector<Glib::ustring> DmGreeterHelper::getUserNameList() {
    return m_priv->m_userNameList;
}

Glib::ustring DmGreeterHelper::getUserImagePath(const Glib::ustring& userName) {
    LightDMUser* userInfo = lightdm_user_list_get_user_by_name(m_priv->m_userList, userName.c_str());
    if (NULL != userInfo) {
        return lightdm_user_get_image(userInfo);
    }
    return "";
}

bool DmGreeterHelper::isUserLoggedIn(const Glib::ustring& userName) {
    LightDMUser* userInfo = lightdm_user_list_get_user_by_name(m_priv->m_userList, userName.c_str());
    if (NULL != userInfo) {
	return lightdm_user_get_logged_in(userInfo);
    }
    return false;
}

bool DmGreeterHelper::isInAuthentication() {
    return lightdm_greeter_get_in_authentication(m_priv->m_greeter);
}

bool DmGreeterHelper::isAuthenticated() {
    return lightdm_greeter_get_is_authenticated(m_priv->m_greeter);
}

void DmGreeterHelper::startAuthentication (const Glib::ustring& username) {
    m_priv->m_inAuthName = username;
#if HAVE_LIBLIGHTDMGOBJECT_1_19_2
    lightdm_greeter_authenticate (m_priv->m_greeter, username.c_str(), NULL);
#else
    lightdm_greeter_authenticate (m_priv->m_greeter, username.c_str());
#endif
}

void DmGreeterHelper::requestStartSession() {
    m_priv->startSession();
}

void DmGreeterHelper::respondPwd(const Glib::ustring& passwd) {
#if HAVE_LIBLIGHTDMGOBJECT_1_19_2
    lightdm_greeter_respond(m_priv->m_greeter, passwd.c_str(), NULL);
#else
    lightdm_greeter_respond(m_priv->m_greeter, passwd.c_str());
#endif
}

void DmGreeterHelper::cancelAuthentication() {
    if (lightdm_greeter_get_in_authentication (m_priv->m_greeter)) {
#if HAVE_LIBLIGHTDMGOBJECT_1_19_2
        lightdm_greeter_cancel_authentication (m_priv->m_greeter, NULL);
#else
        lightdm_greeter_cancel_authentication (m_priv->m_greeter);
#endif
    }
}

bool DmGreeterHelper::canSuspend() {
    return lightdm_get_can_suspend();
}

bool DmGreeterHelper::requestSuspend(GError **error) {
    return lightdm_suspend(error);
}

bool DmGreeterHelper::canReboot() {
    return lightdm_get_can_restart();
}

bool DmGreeterHelper::requestReboot(GError **error) {
    return lightdm_restart(error);
}

bool DmGreeterHelper::canShutdown() {
    return lightdm_get_can_shutdown();
}

bool DmGreeterHelper::requestShutdown(GError **error) {
    return lightdm_shutdown(error);
}

bool DmGreeterHelper::isLoginLocked() {
    return m_isLoginLocked;
}

void DmGreeterHelper::setLoginLock(bool isLocked) {
    m_isLoginLocked = isLocked;
}

sigc::signal<void> DmGreeterHelper::signalAuthFailed() {
    return m_authFailed;
}

sigc::signal<void> DmGreeterHelper::signalSessionFailed() {
    return m_sessionFailed;
}
sigc::signal<void, Glib::ustring, int> DmGreeterHelper::signalPrompt() {
    return m_signalPrompt;
}

sigc::signal<void, Glib::ustring, int> DmGreeterHelper::signalMessage() {
    return m_signalMessage;
}

sigc::signal<void> DmGreeterHelper::signalAuthComplete() {
    return m_signalAuthComplete;
}

sigc::signal<void> DmGreeterHelper::signalAutoLoginTimeout() {
    return m_signalAutoLoginTimeout;
}

DmGreeterHelper::DmGreeterHelper()
    : m_priv(new DmGreeterHelperPrivate(this))
    , m_isLoginLocked(false) {

}

static void userListReader (gpointer data, gpointer user_data) {
    ((DmGreeterHelperPrivate*)user_data)->m_userNameList.push_back(lightdm_user_get_name((LightDMUser*)data));
}

static void show_prompt_cb (LightDMGreeter *greeter, const gchar *text, LightDMPromptType type, gpointer user_data) {
    DmGreeterHelperPrivate* priv = (DmGreeterHelperPrivate*)user_data;
    priv->m_dmHelper->signalPrompt().emit(text, type);
}

static void show_message_cb (LightDMGreeter *greeter, const gchar *text, LightDMMessageType type, gpointer user_data) {
    DmGreeterHelperPrivate* priv = (DmGreeterHelperPrivate*)user_data;
    priv->m_dmHelper->signalMessage().emit(text, type);
}

static void authentication_complete_cb (LightDMGreeter *greeter, gpointer user_data) {
    DmGreeterHelperPrivate* priv = (DmGreeterHelperPrivate*)user_data;
    if (lightdm_greeter_get_is_authenticated(priv->m_greeter)) {
         priv->startSession();
    } else {
        priv->m_dmHelper->signalAuthFailed().emit();
    }
}

static void timed_autologin_cb (LightDMGreeter *greeter, gpointer user_data) {
    DmGreeterHelperPrivate* priv = (DmGreeterHelperPrivate*)user_data;

    if (!lightdm_greeter_get_lock_hint (priv->m_greeter)) {
        if (lightdm_greeter_get_is_authenticated (priv->m_greeter)) {
            if (lightdm_greeter_get_authentication_user (priv->m_greeter)) {
                priv->startSession();
            } else if (lightdm_greeter_get_autologin_guest_hint (priv->m_greeter)) {
                priv->startSession();
            } else if (lightdm_greeter_get_autologin_user_hint (priv->m_greeter)) {
                priv->m_dmHelper->startAuthentication (lightdm_greeter_get_autologin_user_hint (priv->m_greeter));
            }
        }
        else {
#if HAVE_LIBLIGHTDMGOBJECT_1_19_2
            lightdm_greeter_authenticate_autologin (priv->m_greeter, NULL);
#else
            lightdm_greeter_authenticate_autologin (priv->m_greeter);
#endif
        }
    }
}

DmGreeterHelperPrivate::DmGreeterHelperPrivate(DmGreeterHelper* dmHelper)
    : m_userList(lightdm_user_list_get_instance())
    , m_greeter(lightdm_greeter_new ())
    , m_dmHelper(dmHelper)
    , m_isCanceling(false) {
    loadUserList();

    g_signal_connect (m_greeter, "show-prompt", G_CALLBACK (show_prompt_cb), this);
    g_signal_connect (m_greeter, "show-message", G_CALLBACK (show_message_cb), this);
    g_signal_connect (m_greeter, "authentication-complete", G_CALLBACK (authentication_complete_cb), this);
    g_signal_connect (m_greeter, "autologin-timer-expired", G_CALLBACK (timed_autologin_cb), this);
}

void DmGreeterHelperPrivate::loadUserList() {
//    g_list_foreach(lightdm_user_list_get_users(m_userList), userListReader, this);

    setpwent();
    struct passwd* userInfo = getpwent();
    while (NULL != userInfo) {
        if (checkShell(userInfo->pw_shell) && checkHomeDir(userInfo->pw_dir)) {
            m_userNameList.push_back(Glib::ustring(userInfo->pw_name));
        }
        userInfo = getpwent();
    }
}

bool DmGreeterHelperPrivate::checkShell (const gchar *usersh)
{
    if (NULL == usersh || 0 == strcmp(usersh, "")) {
        return 0;
    }
    gint found = 0;
    gchar *csh;

    if (strcmp (usersh, "/usr/sbin/nologin") == 0 ||
        strcmp (usersh, "/bin/true") == 0 ||
        strcmp (usersh, "/bin/false") == 0) {
      return 0;
    }

    setusershell ();

    while ((csh = getusershell ()) != NULL)
        if (! strcmp (csh, usersh))
            found = 1;

    endusershell ();

    return (found);
}

bool DmGreeterHelperPrivate::checkHomeDir(const gchar *homedir)
{
    if (NULL == homedir || 0 == strcmp(homedir, "")) {
        return FALSE;
    }
    if (strncmp (homedir, "/home", strlen("/home")) == 0) {
       return TRUE;
    }

    return FALSE;
}

bool DmGreeterHelperPrivate::isValidSession (const char* session)
{
    if (NULL == session) {
        return false;
    }
    GList* sessionList = lightdm_get_sessions();
    for (; sessionList; sessionList = g_list_next (sessionList)) {
        if (NULL != sessionList && 0 == strcmp(session, lightdm_session_get_key ((LightDMSession*)(sessionList->data)))) {
            return true;
        }
    }
    return false;
}

cairo_surface_t* DmGreeterHelperPrivate::createRootSurface(GdkScreen* screen)
{
    gint number, width, height;
    Display *display;
    Pixmap pixmap;
    cairo_surface_t *surface;

    number = gdk_screen_get_number (screen);
    width = gdk_screen_get_width (screen);
    height = gdk_screen_get_height (screen);

    /* Open a new connection so with Retain Permanent so the pixmap remains when the greeter quits */
    gdk_flush ();
    display = XOpenDisplay (gdk_display_get_name (gdk_screen_get_display (screen)));
    if (!display)
    {
        return NULL;
    }

    XSetCloseDownMode (display, RetainPermanent);
    pixmap = XCreatePixmap (display, RootWindow (display, number), width, height, DefaultDepth (display, number));
    XCloseDisplay (display);

    /* Convert into a Cairo surface */
    surface = cairo_xlib_surface_create (GDK_SCREEN_XDISPLAY (screen),
                                         pixmap,
                                         GDK_VISUAL_XVISUAL (gdk_screen_get_system_visual (screen)),
                                         width, height);

    return surface;
}

void DmGreeterHelperPrivate::setRootPixmapId(GdkScreen* screen,
                   Display* display,
                   Pixmap xpixmap)
{

    Window xroot = RootWindow (display, gdk_screen_get_number (screen));
    char *atom_names[] = {"_XROOTPMAP_ID", "ESETROOT_PMAP_ID"};
    Atom atoms[G_N_ELEMENTS(atom_names)] = {0};

    Atom type;
    int format;
    unsigned long nitems, after;
    unsigned char *data_root, *data_esetroot;

    /* Get atoms for both properties in an array, only if they exist.
     * This method is to avoid multiple round-trips to Xserver
     */
    if (XInternAtoms (display, atom_names, G_N_ELEMENTS(atom_names), True, atoms) &&
        atoms[0] != None && atoms[1] != None)
    {

        XGetWindowProperty (display, xroot, atoms[0], 0L, 1L, False, AnyPropertyType,
                            &type, &format, &nitems, &after, &data_root);
        if (data_root && type == XA_PIXMAP && format == 32 && nitems == 1)
        {
            XGetWindowProperty (display, xroot, atoms[1], 0L, 1L, False, AnyPropertyType,
                                &type, &format, &nitems, &after, &data_esetroot);
            if (data_esetroot && type == XA_PIXMAP && format == 32 && nitems == 1)
            {
                Pixmap xrootpmap = *((Pixmap *) data_root);
                Pixmap esetrootpmap = *((Pixmap *) data_esetroot);
                XFree (data_root);
                XFree (data_esetroot);

                gdk_error_trap_push ();
                if (xrootpmap && xrootpmap == esetrootpmap) {
                    XKillClient (display, xrootpmap);
                }
                if (esetrootpmap && esetrootpmap != xrootpmap) {
                    XKillClient (display, esetrootpmap);
                }

                XSync (display, False);
                gdk_error_trap_pop_ignored ();
            }
        }
    }

    /* Get atoms for both properties in an array, create them if needed.
     * This method is to avoid multiple round-trips to Xserver
     */
    if (!XInternAtoms (display, atom_names, G_N_ELEMENTS(atom_names), False, atoms) ||
        atoms[0] == None || atoms[1] == None) {
        return;
    }

    /* Set new _XROOTMAP_ID and ESETROOT_PMAP_ID properties */
    XChangeProperty (display, xroot, atoms[0], XA_PIXMAP, 32,
                     PropModeReplace, (unsigned char *) &xpixmap, 1);

    XChangeProperty (display, xroot, atoms[1], XA_PIXMAP, 32,
                     PropModeReplace, (unsigned char *) &xpixmap, 1);
}

void DmGreeterHelperPrivate::setSurfaceAsRoot(GdkScreen* screen,
                    cairo_surface_t* surface)
{
    g_return_if_fail(cairo_surface_get_type (surface) == CAIRO_SURFACE_TYPE_XLIB);

    /* Desktop background pixmap should be created from dummy X client since most
     * applications will try to kill it with XKillClient later when changing pixmap
     */
    Display *display = GDK_DISPLAY_XDISPLAY (gdk_screen_get_display (screen));
    Pixmap pixmap_id = cairo_xlib_surface_get_drawable (surface);
    Window xroot = RootWindow (display, gdk_screen_get_number(screen));

    XGrabServer (display);

    XSetWindowBackgroundPixmap (display, xroot, pixmap_id);
    setRootPixmapId (screen, display, pixmap_id);
    XClearWindow (display, xroot);

    XFlush (display);
    XUngrabServer (display);
}

void DmGreeterHelperPrivate::drawBackgroundToScreen() {

    GdkScreen* screen = gdk_screen_get_default();
    cairo_surface_t* surface = createRootSurface(screen);
    cairo_t* cr = cairo_create(surface);

    Glib::RefPtr< Gdk::Screen > defaultScreen = Gdk::Screen::get_default();
    int monitorLength = defaultScreen->get_n_monitors();
    int index = 0;

    for (index = 0; index < monitorLength; ++index) {
        GdkPixbuf* bgPixbuf = NULL;

        Gdk::Rectangle monitorRect;
        defaultScreen->get_monitor_geometry(index, monitorRect);

        cairo_new_sub_path(cr);
        bgPixbuf = gdk_pixbuf_new_from_file_at_scale(GLOBAL_RES_DIR"/pic/defaultbg.jpg", monitorRect.get_width(), monitorRect.get_height(), FALSE, NULL);
        gdk_cairo_set_source_pixbuf(cr, bgPixbuf, monitorRect.get_x(), monitorRect.get_y());
        cairo_close_path(cr);
        cairo_paint(cr);
    }

    /* Desktop background pixmap should be created from dummy X client since most
     * applications will try to kill it with XKillClient later when changing pixmap
     */
    setSurfaceAsRoot(screen, surface);

    cairo_destroy(cr);
    cairo_surface_destroy(surface);
}

void DmGreeterHelperPrivate::startSession () {
    LightDMUser* userInfo = lightdm_user_list_get_user_by_name(m_userList, m_inAuthName.c_str());
    const char* session = NULL;
    if (NULL != userInfo) {
       session = lightdm_user_get_session(userInfo);
    }
    if (NULL == session || !isValidSession(session)) {
        const char* default_session = lightdm_greeter_get_default_session_hint (m_greeter);
        if (NULL != default_session
            && (NULL == session || 0 != strcmp(session, default_session))
            && isValidSession (default_session)) {
            session = default_session;
        }
        /* first in the sessions list */
        else {
            GList* sessionList = lightdm_get_sessions();
            if (sessionList) {
                session = lightdm_session_get_key ((LightDMSession*)(sessionList->data));
            } else {
                session = NULL;
            }
        }
    }

    drawBackgroundToScreen();

    if (!lightdm_greeter_start_session_sync(m_greeter, session, NULL)) {
        m_dmHelper->signalSessionFailed().emit();
    }
}
