extern class Widget;
extern class Window;

#define WIN_FLAGS_NULL 0x0
#define WIN_FLAGS_NO_REINDEX 0x1 // Wallpaper, taskbar, etc.
#define WIN_FLAGS_RESIZABLE 0x2
#define WIN_FLAGS_MOVABLE 0x4
#define WIN_FLAGS_ICON 0x8
#define WIN_FLAGS_TITLE_BAR 0x10
#define WIN_FLAGS_MIN_BUTTON 0x20
#define WIN_FLAGS_MAX_BUTTON 0x40
#define WIN_FLAGS_CLOSE_BUTTON 0x80

#define WIN_FLAGS_MINIMIZED 0x100
#define WIN_FLAGS_MAXIMIZED 0x200
#define WIN_FLAGS_HIDDEN 0x400
#define WIN_FLAGS_NOHILIGHT 0x800

#define WIN_FLAGS_SKIP 0x1000
#define WIN_FLAGS_NOFILL 0x2000
#define WIN_FLAGS_MENU 0x4000

#define WIN_FLAGS_MAX 0x10000

#define WIN_SIGNATURE 0x1596e3c1c62c34b929d75cded8c0

#define WIN_FLAGS_DEFAULT \
    (WIN_FLAGS_RESIZABLE | WIN_FLAGS_MOVABLE | WIN_FLAGS_ICON | WIN_FLAGS_TITLE_BAR | WIN_FLAGS_MIN_BUTTON | WIN_FLAGS_MAX_BUTTON | WIN_FLAGS_CLOSE_BUTTON)

class @widget_callbacks
{
    U0 (*change)(Widget* widget);
    U0 (*clicked)(Widget* widget);
    U0 (*repaint)(Widget* widget);
};

class @widget_origin
{
    I64 x;
    I64 y;
    I64 width;
    I64 height;
    I64 mouse_x;
    I64 mouse_y;
};

class Widget {
    Bool change;
    I64 id;
    I64 type;
    I64 x;
    I64 y;
    I64 width;
    I64 height;
    I64 opacity;
    U64 flags;
    U64 data;
    U8* tag;
    Widget* echo;
    Window* parent_win;
    Context2D* backing_store;
    Context2D* pointer;
    @widget_callbacks callback;
    @widget_origin origin;
};

class @window_widgets_list
{
    @window_widgets_list* prev;
    @window_widgets_list* next;
    Widget* widget;
};

class @window_origin
{
    I64 x;
    I64 y;
    I64 width;
    I64 height;
    I64 mouse_x;
    I64 mouse_y;
};

class @window_position
{
    I64 x;
    I64 y;
}

class @window_buttons
{
    Bool minimize;
    Bool maximize;
    Bool close;
};

class @window_callbacks
{
    U0 (*minimize)(Window* win);
    U0 (*maximize)(Window* win);
    U0 (*mouseat)(Window* win);
    U0 (*keypress)(Window* win, I64 key);
    U0 (*repaint)(Window* win);
    U0 (*close)(Window* win);
};

class @window_mouse
{
    I64 x;
    I64 y;
    Bool left;
    Bool right;
};

class @window_event
{ // FIXME: Better name?
    I64 x;
    I64 y;
    Bool left;
    Bool right;
};

class Window {
    U64 signature;
    Bool alpha;
    Bool refresh;
    Bool repainting;
    Bool explicit_repaint;
    CTask* client;
    I64 x;
    I64 y;
    I64 width;
    I64 height;
    I64 opacity;
    U64 flags;
    U8 title[512];
    I64 title_bar_x;
    I64 title_bar_width;
    I64 min_width;
    I64 min_height;
    Context2D* icon;
    Context2D* backing_store;
    Context2D* pointer;
    Context2D* render_ctx;
    Context2D* resize_ctx;
    Widget* mouse_down_widget;
    Widget* focused_widget;
    Widget* hovered_widget;
    @window_buttons button;
    @window_callbacks callback;
    @window_origin origin;
    @window_mouse mouse;
    @window_event left_btn_down; // FIXME: put these in a Window.event.xxx class?
    @window_event left_btn_up;
    @window_event right_btn_down;
    @window_event right_btn_up;
    @window_widgets_list* widget;
};

class @gui_widget
{
    Bool (*IsHovered)(Window* win, Widget* widget);
    U0 (*SetCallback)(Widget* widget, U8* name, U64 callback);
    U0 (*SetEcho)(Widget* widget, Widget* echo);
    U0 (*SetFont)(Widget* widget, U8* font_name);
    U0 (*SetMousePointer)(Widget* widget, Context2D* pointer);
    U0 (*ClearMousePointer)(Widget* widget);
    U0 (*SetOpacity)(Widget* widget, I64 opacity);
    U0 (*SetText)(Widget* widget, U8* text);
};

class @gui_window
{
    U0 (*Center)(Window* win, Bool horz = TRUE, Bool vert = TRUE);
    U0 (*DisableAlphaChannel)(Window* win);
    U0 (*EnableAlphaChannel)(Window* win);
    U0 (*Hide)(Window* win);
    Bool (*IsHovered)(Window* win);
    Bool (*IsVisible)(Window* win);
    U0 (*SetCallback)(Window* win, U8* name, U64 callback);
    U0 (*SetFocus)(Window* win);
    U0 (*SetIcon)(Window* win, Context2D* icon);
    U0 (*SetMousePointer)(Window* win, Context2D* pointer);
    U0 (*ClearMousePointer)(Window* win);
    U0 (*SetOpacity)(Window* win, I64 opacity);
    U0 (*SetTitle)(Window* win, U8* text);
    U0 (*SetPosition)(Window* win, I64 x, I64 y);
    U0 (*SetZIndex)(Window* win, I64 index);
    U0 (*Show)(Window* win);
    U0 (*Refresh)(Window* win);
};

class @gui
{
    @gui_widget Widget;
    @gui_window Window;
    U0 (*App)();
    Widget* (*InitWidget)(Widget* widget, Window* win, I64 type, I64 x, I64 y,
        I64 width, I64 height);
    Widget* (*CreateWidget)(Window* win, I64 type, I64 x, I64 y, I64 width,
        I64 height);
};

@gui Gui;

I64 @gui_app_header_size;
U8* @gui_app_header_data = FileRead("M:/Include/Gui.HC", &@gui_app_header_size);

U0 @gui_app()
{
    CDoc* @gui_app_header_doc = DocNew;
    DocLoad(@gui_app_header_doc, @gui_app_header_data, @gui_app_header_size);
    ExeDoc(@gui_app_header_doc);
}

Bool @gui_window_flag_is_set(Window* win, U64 flag)
{
    if (!win)
        return FALSE;
    if (win->flags & flag == flag)
        return TRUE;
    return FALSE;
}

Bool @gui_window_is_hovered(Window* win)
{
    if (Mouse.x > win->x && Mouse.x < win->x + win->width && Mouse.y > win->y && Mouse.y < win->y + win->height)
        return TRUE;
    return FALSE;
}

Bool @gui_window_is_visible(Window* win)
{
    if (!win)
        return NULL;
    return !@gui_window_flag_is_set(win, WIN_FLAGS_HIDDEN);
}

U0 @gui_widget_destroy(Widget* widget)
{
    Window* win = widget->parent_win;
    @window_widgets_list* widgets = win->widget;
    @window_widgets_list* prev;
    @window_widgets_list* next;
    while (widgets) {
        if (widgets->widget == widget) {
            prev = widgets->prev;
            next = widgets->next;
            if (prev)
                prev->next = next;
            if (next)
                next->prev = prev;
            // FIXME: Free widget and child data
            widget->type = NULL;
        }
        widgets = widgets->next;
    }
}

U0 @gui_widget_repaint(Window* win, Widget* widget, I64 type) { }

U0 @gui_window_repaint(Window* win, I64 type)
{
    @system_log(Fs, "Repainting window 0x%08x [%s]", win, win->title);
}

U0 @gui_window_center(Window* win, Bool horz = TRUE, Bool vert = TRUE)
{
    if (!win)
        return;
    I64 x = win->x;
    I64 y = win->y;
    if (horz)
        x = (Display.Width() / 2) - (win->width / 2);
    if (vert)
        y = (Display.Height() / 2) - (win->height / 2);
    win->x = x;
    win->y = y;
}

U0 @gui_window_set_position(Window* win, I64 x, I64 y)
{
    if (!win)
        return;
    win->x = x;
    win->y = y;
}

U0 @gui_window_set_icon(Window* win, Context2D* icon)
{
    if (!win || !icon)
        return;
    Bool refresh = FALSE;
    if (win->icon != icon)
        refresh = TRUE;
    win->icon = icon;
    if (refresh)
        Gui.Window.Refresh(win);
}

U0 @gui_window_set_mouse_pointer(Window* win, Context2D* pointer)
{
    if (!win)
        return;
    win->pointer = pointer;
}

U0 @gui_window_clear_mouse_pointer(Window* win)
{
    if (!win)
        return;
    win->pointer = NULL;
}

U0 @gui_window_set_opacity(Window* win, I64 opacity)
{
    if (!win)
        return;
    Bool refresh = FALSE;
    if (win->opacity != opacity)
        refresh = TRUE;
    win->opacity = ClampI64(opacity, 0, 255);
    if (refresh)
        Gui.Window.Refresh(win);
}

U0 @gui_window_set_title(Window* win, U8* text)
{
    if (!win || !text)
        return;
    if (!StrLen(text))
        return;
    if (StrLen(text) > 511) {
        MemCpy(&win->title, text, 512);
        win->title[511] = NULL;
        return;
    }
    StrCpy(&win->title, text);
    Gui.Window.Refresh(win);
}

U0 @gui_window_callback_close(Window* win) { }

U0 @gui_window_callback_maximize(Window* win)
{
    win->flags |= WIN_FLAGS_MAXIMIZED;
}

U0 @gui_window_callback_minimize(Window* win)
{
    win->flags |= WIN_FLAGS_MINIMIZED;
}

U0 @gui_window_disable_alpha_channel(Window* win) { win->alpha = FALSE; }

U0 @gui_window_enable_alpha_channel(Window* win) { win->alpha = TRUE; }

U0 @gui_window_hide(Window* win) { win->flags |= WIN_FLAGS_HIDDEN; }

U0 @gui_window_show(Window* win)
{
    win->flags &= ~WIN_FLAGS_HIDDEN;
    win->flags &= ~WIN_FLAGS_MINIMIZED;
}

U0 @gui_window_set_callback(Window* win, U8* name, U64 callback)
{
    if (!win || !name || !callback)
        return;
    if (!StrCmp(name, "minimize"))
        win->callback.minimize = callback;
    if (!StrCmp(name, "maximize"))
        win->callback.maximize = callback;
    if (!StrCmp(name, "mouseat"))
        win->callback.mouseat = callback;
    if (!StrCmp(name, "keypress"))
        win->callback.keypress = callback;
    if (!StrCmp(name, "repaint"))
        win->callback.repaint = callback;
    if (!StrCmp(name, "close"))
        win->callback.close = callback;
}

Gui.App = &@gui_app;
Gui.Window.Center = &@gui_window_center;
Gui.Window.DisableAlphaChannel = &@gui_window_disable_alpha_channel;
Gui.Window.EnableAlphaChannel = &@gui_window_enable_alpha_channel;
Gui.Window.Hide = &@gui_window_hide;
Gui.Window.IsHovered = &@gui_window_is_hovered;
Gui.Window.IsVisible = &@gui_window_is_visible;
Gui.Window.SetCallback = &@gui_window_set_callback;
Gui.Window.SetIcon = &@gui_window_set_icon;
Gui.Window.SetMousePointer = &@gui_window_set_mouse_pointer;
Gui.Window.ClearMousePointer = &@gui_window_clear_mouse_pointer;
Gui.Window.SetOpacity = &@gui_window_set_opacity;
Gui.Window.SetTitle = &@gui_window_set_title;
Gui.Window.SetPosition = &@gui_window_set_position;
Gui.Window.Show = &@gui_window_show;

"gui ";