#import "OAExpressionOperations.h"
#import "OAExpressionToken.h"
#import "OAMacros.h"
#import "OACategories.h"

#pragma mark -
#pragma mark Operator Table

struct OAOperatorInfo {
    NSString *text;
    int kind;
    int priority;
    int category;
};

#define STANDARDMATH    1
#define DUNGEONSDRAGONS 2

struct OAOperatorInfo operatorTable[] = {
    { @"-", OAEXPRUNARYOP|OAEXPRBINARYOP, 10, STANDARDMATH},
    { @"+", OAEXPRBINARYOP, 10, STANDARDMATH},
    { @"*", OAEXPRBINARYOP, 20, STANDARDMATH},
    { @"/", OAEXPRBINARYOP, 20, STANDARDMATH},
    { @"%", OAEXPRBINARYOP, 20, STANDARDMATH},
    { @"d", OAEXPRBINARYOP, 30, DUNGEONSDRAGONS},
    { @"D", OAEXPRBINARYOP, 30, DUNGEONSDRAGONS},
    { nil, 0, 0}
};

#pragma mark -
#pragma mark Interfaces for non-abstract subclasses

@interface OAExpressionOperations (AbstractPrivate)

- createValueFor:(NSString*)number;

@end

@interface OANumberExpressionOperations : OAExpressionOperations
- createValueFor:(NSString*)number;
- apply:(OAExpressionToken*)op to:(NSNumber*)value;
- apply:(OAExpressionToken*)op left:(NSNumber*)lvalue right:(NSNumber*)rvalue;
@end

@interface OADecimalNumberExpressionOperations : OAExpressionOperations
- createValueFor:(NSString*)number;
- apply:(OAExpressionToken*)op to:(NSDecimalNumber*)value;
- apply:(OAExpressionToken*)op left:(NSDecimalNumber*)lvalue right:(NSDecimalNumber*)rvalue;
@end

#pragma mark -
#pragma mark Tokenizer

@implementation OAExpressionOperations

+ (OAExpressionOperations*) operationsOnNSNumber
{
    return [[[OANumberExpressionOperations alloc] init] autorelease];
}

+ (OAExpressionOperations*) operationsOnNSDecimalNumber
{
    return [[[OADecimalNumberExpressionOperations alloc] init] autorelease];
}

- initWithOperatorCategory:(int)category
{
    if ((self = [super init])) {
        m_operators = [[NSMutableArray alloc] init];
        for (struct OAOperatorInfo *p = operatorTable; p->text != nil; p++) {
            if (p->category & category) {
                [m_operators addObject:
                    [[[OAExpressionToken alloc] initWithText:p->text 
                                                   tokenKind:p->kind 
                                                    priority:p->priority
                                                       value:nil] autorelease]];
            }
        }
    }
    
    return self;
}

- init
{
    return [self initWithOperatorCategory:INT_MAX]; // all categories by default
}

- (void) dealloc 
{
    [m_operators release];
    [super dealloc];
}

- (OAExpressionToken*) parseNumber:(NSString*)string
{
    NSCharacterSet *digits = [NSCharacterSet decimalDigitCharacterSet];
    NSCharacterSet *decimalSep = [NSCharacterSet characterSetWithCharactersInString:@"."]; // HACK
    BOOL seenDot=NO;
    int i=0;
    
    for (; i < [string length]; i++) {
        unichar c = [string characterAtIndex:i];
        if ([decimalSep characterIsMember:c]) {
            if (seenDot) {
                // Numbers can only have one decimal separator.
                // This is illegal, just return nil.
                return nil;
            }
            seenDot = YES;
        }
        else if (![digits characterIsMember:c]) {
            // This is the end of the number.
            break;
        }
    }
    
    NSString *numberValue = [string substringToIndex:i];
    id value = [self createValueFor:numberValue];
    return [[[OAExpressionToken alloc] initWithText:numberValue 
                                          tokenKind:OAEXPRVALUE 
                                           priority:0
                                              value:value] autorelease];
}

- (OAExpressionToken*) tokenize:(NSString*)string 
{
    NSCharacterSet *digits = [NSCharacterSet decimalDigitCharacterSet];
    unichar c = [string characterAtIndex:0];
    
    // Identify numbers:
    // HACK: make digit separator local sensitive!!
    if ([digits characterIsMember:c] || c == '.') {
        return [self parseNumber:string];
    }
    
    // Identify operators:
    FOREACH(OAExpressionToken *, token, m_operators) {
        if ([string hasPrefix:[token text]]) {
            return token;
        }
    } ENDFOREACH;
    
    // No recognizable token: return nil
    return nil;
}

- apply:(OAExpressionToken*)op to:value
{
    NSAssert(NO, @"Abstract method");
    return nil;
}

- apply:(OAExpressionToken*)op left:lvalue right:rvalue
{
    NSAssert(NO, @"Abstract method");
    return nil;
}

@end

#pragma mark -
#pragma mark Operator Implementation

// TODO --- rewrite this to add more generic categories to NSNumber and
// NSDecimalNumber so that we can just do "numberByAddingNumber:" and
// have it do the right thing.  Then we could add a SEL to the
// table above and have this code below be much more general.

@implementation OANumberExpressionOperations

- apply:(OAExpressionToken*)op to:(NSNumber*)value
{
    if ([@"-" isEqualToString:[op text]]) {
        return [NSNumber numberWithDouble:-[value doubleValue]];
    }
    else if ([@"+" isEqualToString:[op text]]) {
        return value;
    }
    
    // Invalid operator
    NSAssert1(NO, @"Illegal operator: %@", [op text]);
    return nil;
}

- apply:(OAExpressionToken*)op left:(NSNumber*)lvalue right:(NSNumber*)rvalue
{
    if ([@"-" isEqualToString:[op text]]) {
        return [NSNumber numberWithDouble:[lvalue doubleValue] - [rvalue doubleValue]];
    }
    else if ([@"+" isEqualToString:[op text]]) {
        return [NSNumber numberWithDouble:[lvalue doubleValue] + [rvalue doubleValue]];
    }
    else if ([@"*" isEqualToString:[op text]]) {
        return [NSNumber numberWithDouble:[lvalue doubleValue] * [rvalue doubleValue]];
    }
    else if ([@"/" isEqualToString:[op text]]) {
        return [NSNumber numberWithDouble:[lvalue doubleValue] / [rvalue doubleValue]];
    }
    else if ([@"d" isEqualToStringIgnoringCase:[op text]]) {
        // TODO
    }
    
    // Invalid operator
    NSAssert1(NO, @"Illegal operator: %@", [op text]);
    return nil;
}

- createValueFor:(NSString*)number
{
    NSNumberFormatter *formatter = [[[NSNumberFormatter alloc] init] autorelease];
    return [formatter numberFromString:number];
}


@end

@implementation OADecimalNumberExpressionOperations
- apply:(OAExpressionToken*)op to:(NSDecimalNumber*)value
{
    if ([@"-" isEqualToString:[op text]]) {
        return [value decimalNumberByMultiplyingBy:[NSDecimalNumber negativeOne]];
    }
    else if ([@"+" isEqualToString:[op text]]) {
        return value;
    }
    
    // Invalid operator
    NSAssert1(NO, @"Illegal operator: %@", [op text]);
    return nil;
}

- apply:(OAExpressionToken*)op left:(NSDecimalNumber*)lvalue right:(NSDecimalNumber*)rvalue
{
    if ([@"-" isEqualToString:[op text]]) {
        return [lvalue decimalNumberBySubtracting:rvalue];
    }
    else if ([@"+" isEqualToString:[op text]]) {
        return [lvalue decimalNumberByAdding:rvalue];
    }
    else if ([@"*" isEqualToString:[op text]]) {
        return [lvalue decimalNumberByMultiplyingBy:rvalue];
    }
    else if ([@"/" isEqualToString:[op text]]) {
        return [lvalue decimalNumberByDividingBy:rvalue];
    }
    else if ([@"d" isEqualToStringIgnoringCase:[op text]]) {
        // TODO
    }
    
    // Invalid operator
    NSAssert1(NO, @"Illegal operator: %@", [op text]);
    return nil;
}

- createValueFor:(NSString*)number
{
    return [[[NSDecimalNumber alloc] initWithString:number] autorelease];
}

@end

