//
//  MPRangePath.h
//  MathKit
//
//  Created by Kim Wittenburg on 18.04.14.
//  Copyright (c) 2014 Kim Wittenburg. All rights reserved.
//

#import "MPExpression.h"


/*!
 @header
 This file contains the <code>MPRangePath</code> class.
 
 The <code>MPRangePath</code> combines an <code>NSIndexPath</code> with a
 <code>NSRange</code>. A range path is used in a tree structure to identify a
 number of subsequent nodes. This is achieved by combining a range path that
 stores the location of the first node in the tree with an integer that stores
 the number of nodes that should be included in the range path.
 */


/*!
    @define     MPMakeRangePath
    @abstract   Creates a new <code>NSRangePath</code> instance with the given
                location and length.
 
    @param      loc
                The location of the first included item. This must be an
                <code>NSIndexPath</code> instance.
 
    @param      len
                The number of nodes to include in the range path.
 
    @return     A new range path.
 */
#define MPMakeRangePath(loc, len) [MPRangePath rangePathWithLocation:(loc) length:(len)]



@class MPRangePath, MPExpression;


/*!
    @class      MPRangePath
    @abstract   A <code>MPRangePath</code> instance identifies a range of
                subsequent nodes in a tree structure.
 
    @discussion <code>MPRangePath</code> instances are immutable.
 */
@interface MPRangePath : NSObject <NSCopying, NSCoding>

#pragma mark Creation Methods
/*!
 @methodgroup Creation Methods
 */


/*!
    @method     init
    @abstract   Initializes an empty range path.
 
    @discussion This method is a convenience initializer to create a range path
                with an empty location and a length of <code>0</code>.
 
    @return     A new <code>MPRangePath</code> instance.
 */
- (instancetype)init;


/*!
    @method     initWithRange:
    @abstract   Initializes a newly created range path with the specified range.
 
    @discussion This method is a convenience initializer to convert from a
                <code>NSRange</code> struct to a <code>MPRangePath</code>
                instance. The location of the created range path is an index path
                of length <code>1</code>.
 
    @param      aRange
                The range to be converted into a <code>MPRangePath</code>.
 
    @return     A <code>MPRangePath</code> instance.
 */
- (instancetype)initWithRange:(NSRange)aRange;


/*!
    @method     initWithLocation:lenght:
    @abstract   Initializes a newly created range path with the specified location
                and length.
 
    @discussion The last index in the <code>location</code> addresses the first
                item that is actually selected.
 
    @param      location
                The location of the first element that should be addressed by
                this range path.
 
    @param      length
                The number of elements to be addressed by this range path.
 
    @return     A <code>MPRangePath</code> instance.
 */
- (instancetype)initWithLocation:(NSIndexPath *)location
                          length:(NSUInteger)length; /* designated initializer */


/*!
    @method     rangePath
    @abstract   Allocates and initializes an empty <code>NSRangePath</code>
                instance.
 
    @discussion An empty range path's location has <code>0</code> indexes and a
                length of <code>0</code>.

    @return     A newly created <code>MPRangePath</code> instance.
 */
+ (instancetype)rangePath;


/*!
    @method     rangePathWithRange:
    @abstract   Allocates a new <code>MPRangePath</code> instance and initializes
                it with the specified location and length.
 
    @discussion The location of <code>aRange</code> is translated into an index
                path for the range path.
 
    @param      aRange
                The range to be converted into a range path.
 
    @return     A newly created <code>MPRangePath</code> instance.
 */
+ (instancetype)rangePathWithRange:(NSRange)aRange;


/*!
    @method     rangePathWithLocation:length:
    @abstract   Allocates a new <code>MPRangePath</code> instance and initializes
                it with the specified location and length.

    @param      location
                The location of the first element that should be addressed by the
                range path.
 
    @param      length
                The number of elements that should be addressed by the range
                path.
 
    @return     A newly created <code>MPRangePath</code> instance.
 */
+ (instancetype)rangePathWithLocation:(NSIndexPath *)location
                           length:(NSUInteger)length;


#pragma mark Properties
/*!
 @methodgroup Properties
 */


/*!
    @property   location
    @abstract   The location of the first element addressed by the receiver.
 */
@property (readonly, nonatomic, strong) NSIndexPath *location;


/*!
    @property   length
    @abstract   The number of elements addressed by the receiver. The element
                addressed by the last index of the location property plus the
                length of the receiver is <b>not</b> considered inside of the
                range path.
 */
@property (readonly, nonatomic) NSUInteger length;


/*!
    @method     maxRangePath
    @abstract   Similar to the <code>NSRange</code> function
                <code>NSMaxRange</code> this method returns the first index after
                the receiving <code>NSRangePath</code>.
 
    @return     The first index after the receiving <code>NSRangePath</code>.
 */
- (NSIndexPath *)maxRangePath;


/*!
    @method     rangeAtLastIndex
    @abstract   Returns a <code>NSRange</code> constructed from the last index of
                the receiver's location and the receiver's length.
 
    @return     The range identifying the elements at the last index in the
                receiver's index path.
 */
- (NSRange)rangeAtLastIndex;


#pragma mark Working With Ranges
/*!
 @methodgroup Working With Ranges
 */


/*!
    @method     containsLocation:
    @abstract   Checks wether the range path addressed by the receiver includes
                <code>location</code>.
 
    @discussion The index path is considered inside the receiver if the indexes
                in the receiver's <code>location</code> property are equal to the
                respective indexes index in <code>location</code>. For the last
                position of the last index of the receiver's
                <code>location</code> it is also valid if the respective index is
                inside the range at the last index.
 
    @param      location
                The index path to check wether it is contained in the receiver.
 
    @return     <code>YES</code> if the range path addressed by the receiver also
                contains <code>location,</code> <code>NO</code> otherwise.
 */
- (BOOL)containsLocation:(NSIndexPath *)location;


/*!
    @method     containsRangePath:
    @abstract   Checks wether the receiver completely contains
                <code>aRangePath</code>.
 
    @discussion A range path is contained by another range path if either the
                receiver's range at its last index contains the index at the
                respective location of the location path of
                <code>aRangePath</code> or (if the locations are of the same
                length) the receiver's range at its last index contains the last
                index's range of <code>aRangePath</code>.
 
    @param      aRangePath
                The range path to check wether it is contained in the receiver.
 
    @return     <code>YES</code> if the range path addressed by the receiver also
                includes <code>aRangePath,</code> <code>NO</code> otherwise.
 */
- (BOOL)containsRangePath:(MPRangePath *)aRangePath;


/*!
    @method     isEqualToRangePath:
    @abstract   Checks wether the receiver and <code>aRangePath</code> are equal.
 
    @discussion If you know that you are comparing two range paths (that are not
                <code>nil</code>) you should prefer this method over
                <code>isEqual:</code> because it is more performant.
 
    @param      aRangePath
                The range path to check for equality. If this parameter is
                <code>nil</code> this method may raise an exception.
 
    @return     <code>YES</code> if the receiver is equal to
                <code>aRangePath</code>, <code>NO</code> otherwise.
 */
- (BOOL)isEqualToRangePath:(MPRangePath *)aRangePath;

@end



@interface MPExpression (MPRangeExtension)

/*!
    @method     subexpressionWithRangePath:
    @abstract   Creates a new expression with the symbols in the specified range
                path.
 
    @discussion The elements in the newly created expression are copied to the
                new exoression. The range path is specified in the length
                reference frame.
 
                If the specified range exceeds the receiver's bounds a
                <code>NSRangeException</code> is raised.
 
    @param      aRangePath
                The range path from which to create the new expression.
 
    @return     A new expression.
 */
- (MPExpression *)subexpressionWithRangePath:(MPRangePath *)aRangePath
                              referenceFrame:(MPReferenceFrame)referenceFrame;


/*!
    @method     replaceItemsInRangePath:referenceFrame:withElements:
    @abstract   Replaces the items in the specified range path with the contents
                of <code>elements</code>.
 
    @discussion All objects in the <code>elements</code> array must conform to
                the <code>@link
                //apple_ref/occ/intf/MPExpressionElement@/link</code> protocol.
                If one or more objects do not conform to the protocol a
                <code>@link
                //apple_ref/c/data/MPIllegalElementException@/link</code> is
                raised.
 
    @param      rangePath
                The range path to be replaced. The location of the range path
                (except for the last index) is expressed in the element reference
                frame. The range of the range path is expressed in the specified
                <code>referenceFrame</code>.
 
    @param      referenceFrame
                The reference frame to use. This only applies to the range at the
                last index of the range path. The path of the range path is
                always expressed in the element reference frame.
 
    @param      elements
                The elements replacing the ones in the specified range path.
 */
- (void)replaceItemsInRangePath:(MPRangePath *)rangePath
                 referenceFrame:(MPReferenceFrame)referenceFrame
                   withElements:(NSArray *)elements;

@end
