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

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

   menu.c for the Openbox window manager
   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 "AZMenuManager.h"
#import "AZScreen.h"
#import "AZClient.h"
#import "AZClientManager.h"
#import "AZMenuFrame.h"
#import "AZKeyboardHandler.h"
#import "openbox.h"
#import "config.h"
#import "geom.h"
#import "misc.h"
#import "client_menu.h"
#import "client_list_menu.h"
#import "parse.h"
#import "action.h"

typedef struct _ObMenuParseState ObMenuParseState;

struct _ObMenuParseState
{
    AZMenu *parent;
    AZMenu *pipe_creator;
};

static AZParser *menu_parse_inst = nil;
static ObMenuParseState menu_parse_state;

//static void menu_destroy_hash_value(AZMenu *self);
static void parse_menu_item(AZParser *parser, xmlDocPtr doc, xmlNodePtr node,
                            void * data);
static void parse_menu_separator(AZParser *parser,
                                 xmlDocPtr doc, xmlNodePtr node,
                                 void * data);
static void parse_menu(AZParser *parser, xmlDocPtr doc, xmlNodePtr node,
                       void *data);

void menu_pipe_execute(AZMenu *self)
{
    xmlDocPtr doc;
    xmlNodePtr node;
    const char *output;

    if (![self execute])
        return;

    NSString *command;
    NSArray *com = [[self execute] componentsSeparatedByString: @" "];
    NSArray *args = nil;
    if ([com count] > 1) {
      args = [com subarrayWithRange: NSMakeRange(1, [com count]-1)];
      command = [com objectAtIndex: 0];

    }
    else
    {
      command = [self execute];
    }
    NSPipe *pipe = [NSPipe pipe];
    NSTask *task = [[NSTask alloc] init];
    [task setStandardOutput: pipe];
    [task setLaunchPath: command];
    if (args)
      [task setArguments: args];
    [task launch];
    NSFileHandle *f_output = [pipe fileHandleForReading];
    NSData *data = [f_output readDataToEndOfFile];
    NSString *s = [[NSString alloc] initWithData: data encoding: [NSString defaultCStringEncoding]];
    output = [s UTF8String];
    AUTORELEASE(task);

    if (parse_load_mem((char*)output, strlen(output),
                       "openbox_pipe_menu", &doc, &node))
    {
	[[AZMenuManager defaultManager] removePipeMenu: self];
	[self clearEntries];

        menu_parse_state.pipe_creator = self;
        menu_parse_state.parent = self;
	[menu_parse_inst parseDocument: doc node: node->children];
        xmlFreeDoc(doc);
    } else {
        NSLog(@"Warning: Invalid output from pipe-menu: %@", [self execute]);
    }
}

static void parse_menu_item(AZParser *parser, xmlDocPtr doc, xmlNodePtr node,
                            void * data)
{
    ObMenuParseState *state = data;
    NSString *label;
    
    if (state->parent) {
        if (parse_attr_string("label", node, &label)) {
	    NSMutableArray *acts = [[NSMutableArray alloc] init];

            for (node = node->children; node; node = node->next)
                if (!xmlStrcasecmp(node->name, (const xmlChar*) "action")) {
                    AZAction *a = action_parse
                        (doc, node, OB_USER_ACTION_MENU_SELECTION);
                    if (a)
			[acts addObject: a];
                }
	    [state->parent addNormalMenuEntry: -1 label: label actions: acts];
	    DESTROY(acts);
        }
    }
}

static void parse_menu_separator(AZParser *parser,
                                 xmlDocPtr doc, xmlNodePtr node,
                                 void * data)
{
    ObMenuParseState *state = data;

    if (state->parent)
	[state->parent addSeparatorMenuEntry: -1];
}

static void parse_menu(AZParser *parser, xmlDocPtr doc, xmlNodePtr node,
                       void * data)
{
    ObMenuParseState *state = data;
    NSString *name = nil, *title = nil, *script = nil;
    AZMenu *menu;

    if (!parse_attr_string("id", node, &name))
	return;

    if (![[AZMenuManager defaultManager] menuWithName: name]) {
        if (!parse_attr_string("label", node, &title))
	    return;

        if ((menu = [[AZMenu alloc] initWithName: name title: title])) {
            [menu set_pipe_creator: state->pipe_creator];
            if (parse_attr_string("execute", node, &script)) {
                [menu set_execute: [script stringByExpandingTildeInPath]];
            } else {
                AZMenu *old;

                old = state->parent;
                state->parent = menu;
		[parser parseDocument: doc node: node->children];
                state->parent = old;
            }
	    [[AZMenuManager defaultManager] registerMenu: menu];
	    DESTROY(menu);
        }
    }

    if (state->parent)
	[state->parent addSubmenuMenuEntry: -1 submenu: name];
}

#if 0
static void menu_destroy_hash_value(AZMenu *self)
{
    /* make sure its not visible */
    {
	NSArray *visibles = [AZMenuFrame visibleFrames];
	int i, count = [visibles count];
        AZMenuFrame *f;

	for (i = 0; i < count; i++) {
	    f = [visibles objectAtIndex: i];
            if ([f menu] == self)
		AZMenuFrameHideAll();
        }
    }

    DESTROY(self);
}
#endif

static AZMenuManager *sharedInstance;

@implementation AZMenuManager

+ (AZMenuManager *) defaultManager
{
  if (sharedInstance == nil)
    sharedInstance = [[AZMenuManager alloc] init];
  return sharedInstance; 
}

- (void) startup: (BOOL) reconfig
{
    xmlDocPtr doc;
    xmlNodePtr node;
    BOOL loaded = NO;
    int i, count;

    menu_hash = [[NSMutableDictionary alloc] init];

    client_list_menu_startup(reconfig);
    client_menu_startup();

    menu_parse_inst = [[AZParser alloc] init];

    menu_parse_state.parent = nil;
    menu_parse_state.pipe_creator = nil;
    [menu_parse_inst registerTag: @"menu" callback: parse_menu 
	                 data: &menu_parse_state];
    [menu_parse_inst registerTag: @"item" callback: parse_menu_item 
	                 data: &menu_parse_state];
    [menu_parse_inst registerTag: @"separator" callback: parse_menu_separator
	                 data: &menu_parse_state];

    count = [config_menu_files count];
    for (i = 0; i < count; i++) {
	if (parse_load_menu([config_menu_files objectAtIndex: i],
				&doc, &node))
	{
            loaded = YES;
	    [menu_parse_inst parseDocument: doc node: node->children];
            xmlFreeDoc(doc);
        }
    }
    if (!loaded) {
        if (parse_load_menu(@"menu.xml", &doc, &node)) {
	    [menu_parse_inst parseDocument: doc node: node->children];
            xmlFreeDoc(doc);
        }
    }
    
    NSAssert(menu_parse_state.parent == nil, @"menu_parse_state.parent is not nil");
}

- (void) shutdown: (BOOL) reconfig
{
    DESTROY(menu_parse_inst);
    
    client_list_menu_shutdown(reconfig);

    AZMenuFrameHideAll();
    DESTROY(menu_hash);
}

- (AZMenu *) menuWithName: (NSString *) name
{
    AZMenu *menu = nil;

    NSAssert(name != nil, @"menuWithName: cannot take 'nil'.");

    if (!(menu = [menu_hash objectForKey: name]))
	NSLog(@"Warning: Attemped to access menu '%@' but it does not exist.", name);
    return menu;
}  

- (void) removeMenu: (AZMenu *) menu
{
  /* make sure its not visible */
  {
    NSArray *visibles = [AZMenuFrame visibleFrames];
    int i, count = [visibles count];
    AZMenuFrame *f;

    for (i = 0; i < count; i++) {
      f = [visibles objectAtIndex: i];
      if ([f menu] == menu)
        AZMenuFrameHideAll();
    }
  }

  [menu_hash removeObjectForKey: [menu name]];
}

- (void) removePipeMenu: (AZMenu *) pipe_menu
{
  NSArray *keys = [menu_hash allKeys];
  int i, count = [keys count];
  for (i = 0; i < count; i++) {
     AZMenu *m = [menu_hash objectForKey: [keys objectAtIndex: i]];
     if ([m pipe_creator] == pipe_menu) {
       [self removeMenu: m];
     }
  }
}

- (void) showMenu: (NSString *) name x: (int) x y: (int) y 
	   client: (AZClient *) client
{
    AZMenu *menu;
    AZMenuFrame *frame;
    unsigned int i;

    if (!(menu = [self menuWithName: name])
	|| [[AZKeyboardHandler defaultHandler] interactivelyGrabbed]) return;

    /* if the requested menu is already the top visible menu, then don't
       bother */
    NSArray *visibles = [AZMenuFrame visibleFrames];
    if ([visibles count]) {
	frame = [visibles objectAtIndex: 0];
        if ([frame menu] == menu)
            return;
    }

    AZMenuFrameHideAll();

    frame = [[AZMenuFrame alloc] initWithMenu: menu client: client];
    if (client && x < 0 && y < 0) {
        x = [[client frame] area].x + [[client frame] size].left;
        y = [[client frame] area].y + [[client frame] size].top;
	[frame moveToX: x y: y];
    } else
	[frame moveToX: x - ob_rr_theme->bwidth y: y - ob_rr_theme->bwidth];
    AZScreen *screen = [AZScreen defaultScreen];
    for (i = 0; i < [screen numberOfMonitors]; ++i) {
        Rect *a = [screen physicalAreaOfMonitor: i];
        if (RECT_CONTAINS(*a, x, y)) {
	    [frame set_monitor: i];
            break;
        }
    }
    if (![frame showWithParent: nil])
	DESTROY(frame);
#if 0 
    /* I don't think it is a good idea to select the first non-submenu 
     * automatically because it is not a consistent user interface behavior.
     */
    else if ([[frame entries] count]) {
      AZMenuEntryFrame *e = [[frame entries] objectAtIndex: 0];
      if ([[e entry] type] == OB_MENU_ENTRY_TYPE_NORMAL &&
	   [(AZNormalMenuEntry*)[e entry] enabled]) {
	[frame selectNext];
      }
    }
#endif
}

- (void) registerMenu: (AZMenu *) menu
{
  AZMenu *m = nil;
  if ((m = [menu_hash objectForKey: [menu name]]))  {
    [self removeMenu: m];
  }
  [menu_hash setObject: menu forKey: [menu name]];
}

@end

