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

#import "MPElementaryFunctionTerm.h"

#import "MPParsedExpression.h"
#import "MPMathRules.h"



@implementation MPElementaryFunctionTerm {
    NSDecimalNumber *(^_function)(NSDecimalNumber *);
}

- (instancetype)initWithFunctionIdentifier:(NSString *)function
                                      term:(MPTerm *)term
{
    self = [super init];
    if (self) {
        NSAssert(function != nil, @"function must not be nil.");
        NSAssert(term != nil, @"term must not be nil.");
        double (*func)(double);
        BOOL takesArc = NO;
        BOOL returnsArc = NO;
        if ([function isEqualToString:@"sin"]) {
            func = &sin;
            takesArc = YES;
        } else if ([function isEqualToString:@"cos"]) {
            func = &cos;
            takesArc = YES;
        } else if ([function isEqualToString:@"tan"]) {
            func = &tan;
            takesArc = YES;
        } else if ([function isEqualToString:@"asin"] || [function isEqualToString:@"arcsin"]) {
            func = &asin;
            returnsArc = YES;
        } else if ([function isEqualToString:@"acos"] || [function isEqualToString:@"arccos"]) {
            func = &acos;
            returnsArc = YES;
        } else if ([function isEqualToString:@"atan"] || [function isEqualToString:@"arctan"]) {
            func = &atan;
            returnsArc = YES;
        } else if ([function isEqualToString:@"lg"] || [function isEqualToString:@"log"]) {
            func = &log10;
        } else if ([function isEqualToString:@"ln"]) {
            func = &log;
        } else {
            NSAssert(true, @"function must be one of (sin, cos, tan, asin, arcsin, acos, arccos, atan, arctan, lg, log, ln).");
        }
        [self setFunction:func
            takesArcValue:takesArc
          returnsArcValue:returnsArc];
        _term = term;
        _functionIdentifier = function.copy;
    }
    return self;
}


- (void)setFunction:(double (*)(double))function
      takesArcValue:(BOOL)takesArc
    returnsArcValue:(BOOL)returnsArc
{
    id __weak weakSelf = self;
    _function = ^(NSDecimalNumber *value){
        if (takesArc) {
            value = [weakSelf convertToRadiantsIfNecessary:value];
        }
        NSDecimalNumber *returnValue = [[NSDecimalNumber alloc] initWithDouble:function(value.doubleValue)];
        if (returnsArc) {
            returnValue = [weakSelf convertToDegreesIfNecessary:returnValue];
        }
        return returnValue;
    };
}


- (NSDecimalNumber *)convertToRadiantsIfNecessary:(NSDecimalNumber *)degrees
{
    if ([MPMathRules sharedRules].isUsingDegrees) {
        // * M_PI / 180
        return [[degrees decimalNumberByMultiplyingBy:[[NSDecimalNumber alloc] initWithDouble:M_PI]] decimalNumberByDividingBy:[[NSDecimalNumber alloc] initWithInteger:180]];
    } else {
        return degrees;
    }
}


- (NSDecimalNumber *)convertToDegreesIfNecessary:(NSDecimalNumber *)radiants
{
    if ([MPMathRules sharedRules].isUsingDegrees) {
        // * 180 / M_PI
        return [[radiants decimalNumberByMultiplyingBy:[[NSDecimalNumber alloc] initWithInteger:180]] decimalNumberByDividingBy:[[NSDecimalNumber alloc] initWithDouble:M_PI]];
    } else {
        return radiants;
    }
}


- (NSDecimalNumber *)doEvaluation:(NSError *__autoreleasing *)error
{
    NSDecimalNumber *value = [self.term evaluate:error];
    if (!value) {
        return nil;
    }
    NSDecimalNumber *result = _function(value);
    if ([result isEqualToNumber:[NSDecimalNumber notANumber]]) {
        result = nil;
        if (error) {
            NSString *errorDescription = [NSString stringWithFormat:NSLocalizedString(@"%@ is not defined for the value %@.", nil), self.functionIdentifier, [value descriptionWithLocale:[NSLocale currentLocale]]];
            *error = [NSError errorWithDomain:MPMathKitErrorDomain
                                         code:102
                                     userInfo:@{NSLocalizedDescriptionKey: errorDescription}];
        }
    }
    return result;
}

@end
