#include "Windowmanager.h"
#include "Icccm.h"
#include "Frame.h"
#include "Desktop.h"
#include "Winhints.h"
#include "Theme.h"

#include <X11/Xproto.h>

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "../exset/exset.h"
#include "config.h"
#include "debug.h"

WindowManager *root;
Window root_win;

Frame_List remove_list;

Frame_List stack_order;
Frame_List map_order;

////////////////////////////////////////////////////////////////
static int initializing;
Exset xset;

static const char* program_name;

// in Hotkeys.cpp
extern int Handle_Hotkey();
extern void Grab_Hotkeys();
extern void read_hotkeys_configuration();
void read_disp_configuration();

#if DESKTOPS
extern void init_desktops();
#endif

static const char* cfg, *cbg;

Fl_Color title_active_color, title_active_color_text;
Fl_Color title_normal_color, title_normal_color_text;

////////////////////////////////////////////////////////////////

// fltk calls this for any events it does not understand:
static int wm_event_handler(int e)
{
    if(fl_xevent.type == KeyPress) e=FL_KEY;

    // XEvent that fltk did not understand.
    if(!e) {
        Window window = fl_xevent.xany.window;

        // unfortunately most of the redirect events put the interesting
        // window id in a different place:
        switch (fl_xevent.type) {
        case CirculateNotify:
        case CirculateRequest:
        case ConfigureNotify:
        case ConfigureRequest:
        case CreateNotify:
        case GravityNotify:
        case MapNotify:
        case MapRequest:
        case ReparentNotify:
        case UnmapNotify:
            window = fl_xevent.xmaprequest.window;
            break;
        }

        for(uint n=stack_order.size(); n--;) {
            Frame *c = stack_order[n];
            if (c->window() == window || fl_xid(c) == window) {
                return c->handle(&fl_xevent);
            }
        }

        return root->handle(&fl_xevent);
    } else
        return root->handle(e);

    return 0;
}

static int xerror_handler(Display* d, XErrorEvent* e) {
    if(initializing && (e->request_code == X_ChangeWindowAttributes) && e->error_code == BadAccess)
        Fl::fatal(_("Another window manager is running.  You must exit it before running %s."), program_name);

#ifndef DEBUG
    if (e->error_code == BadWindow) return 0;
    if (e->error_code == BadColor) return 0;
#endif

    char buf1[128], buf2[128];
    sprintf(buf1, "XRequest.%d", e->request_code);
    XGetErrorDatabaseText(d,"",buf1,buf1,buf2,128);
    XGetErrorText(d, e->error_code, buf1, 128);
    Fl::warning("%s: %s: %s 0x%lx", program_name, buf2, buf1, e->resourceid);
    return 0;
}

WindowManager::WindowManager(int argc, char *argv[])
: Fl_Window(0, 0, Fl::w(), Fl::h())
{
    root = this;
    xset = new Exset();
    init_wm(argc, argv);

    box(FL_NO_BOX);
}

// consume a switch from argv.  Returns number of words eaten, 0 on error:
int arg(int argc, char **argv, int &i) {
    const char *s = argv[i];
    if (s[0] != '-') return 0;
    s++;

    // do single-word switches:
    if (!strcmp(s,"x")) {
        //exit_flag = 1;
        i++;
        return 1;
    }

    // do switches with a value:
    const char *v = argv[i+1];
    if (i >= argc-1 || !v)
        return 0;	// all the rest need an argument, so if missing it is an error

    if (!strcmp(s, "cfg")) {
        cfg = v;
    } else if (!strcmp(s, "cbg")) {
        cbg = v;
    } else if (*s == 'v') {
        int visid = atoi(v);
        fl_open_display();
        XVisualInfo templt; int num;
        templt.visualid = visid;
        fl_visual = XGetVisualInfo(fl_display, VisualIDMask, &templt, &num);
        if (!fl_visual) Fl::fatal("No visual with id %d",visid);
        fl_colormap = XCreateColormap(fl_display, RootWindow(fl_display,fl_screen),
                                      fl_visual->visual, AllocNone);
    } else
        return 0; // unrecognized
    // return the fact that we consumed 2 switches:
    i += 2;
    return 2;
}

int real_align(int i) {
    switch(i) {
    default:
    case 0: break;
    case 1: return FL_ALIGN_RIGHT;
    case 2: return FL_ALIGN_CENTER;
    }
    return FL_ALIGN_LEFT;
}

void read_configuration()
{
    Fl_String buf;
    
    Fl_Config wmconf(fl_find_config_file("wmanager.conf", 0));

    wmconf.set_section("TitleBar");

    wmconf.read("Active color", title_active_color, fl_rgb(0,0,128));
    wmconf.read("Normal color", title_normal_color, fl_rgb(192,192,192));

    wmconf.read("Active color text", title_active_color_text, fl_rgb(255,255,255));
    wmconf.read("Normal color text", title_normal_color_text, fl_rgb(0,0,128));

    wmconf.read("Box type", Titlebar::box_type, 0);
    wmconf.read("Height", Titlebar::default_height, 20);
    wmconf.read("Text align", Titlebar::label_align, 0);
    Titlebar::label_align = real_align(Titlebar::label_align);

    wmconf.set_section("Resize");
    wmconf.read("Opaque resize", Frame::do_opaque, false);
    wmconf.read("Animate", Frame::animate, true);
    wmconf.read("Animate Speed", Frame::animate_speed, 15);
    
    wmconf.set_section("Misc");
	wmconf.read("FocusFollowsMouse", Frame::focus_follows_mouse, false);

    bool theme = false;
    wmconf.read("Use theme", theme, false);
    if(theme) {
        wmconf.read("Theme path", buf, 0);
        Theme::load_theme(buf);
        Theme::use_theme(true);
    } else {
        Theme::unload_theme();
        Theme::use_theme(false);
    }
    Frame::settings_changed_all();
    
    read_hotkeys_configuration();
}

void do_xset_from_conf()
{
    Fl_Config config(fl_find_config_file("ede.conf",1));

    int val1, val2, val3;

    config.set_section("Mouse");
    config.read("Accel", val1, 4);
    config.read("Thress",val2, 4);
    xset.set_mouse(val1, val2);

    config.set_section("Bell");
    config.read("Volume", val1, 50);
    config.read("Pitch", val2, 440);
    config.read("Duration", val3, 200);
    xset.set_bell(val1, val2, val3);

    config.set_section("Keyboard");
    config.read("Repeat", val1, 1);
    config.read("ClickVolume", val2, 50);
    xset.set_keybd(val1, val2);

    config.set_section("Screen");
    config.read("Delay", val1, 15);
    config.read("Pattern",val2, 2);
    xset.set_pattern(val1, val2);

    config.read("CheckBlank", val1, 1);
    xset.set_check_blank(val1);

    config.read("Pattern", val1, 2);
    xset.set_blank(val1);
}

void WindowManager::init_wm(int argc, char *argv[])
{
    static bool wm_inited = false;
    if(wm_inited) return;

    DBG("init windowmanager");

    fl_open_display();

    XShapeQueryExtension(fl_display, &XShapeEventBase, &XShapeErrorBase);

    wm_area.set(0, 0/*22*/, Fl::w(), Fl::h()/*-22*/);

    program_name = fl_file_filename(argv[0]);
    int i;
    if(Fl::args(argc, argv, i, arg) < argc)
        Fl::error("options are:\n"
                  " -d[isplay] host:#.#\tX display & screen to use\n"
                  " -v[isual] #\t\tvisual to use\n"
                  " -g[eometry] WxH+X+Y\tlimits windows to this area\n"
                  " -x\t\t\tmenu says Exit instead of logout\n"
                  " -bg color\t\tFrame color\n"
                  " -fg color\t\tLabel color\n"
                  " -bg2 color\t\tText field color\n"
                  " -cfg color\t\tCursor color\n"
                  " -cbg color\t\tCursor outline color" );

    // Init started
    initializing = 1;

    XSetErrorHandler(xerror_handler);
    Fl::add_handler(wm_event_handler);

    init_atoms(); // intern atoms

    read_configuration();
    do_xset_from_conf();

    show(); // Set XID now

    set_default_cursor();

    ICCCM::set_iconsizes();
    MWM::set_motif_info();

    register_protocols(fl_xid(this));

    Grab_Hotkeys();

    XSync(fl_display, 0);

    init_desktops();

    //Init done
    initializing = 0;
    wm_inited = true;

    // find all the windows and create a Frame for each:
    Frame *f=0;
    unsigned int n;
    Window w1, w2, *wins;
    XWindowAttributes attr;
    XQueryTree(fl_display, fl_xid(this), &w1, &w2, &wins, &n);
    for (i = 0; i < (int)n; ++i) {
        XGetWindowAttributes(fl_display, wins[i], &attr);
        if(attr.override_redirect) continue;
        if(!attr.map_state) {
            if(getIntProperty(wins[i], _XA_WM_STATE, _XA_WM_STATE, 0) != IconicState)
                continue;
        }
        f = new Frame(wins[i], &attr);
    }
    XFree((void *)wins);

    // Activate last one
    for(uint n=0; n<map_order.size(); n++) {
        Frame *f = map_order[n];
        if(f->desktop()==Desktop::current()) {
            f->activate();
            f->raise();
            break;
        }
    }

    update_workarea(true);
}


void WindowManager::show()
{
    if(!shown()) {
        create();

        // Destroy FLTK window
        XDestroyWindow(fl_display, Fl_X::i(this)->xid);
        // Set RootWindow to our xid
        Fl_X::i(this)->xid = RootWindow(fl_display, fl_screen);
        root_win = RootWindow(fl_display, fl_screen);

        // setting attributes on root window makes it the window manager:
        XSelectInput(fl_display, fl_xid(this),
                     SubstructureRedirectMask | SubstructureNotifyMask |
                     ColormapChangeMask | PropertyChangeMask |
                     ButtonPressMask | ButtonReleaseMask |
                     EnterWindowMask | LeaveWindowMask |
                     KeyPressMask | KeyReleaseMask | KeymapStateMask);

        DBG("RootWindow ID set to xid");

        draw();
    }
}

void WindowManager::draw()
{
    DBG("ROOT DRAW");
    //Redraw root window
    XClearWindow(fl_display, fl_xid(this));
}

extern void set_frame_cursor(Fl_Cursor c, Fl_Color fg, Fl_Color bg, Window wid);
void WindowManager::set_default_cursor()
{
    cursor = FL_CURSOR_ARROW;
    set_frame_cursor(FL_CURSOR_ARROW, FL_WHITE, FL_BLACK, root_win);
}

void WindowManager::set_cursor(Fl_Cursor c, Fl_Color fg, Fl_Color bg)
{
    cursor = c;
    set_frame_cursor(c, bg, fg, root_win);
}

Frame *WindowManager::find_by_wid(Window wid)
{
    for(uint n=0; n<map_order.size(); n++) {
        Frame *f = map_order[n];
        if(f->window()==wid) return f;
    }
    return 0;
}

void WindowManager::restack_windows()
{
    Window *windows = new Window[1];
    int total=0;

    DBG("Restack: DOCK, SPLASH");
    for(uint n=0; n<stack_order.size(); n++) {
        Frame *f = stack_order[n];
        if(f->window_type()==TYPE_DOCK || f->window_type()==TYPE_SPLASH) {
            windows = (Window*)realloc(windows, (total+1)*sizeof(Window));
            windows[total++] = fl_xid(f);
        }
    }
    DBG("Restack: TOOLBAR, MENU");
    for(uint n=0; n<stack_order.size(); n++) {
        Frame *f = stack_order[n];
        if(f->window_type()==TYPE_TOOLBAR || f->window_type()==TYPE_MENU) {
            windows = (Window*)realloc(windows, (total+1)*sizeof(Window));
            windows[total++] = fl_xid(f);
        }
    }
    DBG("Restack: NORMAL, UTIL, DIALOG");
    for(uint n=stack_order.size(); n--;) {
        Frame *f = stack_order[n];
        if( (f->window_type()==TYPE_NORMAL ||
             f->window_type()==TYPE_UTIL ||
             f->window_type()==TYPE_DIALOG) &&
           f->state()==NORMAL ) {
            windows = (Window*)realloc(windows, (total+1)*sizeof(Window));
            windows[total++] = fl_xid(f);
        }
    }
    DBG("Restack: DESKTOP");
    for(uint n=0; n<stack_order.size(); n++) {
        Frame *f = stack_order[n];
        if(f->window_type()==TYPE_DESKTOP) {
            windows = (Window*)realloc(windows, (total+1)*sizeof(Window));
            windows[total++] = fl_xid(f);
        }
    }

    DBG("Restack: Call XRestackWindows!");
    if(total) XRestackWindows(fl_display, windows, total);
    delete []windows;
}

void WindowManager::update_workarea(bool send)
{
    int left = 0;
    int right = 0;
    int top = 0;
    int bottom = 0;

    for(uint n=0; n<map_order.size(); n++) {
        Frame *f = map_order[n];
        if( f->strut() && (f->desktop()==Desktop::current() || f->frame_flag(STICKY)) ) {
            left = max(left,  f->strut()->left());
            right= max(right, f->strut()->right());
            top  = max(top,   f->strut()->top());
            bottom = max(bottom,f->strut()->bottom());
        }
    }
    wm_area.set(left, top, Fl::w()-(left+right), Fl::h()-(top+bottom));

    for(uint n=stack_order.size(); n--;) {
        Frame *f = stack_order[n];
        if(f->maximized && f->state()==NORMAL) {
            int W=wm_area.w(), H=wm_area.h();
            W-=f->offset_w;
            H-=f->offset_h;
            ICCCM::get_size(f, W, H);
            W+=f->offset_w;
            H+=f->offset_h;

            f->set_size(wm_area.x(), wm_area.y(), W, H);
            f->maximized = true;
        }
    }

    if(send) {
        Desktop::update_desktop_workarea();
        Desktop::update_desktop_geometry();
    }
}

//Updates NET client list atoms
void WindowManager::update_client_list()
{
    int i=0, client_count=0;
    Frame *f;

    Window *net_map_order   = 0;
    Window *net_stack_order = 0;

    for(uint n=0; n<map_order.size(); n++) {
        f = map_order[n];
        if(!f->frame_flag(SKIP_LIST))
            client_count++;
    }
    if(!client_count) return;

    net_map_order   = new Window[client_count];
    net_stack_order = new Window[client_count];

    i=0;
    for(uint n=0; n<stack_order.size(); n++) {
        f = stack_order[n];
        // We don't want to include transients in our client list
        if(!f->frame_flag(SKIP_LIST)) {
            net_stack_order[i++] = f->window();
        }
    }

    i=0;
    for(uint n=0; n<map_order.size(); n++) {
        f = map_order[n];
        // We don't want to include transients in our client list
        if(!f->frame_flag(SKIP_LIST)) {
            net_map_order[i++] = f->window();
        }
    }

    XChangeProperty(fl_display, fl_xid(root), _XA_NET_CLIENT_LIST, XA_WINDOW, 32, PropModeReplace, (unsigned char*)net_map_order, client_count);
    XChangeProperty(fl_display, fl_xid(root), _XA_NET_CLIENT_LIST_STACKING, XA_WINDOW, 32, PropModeReplace, (unsigned char*)net_stack_order, client_count);

    delete []net_stack_order;
    delete []net_map_order;
}

void WindowManager::idle()
{
    for(uint n=remove_list.size(); n--;) {
        Frame *c = remove_list[n];
        delete c;
    }
    remove_list.clear();
}

// Really really really quick fix, since this
// solution sucks. Btw wm_shutdown is in main.cpp.
extern bool wm_shutdown;
void WindowManager::shutdown()
{
	for(uint n = 0; n < map_order.size(); n++)
	{
		Frame* f = map_order[n];
		f->kill();
	}
	wm_shutdown = true;
}

int WindowManager::handle(int e)
{
    Window window = fl_xevent.xany.window;

    switch(e) {
    case FL_PUSH:
        {
            for(uint n=stack_order.size(); n--;) {
                Frame *c = map_order[n];
                if (c->window() == window || fl_xid(c) == window) {
                    c->content_click();
                    return 1;
                }
            }
            DBG("Button press in root?!?!");
            return 0;
        }

    case FL_SHORTCUT:
    case FL_KEY:
        //case FL_KEYUP:
        return Handle_Hotkey();

    case FL_MOUSEWHEEL:
        {
            XAllowEvents(fl_display, ReplayPointer, CurrentTime);
        }
    }

    return 0;
}

int WindowManager::handle(XEvent *e)
{
    switch(e->type)
    {
    case ClientMessage: {
        DBG("WindowManager ClientMessage 0x%lx", e->xclient.window);

        if(handle_desktop_msgs(&(e->xclient))) return 1;

        return 0;
    }
    case ConfigureRequest: {
        DBG("WindowManager ConfigureRequest: 0x%lx", e->xconfigurerequest.window);
        const XConfigureRequestEvent *e = &(fl_xevent.xconfigurerequest);
        XConfigureWindow(fl_display, e->window,
                         e->value_mask&~(CWSibling|CWStackMode),
                         (XWindowChanges*)&(e->x));
        return 1;
    }

    case MapRequest: {
        DBG("WindowManager MapRequest: 0x%lx", e->xmaprequest.window);
        const XMapRequestEvent* e = &(fl_xevent.xmaprequest);
        new Frame(e->window);
        return 1;
    }
    }
    return 0;
}

bool WindowManager::handle_desktop_msgs(const XClientMessageEvent *e)
{
    if(e->format!=32) return false;

    if(e->message_type==_XA_NET_CURRENT_DESKTOP) {
        Desktop::current((int)e->data.l[0]+1);
        return true;
    } else
    if(e->message_type==_XA_NET_NUM_DESKTOPS) {
        DBG("New desk count: %ld", e->data.l[0]);
        Desktop::update_desktop_count(e->data.l[0]);
        // Set also new names...
        Desktop::set_names();
        return true;
    } else
    if(e->message_type==FLTKChangeSettings) {
        DBG("FLTK change settings");
        read_configuration();
        return true;
    }

    return false;
}