//
//  MPExpressionParser.m
//  MathKit
//
//  Created by Kim Wittenburg on 16.11.14.
//  Copyright (c) 2014 Kim Wittenburg. All rights reserved.
//

#import "MPExpressionParser.h"

#import "MPToken.h"
#import "MPExpression.h"
#import "MPParsedExpression.h"

#import "MPTerm.h"
#import "MPSumTerm.h"
#import "MPProductTerm.h"
#import "MPNegatedTerm.h"
#import "MPFunctionTerm.h"
#import "MPElementaryFunctionTerm.h"
#import "MPNumber.h"
#import "MPVariable.h"
#import "MPFactorialTerm.h"
#import "MPPowerTerm.h"


#define success() state = 0
#define fail() self.errorTokenIndex = self.currentTokenIndex; state = -1



@interface MPExpressionParser ()

@property (nonatomic, strong) NSArray *tokens;
@property (nonatomic) NSUInteger currentTokenIndex;

@property (nonatomic, strong) NSMutableArray *errors;

@property (nonatomic, strong) NSMutableArray *summands;
@property (nonatomic) BOOL summandNegative;
@property (nonatomic, strong) NSMutableArray *factors;
@property (nonatomic) BOOL factorNegative;
@property (nonatomic, strong) NSMutableArray *functionStack;
@property (nonatomic, strong) NSMutableArray *valueGroup;
@property (nonatomic, strong) MPTerm *value;

@property (nonatomic) NSUInteger errorTokenIndex;

@end



@implementation MPExpressionParser

- (instancetype)initWithExpression:(MPExpression *)expression
{
    self = [super init];
    if (self) {
        _tokens = [expression allItemsInReferenceFrame:MPTokenReferenceFrame];
        _expression = expression;
    }
    return self;
}


- (MPParsedExpression *)parse:(NSArray *__autoreleasing *)errors
{
    return [self parseExpectingVariableDefinition:NO
                                           errors:errors];
}


- (MPParsedExpression *)parseExpectingVariableDefinition:(BOOL)flag
                                                  errors:(NSArray *__autoreleasing *)errors
{
    MPParsedExpression *result = [[MPParsedExpression alloc] init];
    self.currentTokenIndex = 0;
    
    BOOL hasVariableDefinition = NO;
    NSString *variableName = nil;
    [self skipWhitespaces];
    if (self.currentToken.tokenType == MPVariableToken) {
        variableName = self.currentToken.stringValue;
        [self nextToken];
        [self skipWhitespaces];
        if (self.currentToken.tokenType == MPEqualsToken) {
            [self nextToken];
            hasVariableDefinition = YES;
        }
    }
    if (flag && hasVariableDefinition) {
        result.definedVariable = variableName;
    } else if (flag) {
        [self addErrorWithCode:3 localizedDescription:NSLocalizedString(@"Expected Variable Definition.", nil) useRange:NO];
        if (errors) {
            *errors = self.errors;
        }
        return nil;
    } else if (hasVariableDefinition) {
        [self addErrorWithCode:4 localizedDescription:NSLocalizedString(@"Unexpected Variable Definition.", nil) useRange:NO];
        if (errors) {
            *errors = self.errors;
        }
        return nil;
    } else {
        self.currentTokenIndex = 0;
    }
    
    [self skipWhitespaces];
    if (self.currentTokenIndex >= self.tokens.count) {
        [self addErrorWithCode:5 localizedDescription:NSLocalizedString(@"Empty Expression.", nil) useRange:NO];
        if (errors) {
            *errors = self.errors;
        }
        return nil;
    }
    
    [self runParserMachine];
    if ([self errorOccured]) {
        if (errors) {
            *errors = self.errors;
        }
        return nil;
    }
    result.term = [[MPSumTerm alloc] initWithSummands:self.summands];
    return result;
}


- (void)runParserMachine
{
    NSInteger state = 1;
    NSInteger backtraceState = 0;
    NSUInteger backtraceTokenIndex = self.currentTokenIndex;
    BOOL alternateRoute = NO;
    while (state != 0) {
        switch (state) {
            case -1:
                state = backtraceState;
                self.currentTokenIndex = backtraceTokenIndex;
                alternateRoute = YES;
                break;
                
            case 1:
                [self skipWhitespaces];
                if (self.currentToken.tokenType == MPOperatorListToken) {
                    self.factorNegative = [self parseOperatorList:self.currentToken];
                    [self nextToken];
                    state = 2;
                } else {
                    self.factorNegative = NO;
                    state = 2;
                }
                break;
                
            case 2:
                [self skipWhitespaces];
                if (self.currentToken.tokenType == MPElementaryFunctionToken) {
                    [self.functionStack addObject:self.currentToken.stringValue];
                    [self nextToken];
                    state = 2;
                } else {
                    state = 3;
                }
                break;
                
            case 3:
                [self skipWhitespaces];
                if (self.currentToken.tokenType == MPGenericFunctionToken) {
                    NSArray *errors;
                    self.value = [[MPFunctionTerm alloc] initWithFunction:(MPFunction *)self.currentToken
                                                                   errors:&errors];
                    if (!self.value) {
                        [self.errors addObjectsFromArray:errors];
                    }
                    [self nextToken];
                    [self runSuffixMachine];
                    if (self.value) {
                        [self.valueGroup addObject:self.value];
                    }
                    state = 6;
                } else {
                    self.value = nil;
                    state = 4;
                }
                break;
                
            case 4:
                if (self.currentToken.tokenType == MPNumberToken) {
                    self.value = [[MPNumber alloc] initWithNumber:[self parseNumber:self.currentToken]];
                    [self nextToken];
                    [self runSuffixMachine];
                    [self.valueGroup addObject:self.value];
                    state = 5;
                } else if (self.currentToken.tokenType == MPVariableToken) {
                    self.value = [[MPVariable alloc] initWithVariableName:[self parseVariable:self.currentToken]];
                    [self nextToken];
                    [self runSuffixMachine];
                    [self.valueGroup addObject:self.value];
                    state = 5;
                } else {
                    fail();
                }
                break;
                
            case 5:
                if (alternateRoute) {
                    alternateRoute = NO;
                    state = 6;
                } else {
                    backtraceState = 5;
                    backtraceTokenIndex = self.currentTokenIndex;
                    state = 4;
                }
                break;
                
            case 6:
                [self collapseFactor];
                [self skipWhitespaces];
                backtraceState = 6;
                backtraceTokenIndex = self.currentTokenIndex;
                if (!alternateRoute && self.currentToken.tokenType == MPOperatorListToken) {
                    [self collapseSummand];
                    self.summandNegative = [self parseOperatorList:self.currentToken];
                    [self nextToken];
                    state = 1;
                } else {
                    state = 7;
                }
                break;
                
            case 7:
                if (alternateRoute) {
                    [self collapseSummand];
                    alternateRoute = NO;
                    success();
                } else {
                    backtraceState = 7;
                    backtraceTokenIndex = self.currentTokenIndex;
                    state = 8;
                }
                break;
                
            case 8:
                [self skipWhitespaces];
                if (self.currentToken.tokenType == MPMultiplicationSymbolToken) {
                    [self nextToken];
                    state = 1;
                } else {
                    state = 1;
                }
                break;
                
            default:
                // Should never get here
                break;
        }
    }
}


- (void)skipWhitespaces
{
    while ([self currentToken] != nil && [self currentToken].tokenType == MPWhitespaceToken) {
        [self nextToken];
    }
}


- (id<MPToken>)currentToken
{
    if (self.currentTokenIndex >= self.tokens.count) {
        return nil;
    }
    return self.tokens[self.currentTokenIndex];
}


- (void)nextToken
{
    self.currentTokenIndex++;
}


- (NSMutableArray *)errors
{
    if (!_errors) {
        _errors = [[NSMutableArray alloc] init];
    }
    return _errors;
}


- (NSMutableArray *)summands
{
    if (!_summands) {
        _summands = [[NSMutableArray alloc] init];
    }
    return _summands;
}


- (NSMutableArray *)factors
{
    if (!_factors) {
        _factors = [[NSMutableArray alloc] init];
    }
    return _factors;
}


- (NSMutableArray *)functionStack
{
    if (!_functionStack) {
        _functionStack = [[NSMutableArray alloc] init];
    }
    return _functionStack;
}


- (NSMutableArray *)valueGroup
{
    if (!_valueGroup) {
        _valueGroup = [[NSMutableArray alloc] init];
    }
    return _valueGroup;
}


- (BOOL)parseOperatorList:(id<MPToken>)token // Returns YES if list is overall negative
{
    NSString *operatorString = [[token.stringValue stringByReplacingOccurrencesOfString:@" " withString:@""]
                                                   stringByReplacingOccurrencesOfString:@"+" withString:@""];
    return (operatorString.length & 1) == 1;
}


- (NSDecimalNumber *)parseNumber:(id<MPToken>)token
{
    return [NSDecimalNumber decimalNumberWithString:token.stringValue
                                             locale:[NSLocale currentLocale]];
}


- (NSString *)parseVariable:(id<MPToken>)token
{
    return token.stringValue;
}


- (void)collapseSummand
{
    if (self.factors.count > 0) {
        MPTerm *summand = [[MPProductTerm alloc] initWithFactors:self.factors];
        if (self.summandNegative) {
            summand = [[MPNegatedTerm alloc] initWithTerm:summand];
        }
        [self.summands addObject:summand];
    }
    self.factors = nil;
    self.summandNegative = NO;
}


- (void)collapseFactor
{
    if (self.valueGroup.count > 0) {
        MPTerm *factor = [[MPProductTerm alloc] initWithFactors:self.valueGroup];
        for (NSInteger index = self.functionStack.count - 1; index >= 0; index--) {
            factor = [[MPElementaryFunctionTerm alloc] initWithFunctionIdentifier:self.functionStack[index]
                                                                             term:factor];
        }
        if (self.factorNegative) {
            factor = [[MPNegatedTerm alloc] initWithTerm:factor];
        }
        [self.factors addObject:factor];
    }
    self.valueGroup = nil;
    self.functionStack = nil;
    self.factorNegative = NO;
}


- (void)runSuffixMachine
{
    BOOL checkMore = YES;
    while (checkMore) {
        if (self.currentToken.tokenType == MPFactorialToken) {
            [self nextToken];
            if (self.value) {
                MPFactorialTerm *term = [[MPFactorialTerm alloc] initWithTerm:self.value];
                self.value = term;
            }
        } else if (self.currentToken.tokenType == MPPowerToken) {
            if (self.value) {
                NSArray *errors;
                MPPowerTerm *term = [[MPPowerTerm alloc] initWithFunction:(MPFunction *)self.currentToken errors:&errors];
                if (!term) {
                    [self.errors addObjectsFromArray:errors];
                } else {
                    term.baseTerm = self.value;
                    self.value = term;
                }
            }
            [self nextToken];
        } else {
            checkMore = NO;
        }
    }
}


- (void)addErrorWithCode:(NSInteger)code
    localizedDescription:(NSString *)description
                useRange:(BOOL)flag
{
    NSRange errorRange;
    if (flag) {
        errorRange = [self.tokens[self.errorTokenIndex] range];
    } else {
        NSInteger errorIndex = [self.expression convertIndex:self.errorTokenIndex
                                          fromReferenceFrame:MPTokenReferenceFrame
                                            toReferenceFrame:MPSymbolReferenceFrame];
        errorRange = NSMakeRange(errorIndex, 0);
    }
    [self.errors addObject:[NSError errorWithDomain:MPMathKitErrorDomain
                                               code:code
                                           userInfo:@{NSLocalizedDescriptionKey: description,
                                                      MPPathToExpressionKey: self.expression.indexPath,
                                                      MPErrorRangeKey: [NSValue valueWithRange:errorRange]}]];
}


- (BOOL)errorOccured
{
    [self skipWhitespaces];
    if (self.currentTokenIndex < self.tokens.count) {
        if (self.errorTokenIndex >= self.tokens.count) {
            [self addErrorWithCode:0 localizedDescription:NSLocalizedString(@"Unexpected end. Expected Value", nil) useRange:NO];
        } else {
            id<MPToken> unexpectedToken = self.tokens[self.errorTokenIndex];
            if (unexpectedToken.tokenType == MPPowerToken) {
                [self addErrorWithCode:34 localizedDescription:NSLocalizedString(@"No base for Power", nil) useRange:NO];
            } else if (unexpectedToken.tokenType == MPDeformedNumberToken) {
                [self addErrorWithCode:42 localizedDescription:NSLocalizedString(@"Not a Number", nil) useRange:YES];
            } else {
                [self addErrorWithCode:1 localizedDescription:NSLocalizedString(@"Unexpected Symbol. Expected Value", nil) useRange:NO];
            }
        }
    }
    return self.errors.count > 0;
}

@end
