//
// $Id: Functional.m,v 1.24 2005/05/18 06:46:52 will_mason Exp $
//
// vi: set ft=objc:

/*
 * ObjectiveLib - a library of containers and algorithms for Objective-C
 *
 * Copyright (c) 2004
 * Will Mason
 *
 * Portions:
 *
 * Copyright (c) 1994
 * Hewlett-Packard Company
 *
 * Copyright (c) 1996,1997
 * Silicon Graphics Computer Systems, Inc.
 *
 * Copyright (c) 1997
 * Moscow Center for SPARC Technology
 *
 * Copyright (c) 1999 
 * Boris Fomitchev
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser 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
 *
 * You may contact the author at will_mason@users.sourceforge.net.
 */

#import "Functional.h"
#import "Arithmetic.h"
#import "Constants.h"
#import "RunTime.h"
#if defined(GNUSTEP)
#import "Comparison.h"
#else
#import <Foundation/NSScriptWhoseTests.h>
#endif
#import <Foundation/NSCoder.h>
#import <string.h>

@implementation OLFunctor

+ (id) functorOfType: (OLFunctorType)type
{
    static BOOL initialized = NO;
    static Class typeClasses[OLFunctorType_Plus + 1];
    OLAllocator* alloc = [OLAllocator defaultAllocator];

    if (!initialized)
    {
        typeClasses[OLFunctorType_Divides] = [OLDivides class];
        typeClasses[OLFunctorType_EqualTo] = [OLEqualTo class];
        typeClasses[OLFunctorType_Greater] = [OLGreater class];
        typeClasses[OLFunctorType_GreaterEqual] = [OLGreaterEqual class];
        typeClasses[OLFunctorType_Less] = [OLLess class];
        typeClasses[OLFunctorType_LessEqual] = [OLLessEqual class];
        typeClasses[OLFunctorType_LogicalAnd] = [OLLogicalAnd class];
        typeClasses[OLFunctorType_LogicalNot] = [OLLogicalNot class];
        typeClasses[OLFunctorType_LogicalOr] = [OLLogicalOr class];
        typeClasses[OLFunctorType_Minus] = [OLMinus class];
        typeClasses[OLFunctorType_Modulus] = [OLModulus class];
        typeClasses[OLFunctorType_Multiplies] = [OLMultiplies class];
        typeClasses[OLFunctorType_Negate] = [OLNegate class];
        typeClasses[OLFunctorType_NotEqualTo] = [OLNotEqualTo class];
        typeClasses[OLFunctorType_Plus] = [OLPlus class];
        initialized = YES;
    }
    return [alloc autorelease:
        [[alloc allocateObject: typeClasses[type]] init]];
}

@end

@implementation OLCodableFunctor

- (id) initWithCoder: (NSCoder*)decoder
{
    return [super init];
}

- (void) encodeWithCoder: (NSCoder*)encoder
{
}

@end

@implementation OLAllocFunctor

- (id) init
{
    return [self initWithAllocator: [OLAllocator defaultAllocator] autorelease: YES];
}

- (id) initWithAllocator: (OLAllocator*)alloc
{
    return [self initWithAllocator: alloc autorelease: YES];
}

- (id) initWithAllocator: (OLAllocator*)alloc autorelease: (BOOL)autorel
{
    [super init];
    allocator = OBJ_RETAIN(alloc);
    autorelease = autorel;
    return self;
}

- (id) initWithAutorelease: (BOOL)autorel
{
    return [self initWithAllocator: [OLAllocator defaultAllocator] autorelease: autorel];
}

- (id) initWithCoder: (NSCoder*)decoder
{
    BOOL defaultAllocator;

    [super initWithCoder: decoder];
    if ([decoder respondsToSelector: @selector(allowsKeyedCoding)] &&
        [decoder allowsKeyedCoding])
    {
        allocator = OBJ_RETAIN([decoder decodeBoolForKey: IS_DEFAULT_ALLOCATOR_KEY] ?
            [OLAllocator defaultAllocator] :
            [decoder decodeObjectForKey: ALLOCATOR_KEY]);
        autorelease = [decoder decodeBoolForKey: AUTORELEASE_KEY];
    }
    else
    {
        [decoder decodeValueOfObjCType: @encode(BOOL) at: &defaultAllocator];
        allocator = OBJ_RETAIN(defaultAllocator ? [OLAllocator defaultAllocator] :
            [decoder decodeObject]);
        [decoder decodeValueOfObjCType: @encode(BOOL) at: &autorelease];
    }
    return self;
}

- (void) dealloc
{
    OBJ_RELEASE(allocator);
    [super dealloc];
}

- (BOOL) autoreleasesResults
{
    return autorelease;
}

- (void) encodeWithCoder: (NSCoder*)encoder
{
    BOOL isDefaultAllocator = [allocator isEqual: [OLAllocator defaultAllocator]];

    [super encodeWithCoder: encoder];
    if ([encoder respondsToSelector: @selector(allowsKeyedCoding)] &&
        [encoder allowsKeyedCoding])
    {
        [encoder encodeBool: isDefaultAllocator forKey: IS_DEFAULT_ALLOCATOR_KEY];
        if (!isDefaultAllocator)
            [encoder encodeObject: allocator forKey: ALLOCATOR_KEY];
        [encoder encodeBool: autorelease forKey: AUTORELEASE_KEY];
    }
    else
    {
        [encoder encodeValueOfObjCType: @encode(BOOL) at: &isDefaultAllocator];
        if (!isDefaultAllocator)
            [encoder encodeObject: allocator];
        [encoder encodeValueOfObjCType: @encode(BOOL) at: &autorelease];
    }
}

- (OLAllocator*) allocator
{
    return allocator;
}

- (void) setAllocator: (OLAllocator*)alloc
{
    if (allocator != alloc)
    {
        OBJ_RELEASE(allocator);
        allocator = OBJ_RETAIN(alloc);
    }
}

- (void) setAutoreleasesResults: (BOOL)autorel
{
    autorelease = autorel;
}

@end

@implementation OLBinaryNegate

+ (id) functorWithBinaryFunction: (OLCodableFunctor<OLBoolBinaryFunction>*)function
{
    OL_BEGIN_AUTO_CTOR(OLBinaryNegate)
        initWithBinaryFunction: function
    OL_END_AUTO_CTOR;
}

- (id) initWithCoder: (NSCoder*)decoder
{
    [super initWithCoder: decoder];
    if ([decoder respondsToSelector: @selector(allowsKeyedCoding)] &&
        [decoder allowsKeyedCoding])
    {
        fn = OBJ_RETAIN([decoder decodeObjectForKey: FUNCTION_KEY]);
    }
    else
    {
        fn = OBJ_RETAIN([decoder decodeObject]);
    }
    return self;
}

- (id) initWithBinaryFunction: (OLCodableFunctor<OLBoolBinaryFunction>*)function
{
    [super init];
    fn = OBJ_RETAIN(function);
    return self;
}

- (void) dealloc
{
    OBJ_RELEASE(fn);
    [super dealloc];
}

- (void) encodeWithCoder: (NSCoder*)encoder
{
    [super encodeWithCoder: encoder];
    if ([encoder respondsToSelector: @selector(allowsKeyedCoding)] &&
        [encoder allowsKeyedCoding])
    {
        [encoder encodeObject: fn forKey: FUNCTION_KEY];
    }
    else
    {
        [encoder encodeObject: fn];
    }
}

- (BOOL) performBinaryFunctionWithArg: (id)arg1 andArg: (id)arg2
{
    return [fn performBinaryFunctionWithArg: arg1 andArg: arg2] ? NO : YES;
}

@end

@implementation OLBinder1st

+ (id) functorWithFunction: (OLAllocFunctor<OLBinaryFunction>*)function andLeftArg: (id)left
{
    OL_BEGIN_AUTO_CTOR(OLBinder1st)
        initWithFunction: function andLeftArg: left
    OL_END_AUTO_CTOR;
}

- (id) initWithCoder: (NSCoder*)decoder
{
    [super initWithCoder: decoder];
    if ([decoder respondsToSelector: @selector(allowsKeyedCoding)] &&
        [decoder allowsKeyedCoding])
    {
        fn = OBJ_RETAIN([decoder decodeObjectForKey: FUNCTION_KEY]);
        lft = OBJ_RETAIN([decoder decodeObjectForKey: ARGUMENT_KEY]);
    }
    else
    {
        fn = OBJ_RETAIN([decoder decodeObject]);
        lft = OBJ_RETAIN([decoder decodeObject]);
    }
    return self;
}

- (id) initWithFunction: (OLAllocFunctor<OLBinaryFunction>*)function andLeftArg: (id)left
{
    [super initWithAllocator: [function allocator]];
    fn = OBJ_RETAIN(function);
    lft = OBJ_RETAIN(left);
    return self;
}

- (void) dealloc
{
    OBJ_RELEASE(fn);
    OBJ_RELEASE(lft);
    [super dealloc];
}

- (void) encodeWithCoder: (NSCoder*)encoder
{
    [super encodeWithCoder: encoder];
    if ([encoder respondsToSelector: @selector(allowsKeyedCoding)] &&
        [encoder allowsKeyedCoding])
    {
        [encoder encodeObject: fn forKey: FUNCTION_KEY];
        [encoder encodeObject: lft forKey: ARGUMENT_KEY];
    }
    else
    {
        [encoder encodeObject: fn];
        [encoder encodeObject: lft];
    }
}

- (id) performUnaryFunctionWithArg: (id)arg
{
    return [fn performBinaryFunctionWithArg: lft andArg: arg];
}

@end

@implementation OLBinder2nd

+ (id) functorWithFunction: (OLAllocFunctor<OLBinaryFunction>*)function andRightArg: (id)right
{
    OL_BEGIN_AUTO_CTOR(OLBinder2nd)
        initWithFunction: function andRightArg: right
    OL_END_AUTO_CTOR;
}

- (id) initWithCoder: (NSCoder*)decoder
{
    [super initWithCoder: decoder];
    if ([decoder respondsToSelector: @selector(allowsKeyedCoding)] &&
        [decoder allowsKeyedCoding])
    {
        fn = OBJ_RETAIN([decoder decodeObjectForKey: FUNCTION_KEY]);
        rght = OBJ_RETAIN([decoder decodeObjectForKey: ARGUMENT_KEY]);
    }
    else
    {
        fn = OBJ_RETAIN([decoder decodeObject]);
        rght = OBJ_RETAIN([decoder decodeObject]);
    }
    return self;
}

- (id) initWithFunction: (OLAllocFunctor<OLBinaryFunction>*)function andRightArg: (id)right
{
    [super initWithAllocator: [function allocator]];
    fn = OBJ_RETAIN(function);
    rght = OBJ_RETAIN(right);
    return self;
}

- (void) dealloc
{
    OBJ_RELEASE(fn);
    OBJ_RELEASE(rght);
    [super dealloc];
}

- (void) encodeWithCoder: (NSCoder*)encoder
{
    [super encodeWithCoder: encoder];
    if ([encoder respondsToSelector: @selector(allowsKeyedCoding)] &&
        [encoder allowsKeyedCoding])
    {
        [encoder encodeObject: fn forKey: FUNCTION_KEY];
        [encoder encodeObject: rght forKey: ARGUMENT_KEY];
    }
    else
    {
        [encoder encodeObject: fn];
        [encoder encodeObject: rght];
    }
}

- (id) performUnaryFunctionWithArg: (id)arg
{
    return [fn performBinaryFunctionWithArg: arg andArg: rght];
}

@end

@implementation OLBoolBinder1st

+ (id) functorWithBoolFunction: (OLCodableFunctor<OLBoolBinaryFunction>*)function andLeftArg: (id)left
{
    OL_BEGIN_AUTO_CTOR(OLBoolBinder1st)
        initWithBoolFunction: function andLeftArg: left
    OL_END_AUTO_CTOR;
}

- (id) initWithBoolFunction: (OLCodableFunctor<OLBoolBinaryFunction>*)function andLeftArg: (id)left
{
    [super init];
    fn = OBJ_RETAIN(function);
    lft = OBJ_RETAIN(left);
    return self;
}

- (id) initWithCoder: (NSCoder*)decoder
{
    [super initWithCoder: decoder];
    if ([decoder respondsToSelector: @selector(allowsKeyedCoding)] &&
        [decoder allowsKeyedCoding])
    {
        fn = OBJ_RETAIN([decoder decodeObjectForKey: FUNCTION_KEY]);
        lft = OBJ_RETAIN([decoder decodeObjectForKey: ARGUMENT_KEY]);
    }
    else
    {
        fn = OBJ_RETAIN([decoder decodeObject]);
        lft = OBJ_RETAIN([decoder decodeObject]);
    }
    return self;
}

- (void) dealloc
{
    OBJ_RELEASE(fn);
    OBJ_RELEASE(lft);
    [super dealloc];
}

- (void) encodeWithCoder: (NSCoder*)encoder
{
    [super encodeWithCoder: encoder];
    if ([encoder respondsToSelector: @selector(allowsKeyedCoding)] &&
        [encoder allowsKeyedCoding])
    {
        [encoder encodeObject: fn forKey: FUNCTION_KEY];
        [encoder encodeObject: lft forKey: ARGUMENT_KEY];
    }
    else
    {
        [encoder encodeObject: fn];
        [encoder encodeObject: lft];
    }
}

- (BOOL) performUnaryFunctionWithArg: (id)arg
{
    return [fn performBinaryFunctionWithArg: lft andArg: arg];
}

@end

@implementation OLBoolBinder2nd

+ (id) functorWithBoolFunction: (OLCodableFunctor<OLBoolBinaryFunction>*)function andRightArg: (id)right
{
    OL_BEGIN_AUTO_CTOR(OLBoolBinder2nd)
        initWithBoolFunction: function andRightArg: right
    OL_END_AUTO_CTOR;
}

- (id) initWithBoolFunction: (OLCodableFunctor<OLBoolBinaryFunction>*)function andRightArg: (id)right
{
    [super init];
    fn = OBJ_RETAIN(function);
    rght = OBJ_RETAIN(right);
    return self;
}

- (id) initWithCoder: (NSCoder*)decoder
{
    [super initWithCoder: decoder];
    if ([decoder respondsToSelector: @selector(allowsKeyedCoding)] &&
        [decoder allowsKeyedCoding])
    {
        fn = OBJ_RETAIN([decoder decodeObjectForKey: FUNCTION_KEY]);
        rght = OBJ_RETAIN([decoder decodeObjectForKey: ARGUMENT_KEY]);
    }
    else
    {
        fn = OBJ_RETAIN([decoder decodeObject]);
        rght = OBJ_RETAIN([decoder decodeObject]);
    }
    return self;
}

- (void) dealloc
{
    OBJ_RELEASE(fn);
    OBJ_RELEASE(rght);
    [super dealloc];
}

- (void) encodeWithCoder: (NSCoder*)encoder
{
    [super encodeWithCoder: encoder];
    if ([encoder respondsToSelector: @selector(allowsKeyedCoding)] &&
        [encoder allowsKeyedCoding])
    {
        [encoder encodeObject: fn forKey: FUNCTION_KEY];
        [encoder encodeObject: rght forKey: ARGUMENT_KEY];
    }
    else
    {
        [encoder encodeObject: fn];
        [encoder encodeObject: rght];
    }
}

- (BOOL) performUnaryFunctionWithArg: (id)arg
{
    return [fn performBinaryFunctionWithArg: arg andArg: rght];
}

@end

@implementation OLDivides

- (id) performBinaryFunctionWithArg: (id)arg1 andArg: (id)arg2
{
    id rc = [arg1 arithmeticDivideBy: arg2 allocator: allocator];

    return autorelease ? [allocator autorelease: rc] : rc;
}

@end

@implementation OLEqualTo

- (BOOL) performBinaryFunctionWithArg: (id)arg1 andArg: (id)arg2
{
    return [arg1 isEqualTo: arg2];
}

@end

@implementation OLGreater

- (BOOL) performBinaryFunctionWithArg: (id)arg1 andArg: (id)arg2
{
    return [arg1 isGreaterThan: arg2];
}

@end

@implementation OLGreaterEqual

- (BOOL) performBinaryFunctionWithArg: (id)arg1 andArg: (id)arg2
{
    return [arg1 isGreaterThanOrEqualTo: arg2];
}

@end

@implementation OLLess

- (BOOL) performBinaryFunctionWithArg: (id)arg1 andArg: (id)arg2
{
    return [arg1 isLessThan: arg2];
}

@end

@implementation OLLessEqual

- (BOOL) performBinaryFunctionWithArg: (id)arg1 andArg: (id)arg2
{
    return [arg1 isLessThanOrEqualTo: arg2];
}

@end

@implementation OLLogicalAnd

- (BOOL) performBinaryFunctionWithArg: (id)arg1 andArg: (id)arg2
{
    return [arg1 boolValue] && [arg2 boolValue] ? YES : NO;
}

@end

@implementation OLLogicalNot

- (BOOL) performUnaryFunctionWithArg: (id)arg
{
    return ![arg boolValue] ? YES : NO;
}

@end

@implementation OLLogicalOr

- (BOOL) performBinaryFunctionWithArg: (id)arg1 andArg: (id)arg2
{
    return [arg1 boolValue] || [arg2 boolValue] ? YES : NO;
}

@end

@implementation OLMemFun

+ (id) functorWithSelector: (SEL)selector
{
    OL_BEGIN_AUTO_CTOR(OLMemFun)
        initWithSelector: selector
    OL_END_AUTO_CTOR;
}

- (id) initWithCoder: (NSCoder*)decoder
{
    [super initWithCoder: decoder];
    [decoder decodeValueOfObjCType: @encode(SEL) at: &sel];
    return self;
}

- (id) initWithSelector: (SEL)selector
{
    [super init];
    sel = selector;
    return self;
}

- (void) encodeWithCoder: (NSCoder*)encoder
{
    [super encodeWithCoder: encoder];
    [encoder encodeValueOfObjCType: @encode(SEL) at: &sel];
}

- (id) performUnaryFunctionWithArg: (id)arg
{
    return [arg performSelector: sel];
}

@end

@implementation OLMemFun1

+ (id) functorWithSelector: (SEL)selector
{
    OL_BEGIN_AUTO_CTOR(OLMemFun1)
        initWithSelector: selector
    OL_END_AUTO_CTOR;
}

- (id) initWithCoder: (NSCoder*)decoder
{
    [super initWithCoder: decoder];
    [decoder decodeValueOfObjCType: @encode(SEL) at: &sel];
    return self;
}

- (id) initWithSelector: (SEL)selector
{
    [super init];
    sel = selector;
    return self;
}

- (void) encodeWithCoder: (NSCoder*)encoder
{
    [super encodeWithCoder: encoder];
    [encoder encodeValueOfObjCType: @encode(SEL) at: &sel];
}

- (id) performBinaryFunctionWithArg: (id)arg1 andArg: (id)arg2
{
    return [arg1 performSelector: sel withObject: arg2];
}

@end

@implementation OLMinus

- (id) performBinaryFunctionWithArg: (id)arg1 andArg: (id)arg2
{
    id rc = [arg1 arithmeticSubtract: arg2 allocator: allocator];

    return autorelease ? [allocator autorelease: rc] : rc;
}

@end

@implementation OLModulus

- (id) performBinaryFunctionWithArg: (id)arg1 andArg: (id)arg2
{
    id rc = [arg1 arithmeticModulus: arg2 allocator: allocator];

    return autorelease ? [allocator autorelease: rc] : rc;
}

@end

@implementation OLMultiplies

- (id) performBinaryFunctionWithArg: (id)arg1 andArg: (id)arg2
{
    id rc = [arg1 arithmeticMultiply: arg2 allocator: allocator];

    return autorelease ? [allocator autorelease: rc] : rc;
}

@end

@implementation OLNegate

- (id) performUnaryFunctionWithArg: (id)arg
{
    id rc = [arg arithmeticNegateWithAllocator: allocator];

    return autorelease ? [allocator autorelease: rc] : rc;
}

@end

@implementation OLNotEqualTo

- (BOOL) performBinaryFunctionWithArg: (id)arg1 andArg: (id)arg2
{
    return [arg1 isNotEqualTo: arg2];
}

@end

@implementation OLPlus

- (id) performBinaryFunctionWithArg: (id)arg1 andArg: (id)arg2
{
    id rc = [arg1 arithmeticAdd: arg2 allocator: allocator];

    return autorelease ? [allocator autorelease: rc] : rc;
}

@end

@implementation OLUnaryNegate

+ (id) functorWithUnaryFunction: (OLCodableFunctor<OLBoolUnaryFunction>*)function
{
    OL_BEGIN_AUTO_CTOR(OLUnaryNegate)
        initWithUnaryFunction: function
    OL_END_AUTO_CTOR;
}

- (id) initWithCoder: (NSCoder*)decoder
{
    [super initWithCoder: decoder];
    if ([decoder respondsToSelector: @selector(allowsKeyedCoding)] &&
        [decoder allowsKeyedCoding])
    {
        fn = OBJ_RETAIN([decoder decodeObjectForKey: FUNCTION_KEY]);
    }
    else
    {
        fn = OBJ_RETAIN([decoder decodeObject]);
    }
    return self;
}

- (id) initWithUnaryFunction: (OLCodableFunctor<OLBoolUnaryFunction>*)function
{
    [super init];
    fn = OBJ_RETAIN(function);
    return self;
}

- (void) dealloc
{
    OBJ_RELEASE(fn);
    [super dealloc];
}

- (void) encodeWithCoder: (NSCoder*)encoder
{
    [super encodeWithCoder: encoder];
    if ([encoder respondsToSelector: @selector(allowsKeyedCoding)] &&
        [encoder allowsKeyedCoding])
    {
        [encoder encodeObject: fn forKey: FUNCTION_KEY];
    }
    else
    {
        [encoder encodeObject: fn];
    }
}

- (BOOL) performUnaryFunctionWithArg: (id)arg
{
    return [fn performUnaryFunctionWithArg: arg] ? NO : YES;
}

@end
