/* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*-

   config.m for the Azalea window manager
   Copyright (c) 2006        Yen-Ju Chen

   config.c for the Openbox window manager
   Copyright (c) 2004        Mikael Magnusson
   Copyright (c) 2003        Ben Jansens

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   See the COPYING file for a copy of the GNU General Public License.
*/

#import "AZDock.h"
#import "config.h"
#import "AZKeyboardHandler.h"
#import "AZMouseHandler.h"
#import "prop.h"
#import "translate.h"
#import "parse.h"
#import "openbox.h"

BOOL config_focus_new;
BOOL config_focus_follow;
unsigned int    config_focus_delay;
BOOL config_focus_raise;
BOOL config_focus_last;

ObPlacePolicy config_place_policy;

NSString *config_theme;
BOOL config_theme_keepborder;
BOOL config_theme_hidedisabled;

NSString *config_title_layout;

int    config_desktops_num;
NSArray *config_desktops_names;
int    config_screen_firstdesk;

BOOL config_resize_redraw;
BOOL config_resize_four_corners;
int     config_resize_popup_show;
int     config_resize_popup_pos;

ObStackingLayer config_dock_layer;
BOOL        config_dock_floating;
BOOL        config_dock_nostrut;
ObDirection     config_dock_pos;
int            config_dock_x;
int            config_dock_y;
ObOrientation   config_dock_orient;
BOOL        config_dock_hide;
unsigned int           config_dock_hide_delay;
unsigned int           config_dock_show_delay;
unsigned int           config_dock_app_move_button;
unsigned int           config_dock_app_move_modifiers;

unsigned int config_keyboard_reset_keycode;
unsigned int config_keyboard_reset_state;

int config_mouse_threshold;
int config_mouse_dclicktime;

BOOL config_menu_warppointer;
BOOL config_menu_xorstyle;
unsigned int    config_menu_hide_delay;
BOOL config_menu_client_list_icons;

NSArray *config_menu_files;

int config_resist_win;
int config_resist_edge;

BOOL config_resist_layers_below;

/*

<keybind key="C-x">
  <action name="ChangeDesktop">
    <desktop>3</desktop>
  </action>
</keybind>

*/

static void parse_key(AZParser *parser, xmlDocPtr doc, xmlNodePtr node,
                      NSMutableArray *keylist)
{
    NSString *key;
    AZAction *action;
    xmlNodePtr n, nact;

    if ((n = parse_find_node("chainQuitKey", node))) {
        key = parse_string(doc, n);
        translate_key(key, &config_keyboard_reset_state,
                      &config_keyboard_reset_keycode);
    }

    key = nil; /* reset, just in case */
    n = parse_find_node("keybind", node);
    while (n) {
        if (parse_attr_string("key", n, &key)) {
	    if (keylist == nil) {
	      keylist = AUTORELEASE([[NSMutableArray alloc] init]);
	    }
	    [keylist addObject: key];

            parse_key(parser, doc, n->children, keylist);

	    [keylist removeLastObject];
        }
        n = parse_find_node("keybind", n->next);
    }
    if ([keylist count]) {
        nact = parse_find_node("action", node);
        while (nact) {
            if ((action = action_parse(doc, nact,
                                       OB_USER_ACTION_KEYBOARD_KEY))) {
		AZKeyboardHandler *kHandler = [AZKeyboardHandler defaultHandler];
		[kHandler bind: keylist action: action];
	    }
            nact = parse_find_node("action", nact->next);
        }
    }
}

static void parse_keyboard(AZParser *parser, xmlDocPtr doc, xmlNodePtr node,
                           void * d)
{
    [[AZKeyboardHandler defaultHandler] unbindAll];

    parse_key(parser, doc, node->children, NULL);
}

/*

<context name="Titlebar"> 
  <mousebind button="Left" action="Press">
    <action name="Raise"></action>
  </mousebind>
</context>

*/

static void parse_mouse(AZParser *parser, xmlDocPtr doc, xmlNodePtr node,
                        void * d)
{
    xmlNodePtr n, nbut, nact;
    NSString *buttonstr;
    NSString *contextstr;
    ObUserAction uact;
    ObMouseAction mact;
    AZAction *action;
    AZMouseHandler *mouseHandler = [AZMouseHandler defaultHandler];

    [mouseHandler unbindAll];

    node = node->children;
    
    if ((n = parse_find_node("dragThreshold", node)))
        config_mouse_threshold = parse_int(doc, n);
    if ((n = parse_find_node("doubleClickTime", node)))
        config_mouse_dclicktime = parse_int(doc, n);

    n = parse_find_node("context", node);
    while (n) {
        if (!parse_attr_string("name", n, &contextstr))
            goto next_n;
        nbut = parse_find_node("mousebind", n->children);
        while (nbut) {
            if (!parse_attr_string("button", nbut, &buttonstr))
                goto next_nbut;
            if (parse_attr_contains("press", nbut, "action")) {
                uact = OB_USER_ACTION_MOUSE_PRESS;
                mact = OB_MOUSE_ACTION_PRESS;
            } else if (parse_attr_contains("release", nbut, "action")) {
                uact = OB_USER_ACTION_MOUSE_RELEASE;
                mact = OB_MOUSE_ACTION_RELEASE;
            } else if (parse_attr_contains("click", nbut, "action")) {
                uact = OB_USER_ACTION_MOUSE_CLICK;
                mact = OB_MOUSE_ACTION_CLICK;
            } else if (parse_attr_contains("doubleclick", nbut,"action")) {
                uact = OB_USER_ACTION_MOUSE_DOUBLE_CLICK;
                mact = OB_MOUSE_ACTION_DOUBLE_CLICK;
            } else if (parse_attr_contains("drag", nbut, "action")) {
                uact = OB_USER_ACTION_MOUSE_MOTION;
                mact = OB_MOUSE_ACTION_MOTION;
            } else
                goto next_nbut;
            nact = parse_find_node("action", nbut->children);
            while (nact) {
                if ((action = action_parse(doc, nact, uact)))
                    [mouseHandler bind: buttonstr context: contextstr
			    mouseAction: mact action: action];
                nact = parse_find_node("action", nact->next);
            }
        next_nbut:
            nbut = parse_find_node("mousebind", nbut->next);
        }
    next_n:
        n = parse_find_node("context", n->next);
    }
}

static void parse_focus(AZParser *parser, xmlDocPtr doc, xmlNodePtr node,
                        void * d)
{
    xmlNodePtr n;

    node = node->children;
    
    if ((n = parse_find_node("focusNew", node)))
        config_focus_new = parse_bool(doc, n);
    if ((n = parse_find_node("followMouse", node)))
        config_focus_follow = parse_bool(doc, n);
    if ((n = parse_find_node("focusDelay", node)))
        config_focus_delay = parse_int(doc, n) * 1000;
    if ((n = parse_find_node("raiseOnFocus", node)))
        config_focus_raise = parse_bool(doc, n);
    if ((n = parse_find_node("focusLast", node)))
        config_focus_last = parse_bool(doc, n);
}

static void parse_placement(AZParser *parser, xmlDocPtr doc, xmlNodePtr node,
                            void * d)
{
    xmlNodePtr n;

    node = node->children;
    
    if ((n = parse_find_node("policy", node)))
        if (parse_contains("UnderMouse", doc, n))
            config_place_policy = OB_PLACE_POLICY_MOUSE;
}

static void parse_theme(AZParser *parser, xmlDocPtr doc, xmlNodePtr node,
                        void * d)
{
    xmlNodePtr n;

    node = node->children;

    if ((n = parse_find_node("name", node))) {
        NSString *c;

        c = parse_string(doc, n);
	ASSIGN(config_theme, [c stringByExpandingTildeInPath]);
    }
    if ((n = parse_find_node("titleLayout", node))) {
	ASSIGN(config_title_layout, parse_string(doc, n));
    }
    if ((n = parse_find_node("keepBorder", node)))
        config_theme_keepborder = parse_bool(doc, n);
    if ((n = parse_find_node("hideDisabled", node)))
        config_theme_hidedisabled = parse_bool(doc, n);
}

static void parse_desktops(AZParser *parser, xmlDocPtr doc, xmlNodePtr node,
                           void * d)
{
    xmlNodePtr n;

    node = node->children;
    
    if ((n = parse_find_node("number", node))) {
        int d = parse_int(doc, n);
        if (d > 0)
            config_desktops_num = d;
    }
    if ((n = parse_find_node("firstdesk", node))) {
        int d = parse_int(doc, n);
        if (d > 0)
            config_screen_firstdesk = d;
    }
    if ((n = parse_find_node("names", node))) {
        xmlNodePtr nname;

	NSMutableArray *a = [[NSMutableArray alloc] init];

        nname = parse_find_node("name", n->children);
        while (nname) {
	    [a addObject: parse_string(doc, nname)];
            nname = parse_find_node("name", nname->next);
        }
	ASSIGNCOPY(config_desktops_names, a);
	DESTROY(a);
    }
}

static void parse_resize(AZParser *parser, xmlDocPtr doc, xmlNodePtr node,
                         void * d)
{
    xmlNodePtr n;

    node = node->children;
    
    if ((n = parse_find_node("drawContents", node)))
        config_resize_redraw = parse_bool(doc, n);
    if ((n = parse_find_node("fourCorner", node)))
        config_resize_four_corners = parse_bool(doc, n);
    if ((n = parse_find_node("popupShow", node))) {
        config_resize_popup_show = parse_int(doc, n);
        if (parse_contains("Always", doc, n))
            config_resize_popup_show = 2;
        else if (parse_contains("Never", doc, n))
            config_resize_popup_show = 0;
        else if (parse_contains("Nonpixel", doc, n))
            config_resize_popup_show = 1;
    }
    if ((n = parse_find_node("popupPosition", node))) {
        config_resize_popup_pos = parse_int(doc, n);
        if (parse_contains("Top", doc, n))
            config_resize_popup_pos = 1;
        else if (parse_contains("Center", doc, n))
            config_resize_popup_pos = 0;
    }
}

static void parse_dock(AZParser *parser, xmlDocPtr doc, xmlNodePtr node,
                       void * d)
{
    xmlNodePtr n;

    node = node->children;

    if ((n = parse_find_node("position", node))) {
        if (parse_contains("TopLeft", doc, n))
            config_dock_floating = NO,
            config_dock_pos = OB_DIRECTION_NORTHWEST;
        else if (parse_contains("Top", doc, n))
            config_dock_floating = NO,
            config_dock_pos = OB_DIRECTION_NORTH;
        else if (parse_contains("TopRight", doc, n))
            config_dock_floating = NO,
            config_dock_pos = OB_DIRECTION_NORTHEAST;
        else if (parse_contains("Right", doc, n))
            config_dock_floating = NO,
            config_dock_pos = OB_DIRECTION_EAST;
        else if (parse_contains("BottomRight", doc, n))
            config_dock_floating = NO,
            config_dock_pos = OB_DIRECTION_SOUTHEAST;
        else if (parse_contains("Bottom", doc, n))
            config_dock_floating = NO,
            config_dock_pos = OB_DIRECTION_SOUTH;
        else if (parse_contains("BottomLeft", doc, n))
            config_dock_floating = NO,
            config_dock_pos = OB_DIRECTION_SOUTHWEST;
        else if (parse_contains("Left", doc, n))
            config_dock_floating = NO,
            config_dock_pos = OB_DIRECTION_WEST;
        else if (parse_contains("Floating", doc, n))
            config_dock_floating = YES;
    }
    if (config_dock_floating) {
        if ((n = parse_find_node("floatingX", node)))
            config_dock_x = parse_int(doc, n);
        if ((n = parse_find_node("floatingY", node)))
            config_dock_y = parse_int(doc, n);
    } else {
        if ((n = parse_find_node("noStrut", node)))
            config_dock_nostrut = parse_bool(doc, n);
    }
    if ((n = parse_find_node("stacking", node))) {
        if (parse_contains("top", doc, n))
            config_dock_layer = OB_STACKING_LAYER_ABOVE;
        else if (parse_contains("normal", doc, n))
            config_dock_layer = OB_STACKING_LAYER_NORMAL;
        else if (parse_contains("bottom", doc, n))
            config_dock_layer = OB_STACKING_LAYER_BELOW;
    }
    if ((n = parse_find_node("direction", node))) {
        if (parse_contains("horizontal", doc, n))
            config_dock_orient = OB_ORIENTATION_HORZ;
        else if (parse_contains("vertical", doc, n))
            config_dock_orient = OB_ORIENTATION_VERT;
    }
    if ((n = parse_find_node("autoHide", node)))
        config_dock_hide = parse_bool(doc, n);
    if ((n = parse_find_node("hideDelay", node)))
        config_dock_hide_delay = parse_int(doc, n) * 1000;
    if ((n = parse_find_node("showDelay", node)))
        config_dock_show_delay = parse_int(doc, n) * 1000;
    if ((n = parse_find_node("moveButton", node))) {
        NSString *str = parse_string(doc, n);
        unsigned int b, s;
        if (translate_button(str, &s, &b)) {
            config_dock_app_move_button = b;
            config_dock_app_move_modifiers = s;
        } else {
            NSLog(@"Warning: invalid button '%@'", str);
        }
    }
}

static void parse_menu(AZParser *parser, xmlDocPtr doc, xmlNodePtr node,
                       void * d)
{
    xmlNodePtr n;
    NSMutableArray *a;
    if (config_menu_files)
      a = [NSMutableArray arrayWithArray: config_menu_files];
    else
      a = AUTORELEASE([[NSMutableArray alloc] init]);

    for (node = node->children; node; node = node->next) {
        if (!xmlStrcasecmp(node->name, (const xmlChar*) "file")) {
            [a addObject: parse_string(doc, node)];
        }
        if ((n = parse_find_node("warpPointer", node)))
            config_menu_warppointer = parse_bool(doc, n);
        if ((n = parse_find_node("xorStyle", node)))
            config_menu_xorstyle = parse_bool(doc, n);
        if ((n = parse_find_node("hideDelay", node)))
            config_menu_hide_delay = parse_int(doc, n);
        if ((n = parse_find_node("desktopMenuIcons", node)))
            config_menu_client_list_icons = parse_bool(doc, n);
    }
    ASSIGNCOPY(config_menu_files, a);
}
   
static void parse_resistance(AZParser *parser, xmlDocPtr doc, xmlNodePtr node, 
                             void * d)
{
    xmlNodePtr n;

    node = node->children;
    if ((n = parse_find_node("strength", node)))
        config_resist_win = parse_int(doc, n);
    if ((n = parse_find_node("screen_edge_strength", node)))
        config_resist_edge = parse_int(doc, n);
    if ((n = parse_find_node("edges_hit_layers_below", node)))
        config_resist_layers_below = parse_bool(doc, n);
}

typedef struct
{
    NSString *key;
    NSString *actname;
} ObDefKeyBind;

static void bind_default_keyboard()
{
    ObDefKeyBind *it;
    ObDefKeyBind binds[] = {
        { @"A-Tab", @"NextWindow" },
        { @"S-A-Tab", @"PreviousWindow" },
        { @"A-F4", @"Close" },
        { NULL, nil}
    };

    for (it = binds; it->key; ++it) {
	[[AZKeyboardHandler defaultHandler] 
		bind: [NSArray arrayWithObjects: it->key, nil]
		action: [AZAction actionWithName: it->actname userAction: OB_USER_ACTION_KEYBOARD_KEY]];
    }
}

typedef struct
{
    NSString *button;
    NSString *context;
    const ObMouseAction mact;
    NSString *actname;
} ObDefMouseBind;

static void bind_default_mouse()
{
    ObDefMouseBind *it;
    ObDefMouseBind binds[] = {
        { @"Left", @"Client", OB_MOUSE_ACTION_PRESS, @"Focus" },
        { @"Middle", @"Client", OB_MOUSE_ACTION_PRESS, @"Focus" },
        { @"Right", @"Client", OB_MOUSE_ACTION_PRESS, @"Focus" },
        { @"Left", @"Desktop", OB_MOUSE_ACTION_PRESS, @"Focus" },
        { @"Middle", @"Desktop", OB_MOUSE_ACTION_PRESS, @"Focus" },
        { @"Right", @"Desktop", OB_MOUSE_ACTION_PRESS, @"Focus" },
        { @"Left", @"Titlebar", OB_MOUSE_ACTION_PRESS, @"Focus" },
        { @"Left", @"Handle", OB_MOUSE_ACTION_PRESS, @"Focus" },
        { @"Left", @"BLCorner", OB_MOUSE_ACTION_PRESS, @"Focus" },
        { @"Left", @"BRCorner", OB_MOUSE_ACTION_PRESS, @"Focus" },
        { @"Left", @"TLCorner", OB_MOUSE_ACTION_PRESS, @"Focus" },
        { @"Left", @"TRCorner", OB_MOUSE_ACTION_PRESS, @"Focus" },
        { @"Left", @"Close", OB_MOUSE_ACTION_PRESS, @"Focus" },
        { @"Left", @"Maximize", OB_MOUSE_ACTION_PRESS, @"Focus" },
        { @"Left", @"Iconify", OB_MOUSE_ACTION_PRESS, @"Focus" },
        { @"Left", @"Icon", OB_MOUSE_ACTION_PRESS, @"Focus" },
        { @"Left", @"AllDesktops", OB_MOUSE_ACTION_PRESS, @"Focus" },
        { @"Left", @"Shade", OB_MOUSE_ACTION_PRESS, @"Focus" },
        { @"Left", @"Client", OB_MOUSE_ACTION_CLICK, @"Raise" },
        { @"Left", @"Titlebar", OB_MOUSE_ACTION_CLICK, @"Raise" },
        { @"Middle", @"Titlebar", OB_MOUSE_ACTION_CLICK, @"Lower" },
        { @"Left", @"Handle", OB_MOUSE_ACTION_CLICK, @"Raise" },
        { @"Left", @"BLCorner", OB_MOUSE_ACTION_CLICK, @"Raise" },
        { @"Left", @"BRCorner", OB_MOUSE_ACTION_CLICK, @"Raise" },
        { @"Left", @"TLCorner", OB_MOUSE_ACTION_CLICK, @"Raise" },
        { @"Left", @"TRCorner", OB_MOUSE_ACTION_CLICK, @"Raise" },
        { @"Left", @"Close", OB_MOUSE_ACTION_CLICK, @"Raise" },
        { @"Left", @"Maximize", OB_MOUSE_ACTION_CLICK, @"Raise" },
        { @"Left", @"Iconify", OB_MOUSE_ACTION_CLICK, @"Raise" },
        { @"Left", @"Icon", OB_MOUSE_ACTION_CLICK, @"Raise" },
        { @"Left", @"AllDesktops", OB_MOUSE_ACTION_CLICK, @"Raise" },
        { @"Left", @"Shade", OB_MOUSE_ACTION_CLICK, @"Raise" },
        { @"Left", @"Close", OB_MOUSE_ACTION_CLICK, @"Close" },
        { @"Left", @"Maximize", OB_MOUSE_ACTION_CLICK, @"ToggleMaximizeFull" },
        { @"Left", @"Iconify", OB_MOUSE_ACTION_CLICK, @"Iconify" },
        { @"Left", @"AllDesktops", OB_MOUSE_ACTION_CLICK, @"ToggleOmnipresent" },
        { @"Left", @"Shade", OB_MOUSE_ACTION_CLICK, @"ToggleShade" },
        { @"Left", @"TLCorner", OB_MOUSE_ACTION_MOTION, @"Resize" },
        { @"Left", @"TRCorner", OB_MOUSE_ACTION_MOTION, @"Resize" },
        { @"Left", @"BLCorner", OB_MOUSE_ACTION_MOTION, @"Resize" },
        { @"Left", @"BRCorner", OB_MOUSE_ACTION_MOTION, @"Resize" },
        { @"Left", @"Titlebar", OB_MOUSE_ACTION_MOTION, @"Move" },
        { @"A-Left", @"Frame", OB_MOUSE_ACTION_MOTION, @"Move" },
        { @"A-Middle", @"Frame", OB_MOUSE_ACTION_MOTION, @"Resize" },
        { nil, nil, 0, nil}
    };

    for (it = binds; it->button; ++it) {
        ObUserAction uact;
        switch (it->mact) {
        case OB_MOUSE_ACTION_PRESS:
            uact = OB_USER_ACTION_MOUSE_PRESS; break;
        case OB_MOUSE_ACTION_RELEASE:
            uact = OB_USER_ACTION_MOUSE_RELEASE; break;
        case OB_MOUSE_ACTION_CLICK:
            uact = OB_USER_ACTION_MOUSE_CLICK; break;
        case OB_MOUSE_ACTION_DOUBLE_CLICK:
            uact = OB_USER_ACTION_MOUSE_DOUBLE_CLICK; break;
        case OB_MOUSE_ACTION_MOTION:
            uact = OB_USER_ACTION_MOUSE_MOTION; break;
        case OB_NUM_MOUSE_ACTIONS:
            NSLog(@"Internal Error: should reach here");
        }
	[[AZMouseHandler defaultHandler] bind: it->button
		context: it->context mouseAction: it->mact
                   action: [AZAction actionWithName: it->actname userAction: uact]];
    }
}

void config_startup(AZParser *parser)
{
    config_focus_new = YES;
    config_focus_follow = NO;
    config_focus_delay = 0;
    config_focus_raise = NO;
    config_focus_last = NO;

    [parser registerTag: @"focus" callback: parse_focus data: NULL];

    config_place_policy = OB_PLACE_POLICY_SMART;

    [parser registerTag: @"placement" callback: parse_placement data: NULL];

    config_theme = nil;

    ASSIGN(config_title_layout, ([NSString stringWithCString: "NLIMC"]));
    config_theme_keepborder = YES;
    config_theme_hidedisabled = NO;

    [parser registerTag: @"theme" callback: parse_theme data: NULL];

    config_desktops_num = 4;
    config_screen_firstdesk = 1;
    config_desktops_names = nil;

    [parser registerTag: @"desktops" callback: parse_desktops data: NULL];

    config_resize_redraw = YES;
    config_resize_four_corners = NO;
    config_resize_popup_show = 1; /* nonpixel increments */
    config_resize_popup_pos = 0;  /* center of client */

    [parser registerTag: @"resize" callback: parse_resize data: NULL];

    config_dock_layer = OB_STACKING_LAYER_ABOVE;
    config_dock_pos = OB_DIRECTION_NORTHEAST;
    config_dock_floating = NO;
    config_dock_nostrut = NO;
    config_dock_x = 0;
    config_dock_y = 0;
    config_dock_orient = OB_ORIENTATION_VERT;
    config_dock_hide = NO;
    config_dock_hide_delay = 300;
    config_dock_show_delay = 300;
    config_dock_app_move_button = 2; /* middle */
    config_dock_app_move_modifiers = 0;

    [parser registerTag: @"dock" callback: parse_dock data: NULL];

    translate_key(@"C-g", &config_keyboard_reset_state,
                  &config_keyboard_reset_keycode);

    bind_default_keyboard();

    [parser registerTag: @"keyboard" callback: parse_keyboard data: NULL];

    config_mouse_threshold = 3;
    config_mouse_dclicktime = 200;

    bind_default_mouse();

    [parser registerTag: @"mouse" callback: parse_mouse data: NULL];

    config_resist_win = 10;
    config_resist_edge = 20;
    config_resist_layers_below = NO;

    [parser registerTag: @"resistance" callback: parse_resistance data: NULL];

    config_menu_warppointer = YES;
    config_menu_xorstyle = YES;
    config_menu_hide_delay = 250;
    config_menu_client_list_icons = YES;
    config_menu_files = nil;

    [parser registerTag: @"menu" callback: parse_menu data: NULL];
}

void config_shutdown()
{
    DESTROY(config_theme);
    DESTROY(config_title_layout);
    DESTROY(config_desktops_names);
    DESTROY(config_menu_files);
}
