//
//  MPElementParser.m
//  MathPad
//
//  Created by Kim Wittenburg on 06.09.14.
//  Copyright (c) 2014 Kim Wittenburg. All rights reserved.
//

#import "MPElementParser.h"

@implementation MPElementParser {
    NSScanner *scanner;
}

- (MPParsedElement *)parseElement:(NSString *)string error:(MPParseError *__autoreleasing *)error
{
    // Setup the Scanner
    scanner = [[NSScanner alloc] initWithString:string];
    scanner.charactersToBeSkipped = [NSCharacterSet whitespaceAndNewlineCharacterSet];
    scanner.caseSensitive = YES;
    
    MPParsedElement *parsedElement = [[MPParsedElement alloc] init];
    
    // Scan the defined variable
    NSString *firstCharacters = nil;
    BOOL hasLettersInFront = [scanner scanCharactersFromSet:[NSCharacterSet letterCharacterSet]
                                                 intoString:&firstCharacters];
    BOOL foundEquals = [scanner scanString:@"="
                                intoString:NULL];
    
    if (hasLettersInFront && firstCharacters.length == 1 && foundEquals) {
        // Found variable definition
        parsedElement.definedVariable = firstCharacters;
        parsedElement.afterVariableDefinitionIndex = scanner.scanLocation;
    } else {
        // No variable definition found, reset the scanner
        parsedElement.definedVariable = nil;
        scanner.scanLocation = 0;
    }
    
    // Scan the Prefix
    BOOL startsWithAsterisk = [scanner scanString:@"*"
                                       intoString:NULL];
    BOOL productClosed;
    NSDecimalNumber *prefix = [self scanClosedProduct:&productClosed
                                                error:NULL];
    
    // Simple Factor
    if (scanner.isAtEnd) {
        parsedElement.isFactor = YES;
        parsedElement.value = prefix == nil ? [NSDecimalNumber one] : prefix;
        parsedElement.prefixOperatorExplicit = startsWithAsterisk;
        parsedElement.suffixOperatorExplicit = !productClosed;
        parsedElement.suffixIndex = scanner.scanLocation;
        return parsedElement;
    } else if (!productClosed) {
        if (error) {
            *error = MPParseError(scanner.scanLocation, @"Missing Number");
        }
        return nil;
    }
    
    parsedElement.prefixOperatorExplicit = startsWithAsterisk;
    parsedElement.prefixValueExplicit = YES;
    parsedElement.prefixMultiplicator = prefix;
    
    parsedElement.suffixValueExplicit = YES;
    
    // Scan Summands
    NSDecimalNumber *value = [NSDecimalNumber zero];
    NSDecimalNumber *currentSummand;
    while (!scanner.isAtEnd) {

        // Add every summand but the last one
        if (currentSummand != nil) {
            value = [value decimalNumberByAdding:currentSummand];
        }
        
        // Find the operator (+ or -)
        NSString *operator;
        BOOL found = [scanner scanString:@"+"
                              intoString:&operator];
        if (!found) {
            found = [scanner scanString:@"-"
                             intoString:&operator];
        }
        if (!found && !scanner.isAtEnd) {
            // Two numbers separated by just a space
            if (error) {
                *error = MPParseError(scanner.scanLocation, @"Missing Operator");
            }
            return nil;
        } else if (!found) {
            // Reached end of string
            break;
        }
        
        currentSummand = [self scanClosedProduct:&productClosed
                                           error:error];
        
        // No number was found
        if (currentSummand == nil) {
            // The string ends wit + or -
            if (scanner.isAtEnd) {
                productClosed = NO;
                parsedElement.suffixValueExplicit = NO;
                NSInteger suffix = [operator isEqualToString:@"+"] ? 1 : -1;
                currentSummand = [[NSDecimalNumber alloc] initWithInteger:suffix];
                if (error) {
                    *error = nil;
                }
                break;
            // Something else instead of a number
            } else {
                if (error) {
                    *error = MPParseError(scanner.scanLocation, @"Expected Number");
                }
                return nil;
            }
        }
        
        // Process the current summand
        if ([operator isEqualToString:@"-"]) {
            currentSummand = [currentSummand decimalNumberByMultiplyingBy:[[NSDecimalNumber alloc] initWithInteger:-1]];
        }
        
        // The summand ends with a *
        if (!productClosed) {
            // The string ends with a *
            if (scanner.isAtEnd) {
                parsedElement.suffixValueExplicit = YES;
                break;
            // After a * followed something else than a number
            } else {
                if (error) {
                    *error = MPParseError(scanner.scanLocation, @"Unexpected Symbol");
                }
                return nil;
            }
        }
    }
    
    parsedElement.value = value;
    parsedElement.suffixOperatorExplicit = !productClosed;
    parsedElement.suffixMultiplicator = currentSummand;
    parsedElement.suffixIndex = scanner.scanLocation;

    return parsedElement;
}

- (NSDecimalNumber *)scanClosedProduct:(BOOL *)closed error:(MPParseError **)error
{
    NSDecimal decimal;
    BOOL found = [scanner scanDecimal:&decimal];
    if (!found) {
        if (error != NULL) {
            *error = MPParseError(scanner.scanLocation, @"Unexpected Symbol");
        }
        return nil;
    }
    NSDecimalNumber *product = [NSDecimalNumber decimalNumberWithDecimal:decimal];
    while (true) {
        found = [scanner scanString:@"*"
                         intoString:NULL];
        if (found) {
            found = [scanner scanDecimal:&decimal];
            if (found) {
                product = [product decimalNumberByMultiplyingBy:[NSDecimalNumber decimalNumberWithDecimal:decimal]];
            } else {
                if (closed != NULL) {
                    *closed = NO;
                }
                return product;
            }
        } else {
            break;
        }
    }
    if (closed != NULL) {
        *closed = YES;
    }
    return product;
}

@end
