#include "Common.h"

#include <gdkmm.h>
#include <gtkmm.h>
#include <syslog.h>
#include <stdarg.h>
#include <string.h>

#include <gdk/gdkx.h>
#include <X11/XKBlib.h>
#include <X11/keysym.h>

#define DEFAULT_SCREEN_WIDTH  (1920.0f)
#define DEFAULT_SCREEN_HEIGHT (1080.0f)

Common* Common::m_inst = NULL;

class StyleInfo {
public:
    StyleInfo(const Glib::ustring& stylePath, uint priority)
        : m_stylePath(stylePath)
        , m_priority(priority) {
    }

    void addCssStylePath(Gtk::Widget& widget) {
        Glib::RefPtr<Gtk::CssProvider> provider = Gtk::CssProvider::create();
        Glib::RefPtr<Gtk::StyleContext> context = widget.get_style_context();
        provider->load_from_path(m_stylePath);
        context->add_provider(provider, m_priority);
        Gtk::Container* container = dynamic_cast<Gtk::Container*>(&widget);
        if (NULL != container) {
            container->foreach(sigc::mem_fun(*this, &StyleInfo::onContainerForeach));
        }
    }

    void onContainerForeach(Gtk::Widget& widget) {
        addCssStylePath(widget);
    }

private:
    Glib::ustring m_stylePath;
    uint m_priority;
};

static Display* get_default_x11_display() {
    return GDK_DISPLAY_XDISPLAY(gdk_display_get_default());
}

static int xkb_init() {
    Display* dpy = get_default_x11_display();
    int xkb_opcode, xkb_event, xkb_error;
    int xkb_lmaj = XkbMajorVersion;
    int xkb_lmin = XkbMinorVersion;
    return XkbLibraryVersion( &xkb_lmaj, &xkb_lmin )
        && XkbQueryExtension( dpy, &xkb_opcode, &xkb_event, &xkb_error,
                   &xkb_lmaj, &xkb_lmin );
}

static unsigned int xkb_mask_modifier( XkbDescPtr xkb, const char *name ) {
    int i;
    if( !xkb || !xkb->names ) {
        return 0;
    }
    for ( i = 0; i < XkbNumVirtualMods; i++) {
        char* modStr = XGetAtomName( xkb->dpy, xkb->names->vmods[i] );
        if(modStr != NULL && strcmp(name, modStr) == 0) {
            unsigned int mask;
            XkbVirtualModsToReal(xkb, 1 << i, &mask);
            return mask;
        }
    }
    return 0;
}

static unsigned int xkb_numlock_mask() {
    Display* dpy = get_default_x11_display();
    XkbDescPtr xkb;
    if(( xkb = XkbGetKeyboard( dpy, XkbAllComponentsMask, XkbUseCoreKbd )) != NULL ) {
        unsigned int mask = xkb_mask_modifier( xkb, "NumLock" );
        XkbFreeKeyboard( xkb, 0, True );
        return mask;
    }
    return 0;
}

Common::Common() {
}

Common* Common::inst() {
    if (NULL == m_inst) {
        m_inst = new Common();
    }
    return m_inst;
}

void Common::getPrimaryScaleFactors(double& scalex, double& scaley) {
    Glib::RefPtr< Gdk::Screen > defaultScreen = Gdk::Screen::get_default();
    getMonitorScaleFactors(defaultScreen, defaultScreen->get_primary_monitor(), scalex, scaley);        
}

void Common::getMonitorScaleFactors(const Glib::RefPtr< Gdk::Screen >& screen, int monitorIndex, double& scalex, double& scaley) {
    int monitorLength = screen->get_n_monitors();
    if (0 <= monitorIndex && monitorLength > monitorIndex) {
        Gdk::Rectangle monitorArea;
        screen->get_monitor_geometry(monitorIndex, monitorArea);
        scalex = ((double)monitorArea.get_width()) / DEFAULT_SCREEN_WIDTH;
        scaley = ((double)monitorArea.get_height()) / DEFAULT_SCREEN_HEIGHT;
    }
}

void Common::getMonitorScaleFactors(const Glib::RefPtr< Gdk::Monitor >& monitor, double& scalex, double& scaley) {
    Gdk::Rectangle monitorArea;
    monitor->get_geometry(monitorArea);
    scalex = ((double)monitorArea.get_width()) / DEFAULT_SCREEN_WIDTH;
    scaley = ((double)monitorArea.get_height()) / DEFAULT_SCREEN_HEIGHT;
}

void Common::addCssStylePath(Gtk::Widget& widget, const Glib::ustring& stylePath, uint priority) {
    StyleInfo info(stylePath, priority);
    info.addCssStylePath(widget);
}

bool Common::isPrimary(const Glib::RefPtr< Gdk::Monitor >& monitor) {
    Glib::RefPtr< Gdk::Display > display = Gdk::Display::get_default();
    Glib::RefPtr< Gdk::DeviceManager > device_manager = display->get_device_manager();
    Glib::RefPtr< Gdk::Device > pointerDev = device_manager->get_client_pointer();
    int pointX;
    int pointY;
    Gdk::Rectangle monitorArea;
    pointerDev->get_position(pointX, pointY);
    monitor->get_geometry(monitorArea);

    if (monitorArea.get_x() <= pointX && (monitorArea.get_width() + monitorArea.get_x()) > pointX
        && monitorArea.get_y() <= pointY && (monitorArea.get_height() + monitorArea.get_y()) > pointY) {
        return true;
    }
    return false;
}
void Common::setNumlock(bool on) {
    unsigned int mask;
    if (!xkb_init()) {
        return;
    }

    mask = xkb_numlock_mask();
    if(0 == mask) {
        return;
    }

    Display* dpy = get_default_x11_display();
    if (on) {
        XkbLockModifiers ( dpy, XkbUseCoreKbd, mask, mask);
    } else {
        XkbLockModifiers ( dpy, XkbUseCoreKbd, mask, 0);
    }
}