/*
 * GToolKit - Objective-C interface to the GIMP Toolkit
 * Copyright (c) 1998  Elmar Ludwig - Universitaet Osnabrueck
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */
#include <GToolKit/GTKAction.h>
#include <GToolKit/GTK.h>

@implementation GTKAction

/*
 * Create an autoreleased GTKAction that will send /selector/ to /target/.
 * @see -performWithSender:args:count:
 */
+ actionWithTarget:_target selector:(SEL) _selector
{
    return [[[self alloc] initWithTarget:_target selector:_selector]
	    autorelease];
}

/*
 * Create an autoreleased GTKAction that will send /selector/ to /target/.
 * /data/ will be passed as an additional argument to the method.
 */
+ actionWithTarget:_target selector:(SEL) _selector data:(const void *) _data
{
    return [[[self alloc] initWithTarget:_target selector:_selector data:_data]
	    autorelease];
}

/*
 * Initialize a new GTKAction without /selector/ and /target/.
 * @see -setTarget:selector:
 */
- init
{
    return [self initWithTarget:nil selector:0];
}

/*
 * Initialize a new GTKAction that will send /selector/ to /target/.
 * @see -performWithSender:args:count:
 */
- initWithTarget:_target selector:(SEL) _selector
{
    return [self initWithTarget:_target selector:_selector data:nil];
}

/*
 * Initialize a new GTKAction that will send /selector/ to /target/.
 * /data/ will be passed as an additional argument to the method.
 */
- initWithTarget:_target selector:(SEL) _selector data:(const void *) _data
{
    [[super init] setTarget:_target selector:_selector];
    [self setData:_data];
    return self;
}

/*
 * Set the receiver's target and/or selector. If /target/ is |nil| or
 * /selector/ is |NULL|, the previous value is not changed.
 */
- (void) setTarget:_target selector:(SEL) _selector
{
    if (_target || _selector == 0) target = _target;
    if (_selector)
	selector = _selector, method = [target methodForSelector:_selector];
}

/*
 * Set the receiver's user data.
 */
- (void) setData:(const void *) _data
{
    data = (void *) _data;
}

/*
 * Return the current target of this action.
 */
- target
{
    return target;
}

/*
 * Return the current selector of this action.
 */
- (SEL) selector
{
    return selector;
}

/*
 * Return the current user data of this action.
 */
- (void *) data
{
    return data;
}

/*
 * Send the current selector to the target. The action will pass the
 * /sender/ object, /count/ arguments from the argument array starting at
 * /args/ and the user data (if it has been set) to the method. The return
 * value will be stored in args[count], which must contain the correct type
 * information for the return value.<p>
 * If the selector's return type is |void| and /count/ is |0|,
 * args may be |NULL|.
 * @see GTK#-connectSignal:withAction: in class @GTK
 */
- (void) performWithSender:sender args:(GtkArg *) args count:(unsigned) count
{
    int (*func)(id, SEL, ...) = (int (*)(id, SEL, ...)) method;
    int is_int[3];
    int num[3];
    void *ptr[3];
    int index;
    int result;

    // allow up to three arguments of type INT or POINTER
    for (index = 0; index < 3; ++index)
    {
	if (index < count)
	{
	    GtkType type = args[index].type;

#if SIZEOF_LONG != SIZEOF_INT && SIZEOF_LONG != SIZEOF_VOID_P
	    if (type == GTK_TYPE_LONG || type == GTK_TYPE_ULONG)
		g_warning("cannot pass GTK_TYPE_LONG on this machine");
#endif
	    is_int[index] = type == GTK_TYPE_INT  || type == GTK_TYPE_UINT  ||
#if SIZEOF_LONG == SIZEOF_INT
			    type == GTK_TYPE_LONG || type == GTK_TYPE_ULONG ||
#endif
			    type == GTK_TYPE_ENUM || type == GTK_TYPE_FLAGS;
	    num[index] = GTK_VALUE_INT(args[index]);
	    ptr[index] = gtk_type_is_a(type, GTK_TYPE_OBJECT) ?
			    Gtk_to_Object(GTK_VALUE_OBJECT(args[index]), 0) :
			    GTK_VALUE_POINTER(args[index]);
	}
	else is_int[index] = 0, num[index] = 0, ptr[index] = data; 
    }

    // method is always called with the maximum number of arguments,
    // this is OK because Objective-C uses the C calling convention
    result =
    (is_int[0] ?
	(is_int[1] ?
	    (is_int[2] ?
		func(target, selector, sender, num[0], num[1], num[2], data)
	     :
		func(target, selector, sender, num[0], num[1], ptr[2], data)
	    )
	 :
	    (is_int[2] ?
		func(target, selector, sender, num[0], ptr[1], num[2], data)
	     :
		func(target, selector, sender, num[0], ptr[1], ptr[2], data)
	    )
	)
     :
	(is_int[1] ?
	    (is_int[2] ?
		func(target, selector, sender, ptr[0], num[1], num[2], data)
	     :
		func(target, selector, sender, ptr[0], num[1], ptr[2], data)
	    )
	 :
	    (is_int[2] ?
		func(target, selector, sender, ptr[0], ptr[1], num[2], data)
	     :
		func(target, selector, sender, ptr[0], ptr[1], ptr[2], data)
	    )
	)
    );

    // return type may be VOID or INT
    if (args && args[count].type != GTK_TYPE_NONE)
	*GTK_RETLOC_INT(args[count]) = result;
}
@end
