//
//  MPExpression.h
//  MathPad
//
//  Created by Kim Wittenburg on 10.08.14.
//  Copyright (c) 2014 Kim Wittenburg. All rights reserved.
//

@import Foundation;
#import "NSString+MPExpressionElement.h"

@class MPExpression, MPFunction, MPRangePath, MPExpressionEvaluator, MPParseError;
@protocol MPExpressionElement;

/*!
    @class      MPExpression
    @brief      An expression is the base object for any mathematical expression.
 
    @discussion Every expression consists of string elements (represented by the
                @c NSString class) and function (represented by the @c MPFunction
                class) elements which both can be contained within an expression.
                 Functions in turn can have expressions as elements (also called
                'children' in this context). Both expressions and functions are
                mutable.
 
                Through this organization expression are organized in a tree-like
                structure (called the 'expression tree') allowing easy and
                logical access to each element.
 
                An expression can evaluate itself giving you either a
                result or possibly an error if the expression was not constructed
                correctly.
  */
@interface MPExpression : NSObject <NSCopying, NSCoding>


#pragma mark Creation Methods


/*!
    @method     init
    @brief      Initlializes a newly created expression.
 
    @discussion This method is a convenience initializer to initialize an empty
                expression.
 
    @return     An expression.
 */
- (instancetype)init;


/*!
    @method     initWithElement:
    @brief      Initializes a newly created expression with one element.

    @discussion This method is a convenience initializer to initialize an
                expression with a single element.
 
    @param      element
                The element to be added to the expression. The @c element will be
                copied.
 
    @return     An expression initialized with @c element.
 */
- (instancetype)initWithElement:(id<MPExpressionElement>)element;


/*!
    @method     initWithElements:
    @brief      Initializes a newly created expression with the given elements.
 
    @discussion This method is the designated initializer for the @c MPExpression
                class.
 
    @param      elements
                The elements that should be added to the expression. Each element
                is copied and the copy is then added to the expression.
 
    @return     An expression containing the elements from @c elements.
 */
- (instancetype)initWithElements:(NSArray *)elements; /* designated initializer */


#pragma mark Working With the Expression Tree


/*!
    @property   parent
    @brief      The receiver's parent.
 
    @discussion Expressions are organized in a tree-like structure. Through this
                property an expression's containing function can be accessed.
                You should not set this property manually because that can cause
                inconsistencies in the expression tree.
 
    @return     The parent of the receiver or @c nil if the receiver is the root
                expression.
 */
@property (nonatomic, weak) MPFunction *parent;


/*!
    @method     fixElements
    @brief      Repairs any inconsistencies in the receiver.
 
    @discussion This method goes over all elements in the receiver and tries to
                repair inconsistencies that occured when mutating the receiver.

                Since this method is called automatically everytime the receiver
                is mutated there should be little need for you to call it
                yourself.
 */
- (void)fixElements;


#pragma mark Primitive Methods


/*!
    @method     length
    @brief      Returns the length of the receiver.
 
    @discussion The length of an expression is calculated by walking over each
                element in the receiver and sending it a @c length message. This
                method should be used to determine the number of digits or
                symbols in an expression.
 
                To address a symbol in the expression counted in the @c length
                reference frame of an expression the word 'location' is used.

    @return     The length of the receiver. This is the number of symbols in all
                elements in the receiver where a function element is counted as a
                single symbol.
 */
- (NSUInteger)length;


/*!
    @method     numberOfElements
    @brief      Returns the number of elements in the receiver.
 
    @discussion The number of elements may vary from the number of elements that
                were added to the receiver (using either @c -initWithElements: or
                @ -replaceElementsInRange:withElements:)
 
                To address a specific symbol in an expression the word 'index' is
                used. The index of elements is used when you access an
                expression's elements using subscript syntax.
 
    @return     The current number of elements in the receiver.
 */
- (NSUInteger)numberOfElements;


/* Subscripting is supported for indexes and elements */
- (void)setObject:(id)obj atIndexedSubscript:(NSUInteger)idx;
- (id)objectAtIndexedSubscript:(NSUInteger)idx;


/*!
    @method     elementAtIndex:
    @brief      Returns the element at @c anIndex.
 
    @discussion The element is not copied before it is returned. So be aware that
                if you mutate a @c MPFunction object returned from this function
                the changes will be reflected in the receiver.
 
                This method can also be called using indexed subscript getter
                syntax.
 
    @param      anIndex
                The index of the element. If the index is greater than or equal
                to the number of elements in the receiver an @c NSRangeException
                is raised.
 
    @return     The element located at @c anIndex.
 */
- (id<MPExpressionElement>)elementAtIndex:(NSUInteger)anIndex;


/*!
    @method     elementsInRange:
    @brief      Returns an array of the elements that are located in the
                specified range. The range is specified in indexes.
 
    @discussion The objects in the returned array are not copied before they are
                returned. You should be aware of the fact that mutations to any
                returned element will be reflected in the receiver.
 
                If the @c range exceeds the receiver's bounds an @c
                NSRangeException is raised.
 
    @param      range
                The requested range within the receiver's bounds.
 
    @return     An array of objects that conform to the @c MPExpressionElement
                protocol (that is @c NSString objects and @c MPFunction objects).
                The length of the returned array is equal to the length of the
                specified range.
 */
- (NSArray *)elementsInRange:(NSRange)range;


/*!
    @method     indexOfElement:
    @brief      Returns the index of @c element or @c NSNotFound if it was not
                found.
 
 @discussion
 
 @param
 
 @return
 */
#warning Implementation may be faulty
- (NSUInteger)indexOfElement:(id<MPExpressionElement>)element;


/*!
    @method     indexOfElementAtSymbolLocation:offset
    @brief      Calculates the index of the element the specified location points
                to.
 
    @discussion The @c location is in the length reference frame whereas the
                returned value is an element index. This method converts from
                the former to the latter.
 
                If the location exceeds the receiver's bounds a @c
                NSRangeException will be raised.
 
    @param      location
                The location of which you want the corresponding element index.
 
    @param      offset
                An output parameter that gets set to the offst into the symbol
                whose index is returned. If location for example points to the
                symbol @c '2' in the string element @c '123' the offset @c would
                be set to @c 1.
 
    @return     The index of the element the location points to.
 */
- (NSUInteger)indexOfElementAtSymbolLocation:(NSUInteger)location offset:(out NSUInteger *)offset;


/*!
    @method     locationOfElementAtIndex:
    @brief      Calculates the location of the element at @c index.
 
    @discussion @c index is an element index. Use this method to convert an
                element index into the length reference frame.
 
                If the index exceeds the receiver's number of elements a @c
                NSRangeException will be raised.
 
    @param      index
                The index of the element that is to be converted into the length
                reference frame.
 
    @return     The number of symbols (in the length reference frame) before the
                element at @c index.
 */
- (NSUInteger)locationOfElementAtIndex:(NSUInteger)index;


/*!
    @method     replaceSymbolsInRange:withElements:
    @brief      Replaces the elements in the given range with the contents of the
                @c elements array.
 
    @discussion This is the most primitive mutation method of @c MPExpression.
                Every other mutating method utlimately must call this method.
                After the receiver has been mutated @c -fixElements is called to
                restore integrity of the receiver.
 
                After the receiver has been mutated (and integrity has been
                restored) the receiver sends a @c
                -didChangeElementsInRangePath:replacementLength: to itself. For
                more information see the documentation on that method.
 
    @param      range
                The @c range is  specified in the length reference
                frame. Because of this this method can be directly used for user
                interaction.
 
    @param      elements
                The elements that should replace the symbols specified by @c
                range.
 */
- (void)replaceSymbolsInRange:(NSRange)range
                 withElements:(NSArray *)elements;


/*!
    @method     elements
    @brief      Returns an array of all elements in the receiver.
 
    @discussion The elements in the returned array are not copied before they are
                returned.
 
    @return     An array of all elements from the receiver.
 */
- (NSArray *)elements;


#pragma mark Evaluating Expressions


/*!
    @method     evaluateWitError:
    @brief      Evaluates the receiving expression.
 
    @discussion This is a convenience method for evaluating an expression. If you
                want more control over the evaluation process use the @c
                evaluator property of the receiver.
 
    @param      errror
                If the receiver (or any of its elements) contains a syntax error
                or can not be evaluated this parameter is set to an appropriate
                value. Pass @c NULL if you are not interested in any errors that
                might occur.
 
    @return     The result of the evaluation or @c nil of the receiver could not
                be evaluated. In that case the @c error parameter is set to an
                appropriate value.
 */
- (NSDecimalNumber *)evaluateWithError:(MPParseError *__autoreleasing *)error;


/*!
    @property   evaluator
    @brief      Returns an object that can evaluate the receiver.
 
    @discussion To just evaluate an expression it is recommended to send it an
                @c evaluateWithError: message. You can however use this property
                instead if you need more control over the evaluation process.
 */
@property (readonly, nonatomic, strong) MPExpressionEvaluator *evaluator;

#pragma mark Notifications
// All notification methods should create a new rangePath with the receiver's index added to the beginning of the path and then ascend the message to it's parent

/*!
    @method     didChangeElementsInRangePath:replacementLength:
    @brief      Called after the receiver has been mutated.
 
    @discussion This method does nothing more than notify it's parent that it has
                been mutated at the receiver's index. If you need to know about
                changes in an expression you should override this method instead
                of @c -replaceSymbolsInRange:withElements because this method
                gives you information about the number of elements changed during
                the mutation.
 
    @param      rangePath
                The range path at which the receiver was changed starting at the
                receiver.
 
    @param      replacementLength
                The number of elements replacing the elements specified by @c
                rangePath.
 */
- (void)didChangeElementsInRangePath:(MPRangePath *)rangePath
                   replacementLength:(NSUInteger)replacementLength;


#pragma mark Basic NSObject Methods


/*!
    @method     isEqualToExpression:
    @brief      Returns wether the receiver is equal to @c anExpression.
 
    @param      anExpression
                The expression the receiver should be compared to.
 
    @return     @c YES if @c anExpression is equal to the receiver, @c NO
                otherwise.
 */
// - (BOOL)isEqualToExpression:(MPExpression *)anExpression;

- (NSString *)description;
- (NSUInteger)hash;

@end

/* --------------------------------------------------------------------------- */
/* Extension Methods */
/* --------------------------------------------------------------------------- */

@interface MPExpression (MPExpressionExtension)


#pragma mark Working With the Expression Tree


/*!
    @method     rootExpression
    @brief      Returns the root expression from the receiver's expression tree.
 
    @discussion The root expression is the ultimate parent of all expressions and
                functions in the expression tree.
 
    @return     The root expression from the receiver's expression tree.
 */
- (MPExpression *)rootExpression;


/*!
    @method     elementAtIndexPath:
    @brief      Returns the element at the specified index path.
 
    @discussion The returned object can be an @c NSString, a @c MPFunction or an
                @c MPExpression depending on the element @c indexPath points to.
                If any of the indexes exceed the bounds of the respective
                receiver an @c NSRangeException is raised.
            
                If the index path does not contain any indexes the receiver
                itself is returned.
 
    @param      indexPath
                The index path the required object is located at.
 
    @return     The element located at @c indexPath. The element is not copied
                before it is returned. Be aware of the fact that any mutations
                made to the returned object are reflected in the receiver.
 */
- (id)elementAtIndexPath:(NSIndexPath *)indexPath;


/*!
    @method     elementsInRangePath:
    @brief      Returns the elements in the specified range path.
 
    @discussion If any of the indexes or the range exceed the bounds of the
                respective receiver an @c NSRangeException is raised.
 
    @param      rangePath
                The range path the requested objects are located at.
 
    @return     An array of objects specified by the range path. The returned
                elements are not copied before they are returned. Be aware that
                any mutations made to the returned objects are reflected in the
                receiver.
 */
- (NSArray *)elementsInRangePath:(MPRangePath *)rangePath;


/*!
    @method     indexPath
    @brief      Returns the index path of the receiver in the expression tree.
 
    @discussion The index path is calculated by walking up the expression tree
                collecting the respective index of the receiver.
 
    @return     The index path of the receiver in the expression tree.
 */
- (NSIndexPath *)indexPath;


#pragma mark Working With Expressions


/*!
    @method     subexpressionFromLocation:
    @brief      Creates a new expression from the specified index (inclusive) to
                the end of the receiver.
 
    @discussion The elements in the newly created expression are copied to the
                new expression. The location is specified in the length reference
                frame.
 
                If the given location exceeds the receiver's bounds a @c
                NSRangeException is raised.
 
    @param      from
                The first location to be included in the new expression.
 
    @return A new expression.
 */
- (MPExpression *)subexpressionFromLocation:(NSUInteger)from;


/*!
    @method     subexpressionToLocation:
    @brief      Creates a new expression from the beginning to the specified
                index (exclusive).
 
    @discussion The elements in the newly created expression are copied to the
                new expression. The location is specified in the length reference
                frame.
 
                If the given location exceeds the receiver's bounds a @c
                NSRangeException is raised.

 
    @param      to
                The first location not to be included in the new expression or
                the length of the new expression.
 
    @return     A new expression.
 */
- (MPExpression *)subexpressionToLocation:(NSUInteger)to;


/*!
    @method     subexpressionWithRange:
    @brief      Creates a new expression with the symbols in the specified range.
 
    @discussion The elements in the newly created expression are copied to the
                new exoression. The range is specified in the length reference
                frame.
 
                If the given range exceeds the receiver's bounds a @c
                NSRangeException is raised.
 
    @param      range
                The range from which to create the new expression.
 
    @return     A new expression.
 */
- (MPExpression *)subexpressionWithRange:(NSRange)range;


#pragma mark Mutating Expressions


/*!
    @method     appendElement:
    @brief      Appends @c anElement to the receiver.
 
    @param      anElement
                The element to append to the receiver.
 */
- (void)appendElement:(id<MPExpressionElement>)anElement;


/*!
    @method     appendElements:
    @brief      Appends the objects from @c elements to the receiver.
 
    @param      elements
                The elements to append to the receiver.
 */
- (void)appendElements:(NSArray *)elements;


/*!
    @method     insertElement:atLocation:
    @brief      Inserts @c anElement at @c location.
 
    @discussion The location is specified in the length reference frame.
 
                If the given location exceeds the receiver's bounds a @c
                NSRangeException is raised.
 
    @param      anElement
                The element to be inserted into the receiver.
 
    @param      location
                The location @c anElement should be inserted at.
 */
- (void)insertElement:(id<MPExpressionElement>)anElement
           atLocation:(NSUInteger)location;


/*!
    @method     insertElements:atLocation:
    @brief      Inserts the elements from @c elements at @c location.
 
    @discussion The location is specified in the length reference frame.
 
                If the given location exceeds the receiver's bounds a @c
                NSRangeException is raised.
 
    @param      elements
                The elements to be inserted into the receiver.
 
    @param      location
                The location the elements in @c elements should be inserted into
                the receiver.
 */
- (void)insertElements:(NSArray *)elements
            atLocation:(NSUInteger)location;


/*!
    @method     deleteElementsInRange:
    @brief      Removes the elements specified by @c range from the receiver.
 
    @discussion The range is specified in the length reference frame.

                If @c range exceeds the receiver's bounds a @c NSRangeException
                is raised.
 
    @param      range
                The range to remove from the receiver.
 */
- (void)deleteElementsInRange:(NSRange)range;


/*!
    @method     mutableElements
    @brief      Returns a proxy mutable array object that responds to all methods
                defined by @c NSMutableArray.
 
    @discussion Mutations on the proxy object also change the receiver. The proxy
                object does not respond to coding methods. Copying the proxy
                object will not duplicate it.

    @return     A proxy object that responds to all methods defined by @c
                NSMutableArray.
 */
// - (NSMutableArray *)mutableElements;

@end

