#include "include/window_manager.h"
#include "include/window_draw.h"
#include "include/taskbar_draw.h"

#include <X11/X.h>
#include <X11/Xlib.h>
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define GREY(depth,i) {i=0;for (int j = 0; j<depth/8; j++) {i+=60<<(j*8);}}

extern Pixmap close, resize, minimize; // from window_draw.c
extern char close_icon[], resize_icon[], minimize_icon[];

Display* X_display = NULL;
Window root_window;

linked_window * windows;

linked_window * find_client(Window w) {
    linked_window *curr_check = windows;
    while (curr_check->next != NULL){
        curr_check = curr_check->next;
        if (curr_check->client == w) {
            return curr_check;
        }
    }
    return NULL;
}

linked_window * find_client_by_frame(Window w) {
    linked_window *curr_check = windows;
    while (curr_check->next != NULL){
        curr_check = curr_check->next;
        if (curr_check->frame == w) {
            return curr_check;
        }
    }
    return NULL;
}


void add_client(Window w, Window frame) {
    linked_window * curr_ptr = windows;
    while (curr_ptr->next != NULL) {
        curr_ptr = curr_ptr->next;
    }
    linked_window * new = malloc(sizeof(linked_window));
    new->prev = curr_ptr;
    curr_ptr->next = new;
    new->client = w;
    new->frame = frame;
    new->next = NULL; // to be sure
}

void remove_client(Window w) {
    linked_window * removal = find_client(w);
    if (removal == NULL) return;
    assert(removal->prev != NULL && "Cannot remove root window!");
    removal->prev->next = removal->next;
    if (removal->next != NULL) {
        removal->next->prev = removal->prev;
    }
    free(removal);
}

linked_window * get_window_definite(Window w) {
    linked_window * client = find_client(w);
    if (client != NULL) {
        return client;
    } else {
        client = find_client_by_frame(w);
        if (client != NULL) {
            return client;
        }
        return NULL;
    }
}

static int X_error_handler(Display* display, XErrorEvent* event) {
    char err[256];
    XGetErrorText(X_display, event->error_code, err, 256);
    fprintf(stderr, "Error raised on display %s; reason: %s\n", XDisplayName(NULL), err);
    return 0;
}

static int X_wm_error_pre(Display* display, XErrorEvent* event) {
    if (event->error_code == BadAccess) {
        char err[256];
        XGetErrorText(X_display, event->error_code, err, 256);
        fprintf(stderr, "Failed to start WM on display %s; reason: %s\n", XDisplayName(NULL), err);
        exit(EXIT_FAILURE);
    }
    return 0;
}

static inline unsigned char * generate_pattern(int width, int height) { // XBM
    unsigned char * out = malloc(sizeof(unsigned char) * (width/8) * height);
    memset(out, 0, sizeof(unsigned char) * (width/8) * height);
    for (int i = 0; i<(width/8) * height; i++) {
        out[i] = (i/(width/8))%4>=2?0b11001100:0b00110011;
    }
    return out;
}

void set_background() {
    XWindowAttributes attrs;
    XGetWindowAttributes(X_display, root_window, &attrs);
    unsigned char* background_data = generate_pattern(attrs.width, attrs.height);
    int color;
    GREY(XDefaultDepthOfScreen(DefaultScreenOfDisplay(X_display)), color);
    Pixmap pixmap = XCreatePixmapFromBitmapData(X_display, root_window, (char*)background_data, attrs.width, attrs.height, color, 0,XDefaultDepthOfScreen(DefaultScreenOfDisplay(X_display)));
    XSetWindowBackgroundPixmap(X_display, root_window, pixmap);
    XClearWindow(X_display, root_window);
    XFreePixmap(X_display, pixmap);
    free(background_data);
}

XFontSet wm_default_fontset;
Font wm_default_font;
char ** missing_charsets;
int missing_charset_count;
char * missing_charset_string;

void init() {
    if ((X_display = XOpenDisplay(NULL)) == NULL) {
        fprintf(stderr, "Failed to open display %s: ",XDisplayName(NULL));
        perror("");
        exit(EXIT_FAILURE);
    }
    fprintf(stderr, "Opened display %s\n", XDisplayName(NULL));
    
    XSetErrorHandler(&X_wm_error_pre); // catch if another window manager is already running
    root_window = DefaultRootWindow(X_display);
    
    XSelectInput(X_display, root_window, 
        SubstructureNotifyMask | 
        SubstructureRedirectMask | 
        ResizeRedirectMask | 
        KeyPressMask | 
        KeyReleaseMask |
        FocusChangeMask |
        ExposureMask |
        ButtonPressMask |
        ButtonReleaseMask |
        ButtonMotionMask | 
        PointerMotionMask
    );

    XSync(X_display, False);    
    XSetErrorHandler(&X_error_handler);
    windows = malloc(sizeof(linked_window));
    windows->client = root_window;
    windows->frame = 0;
    set_background();
    wm_default_font = XLoadFont(X_display, "-misc-fixed-bold-*-normal-*-18-120-*-*-*-*-iso8859-1");
    wm_default_fontset = XCreateFontSet(X_display, "-misc-fixed-bold-*-normal-*-18-120-*-*-*-*-*-*", &missing_charsets, &missing_charset_count, &missing_charset_string);
    
    close =    XCreatePixmapFromBitmapData(X_display, root_window, close_icon, _ICON_SIZE, _ICON_SIZE, 0x333333, 0x555555,XDefaultDepthOfScreen(DefaultScreenOfDisplay(X_display)));
    resize =   XCreatePixmapFromBitmapData(X_display, root_window, resize_icon, _ICON_SIZE, _ICON_SIZE, 0x333333, 0x555555,XDefaultDepthOfScreen(DefaultScreenOfDisplay(X_display)));
    minimize = XCreatePixmapFromBitmapData(X_display, root_window, minimize_icon, _ICON_SIZE, _ICON_SIZE, 0x333333, 0x555555,XDefaultDepthOfScreen(DefaultScreenOfDisplay(X_display)));
    printf("Created icons\n");
    create_taskbar();
}

void free_linked() {

}

void deinit() {
    XFreeFontSet(X_display, wm_default_fontset);

    free(missing_charset_string);
    for (int i = 0; i<missing_charset_count; i++) {
        free(missing_charsets[i]);
    }
    free(missing_charsets);

    free_linked();
    XCloseDisplay(X_display);
    fprintf(stderr, "Closed display %s\n", XDisplayName(NULL));
    exit(EXIT_FAILURE);
}