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


/*!
 @header
 This file contains the <code>MPLayout</code> class.
 
 The <code>MPLayout</code> class renders an expression to be displayed in an
 <code>@link //apple_ref/occ/cl/MPExpressionView@/link</code>. There are multiple
 subclasses of <code>MPLayout</code> the most important of which are <code>@link
 //apple_ref/occ/cl/MPExpressionLayout@/link</code> and <code>@link
 //apple_ref/occ/cl/MPFunctionLayout@/link</code> for rendering expressions and
 functions respectively.
 
 Both types of layouts can occur in two different sizes: normal and small. The
 root expression ueses the normal size. Depending on the concrete subclass of
 <code>@link //apple_ref/occ/cl/MPFunctionLayout@/link</code> that is used a
 function's children (and any subsequent children) may be rendered using the
 small size. Also there are two different fonts available for layouts: a normal
 and a special font. The special font should only be used to emphasize special
 content that uses the normal font.
 
 If an expression is empty an empty box is rendered instead. That box (called the
 <i>empty box</i> uses the font metrics of the empty expression it represents.
 That means it uses different sizes depending on whether the expression uses a
 small or normal sized font. Also it is drawn a little smaller than the actual
 box's height is because it looks nicer. Depending on your needs you can query
 the metrics of the empty box using the <code>kMPEmptyBox...</code> or
 <code>kMPEmptyBoxDrawing...</code> macros. These macros must not be used outside
 of the <code>MPLayout</code> class or any of it subclasses.
 */


/*!
    @define     kMPEmptyBoxWidth
    @abstract   The width of the empty box.
 */
#define kMPEmptyBoxWidth (self.usesSmallSize ? 2.0 : 3.0)


/*!
    @define     kMPEmptyBoxHeight
    @abstract   The actual height of the empty box.
 */
#define kMPEmptyBoxHeight (CTFontGetDescent((CTFontRef)self.font) + CTFontGetAscent((CTFontRef)self.font) + CTFontGetLeading((CTFontRef)self.font))


/*!
    @define     kMPEmptyBoxYOrigin
    @abstract   The actual vertical origin of the empty box.
 
    @discussion The vertical origin is a negative value to be compliant with the
                metrics of strings.
 */
#define kMPEmptyBoxYOrigin (-(CTFontGetDescent((CTFontRef)self.font) + CTFontGetLeading((CTFontRef)self.font)))


/*!
    @define     kMPEmptyBoxDrawingWidth
    @abstract   The with the empty box is rendered with.
 */
#define kMPEmptyBoxDrawingWidth kMPEmptyBoxWidth


/*!
    @define     kMPEmptyBoxDrawingHeight
    @abstract   The height the empty box is drawn with.
 */
#define kMPEmptyBoxDrawingHeight (CTFontGetDescent((CTFontRef)self.font) + CTFontGetAscent((CTFontRef)self.font))


/*!
    @define     kMPEmptyBoxDrawingYOrigin
    @abstract   The vertical origin the empty box is drawn at.
 
    @discussion The vertical origin is a negative value to be compliant with the
                metrics of strings.
 */
#define kMPEmptyBoxDrawingYOrigin (-(CTFontGetDescent((CTFontRef)self.font) + CTFontGetLeading((CTFontRef)self.font)/2))



@class MPLayout, MPExpressionView, MPRangePath;


/*!
    @class      MPLayout
    @abstract   This is an abstract class that defines the basic methods that
                must be implemented by other layout classes. It also defines some
                useful helper methods.
 
    @discussion The <code>MPLayout</code> class maintains a generic cache. It is
                used to store child layouts and actual cache information.
 
                Every layout instance represents either an expression or a
                function. The instance's children represent the children of the
                represented object.
 */
@interface MPLayout : NSObject

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


/*!
    @method     init
    @abstract   Initializes the receiver.
 
    @discussion This method should only be used to initialize the expression
                layout that renders the root layout.

                This method is the designated initializer of the
                <code>MPLayout</code> class.
 
    @return     A newly initialized <code>MPLayout</code> instance.
 */
- (instancetype)init;


/*!
    @method     initWithParent:
    @abstract   Initializes the receiver with the specified parent.
 
    @discussion This method should generally be used when initializing layout
                instances. In most cases it should be called at some point in the
                implementation of the <code>@link
                //apple_ref/occ/instm/MPLayout(MPSubclassImplement)/childLayoutAtIndex:@/link</code>
                method.
 
    @param      parent
                The receiver's parent.
 
    @return     A newly initialized <code>MPLayout</code> instance.
 */
- (instancetype)initWithParent:(MPLayout *)parent;


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


/*!
    @property   expressionView
    @abstract   The receiver's expression view.
 
    @discussion The expression view is the view in which the receivin
                <code>MPLayout</code> instance draws itself into. Normally you
                should not call the setter of this property. It is automatically
                assigned when the <code>@link
                //apple_ref/occ/instp/MPExpressionView/expressionStorage@/link</code>
                property of the respective <code>@link
                //apple_ref/occ/cl/MPExpressionView@/link</code> instance is set.
 */
@property (nonatomic, weak) MPExpressionView *expressionView;


/*!
    @method     font
    @abstract   Returns the receiver's context inferred font.
 
    @discussion The context inferred font is the normal font using the context
                inferred font size. Normally this method is used for any textual
                content of a layout.

    @return     The font the receiver should use for rendering textual content.
 */
- (NSFont *)font;


/*!
    @method     normalFontWithSize:
    @abstract   Returns a <code>NSFont</code> object that represents the normal
                font in the specified <code>size</code>.
 
    @discussion Instead of this method you want to use <code>@link
                //apple_ref/occ/instm/MPLayout/font@/link</code> instead in most
                cases.
 
    @param      size
                The size of the font.
 
    @return     A <code>NSFont</code> object that can be used to render textual
                content of the receiver in the specified <code>size</code>.
 */
- (NSFont *)normalFontWithSize:(CGFloat)size;


/*!
    @method     specialFontWithSize:
    @abstract   Returns a <code>NSFont</code> object that represents the special
                font in the specified <code>size</code>.
 
    @discussion Use fonts returned by this method only if you need to emphasize
                text that is surrounded by other text that uses the normal font.
 
    @param      size
                The size of the font.
 
    @return     A <code>NSFont</code> object that can be used to render special
                textual content of the receiver in the specified
                <code>size</code>.
 */
- (NSFont *)specialFontWithSize:(CGFloat)size;


/*!
    @method     contextInferredFontSize
    @abstract   Returns the appropriate font size for the receiver based on the
                context it will be rendered in.
 */
- (CGFloat)contextInferredFontSize;


/*!
    @method     normalFontSize
    @abstract   Returns the font size that is used in the root expression.
 */
- (CGFloat)normalFontSize;


/*!
    @method     smallFontSize
    @abstract   Returns the font size that is used in expressions that are using
                the small size.
 */
- (CGFloat)smallFontSize;


#pragma mark Cache Tree
/*!
 @methodgroup Cache Tree
 */


/*!
    @property   parent
    @abstract   The receiver's parent layout.
 
    @discussion The parent layout is most likely the one that created the
                receiver and is responsible for setting its attributes
                appropriately based on the rendering context.
 */
@property (readonly, nonatomic, weak) MPLayout *parent;


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


/*!
    @method     chacableObjectForIndex:generator:
    @abstract   Returns the cached object for the specified <code>index</code> or
                creates one if it does not exist.
 
    @discussion This method only returns <code>nil</code> if the
                <code>generator</code> returns <code>nil</code>.
 
    @param      index
                The index of the cached object to retrieve.
 
    @param      generator
                This block is executed if there is no cached object at
                <code>index</code>. The result of this block is then cached at
                the <code>index</code>.
 
    @return     The cached object for <code>index</code> or the result of the
                <code>generator</code> if there is no cached object.
 */
- (id)cachableObjectForIndex:(NSUInteger)index
                   generator:(id(^)())generator;


/*!
    @method     clearCacheInRange:replacementLength:
    @abstract   Removes the objects in the specified from the cache and moves all
                objects at subsequent indexes.
 
    @discussion The objects at subsequent indexes are moved
                <code>replacementLength - range.length</code> places. If
                <code>replacementLength</code> is greater than
                <code>range.length</code> they are moved to smaller indexes.
 
    @param      range
                The range of objects to be removed.
 
    @param      replacementLength
                The number of indexes replacing the ones specified by
                <code>range</code>.
 */
- (void)clearCacheInRange:(NSRange)range
        replacementLength:(NSUInteger)replacementLength;


/*!
    @method     invalidate
    @abstract   Invalidates the receiver.
 
    @discussion Invalidating the receiver causes him to discard all cached
                information about itself and send a <code>invalidate</code>
                message to its parent. The cached information about the
                receiver's children is <b>not</b> discarded. Use <code>@link
                //apple_ref/occ/instm/MPLayout/clearCacheInRange:replacementLength:@/link</code>
                for that purpose.
 */
- (void)invalidate;


/*!
    @method     childLayoutAtIndexPath:
    @abstract   Returns the child layout at the specified index path.
 
    @discussion If the <code>indexPath</code> is empty the receiver is returned.
                If the child layout the index path specifies is not yet created
                this method should do so and add it to the cache of the
                respective parent layout.
 
    @param      indexPath
                The index path of the child.
 
    @return     The child at the specified index path.
 */
- (MPLayout *)childLayoutAtIndexPath:(NSIndexPath *)indexPath;


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


/*!
    @method     createLineForString:
    @abstract   Renders the specified string.
 
    @discussion This method uses the normal font and the context inferred font
                size to render the string. In most cases this is the appropriate 
                method for rendering textual content in a layout.
 
    @param      aString
                The string to be rendered.
 
    @return     A <code>CTLineRef</code> representing the rendered string.
 */
- (CTLineRef)createLineForString:(NSString *)aString;


/*!
    @method     createLineForString:emphasize:
    @abstract   Renders the specified string.
 
    @discussion This method uses either the normal or the special font depending
                on the <code>emphasize</code> flag. It uses the context inferred
                font size.
 
    @param      aString
                The string to be rendered.
 
    @param      emphasize
                A flag indicating whether the special font should be used.
 
    @return     A <code>CTLineRef</code> representing the rendered string.
 */
- (CTLineRef)createLineForString:(NSString *)aString emphasize:(BOOL)emphasize;


/*!
    @method     createLineForString:usingFont:
    @abstract   Renders the specified string in the specified font.
 
    @discussion In most cases you do should prefer <code>@link
                //apple_ref/occ/instm/MPLayout/createLineForString:@/link</code>
                or <code>@link
                //apple_ref/occ/instm/MPLayout/createLineForString:emphasize:@/link</code>
                over this method.
 
    @param      aString
                The string to be rendered.
 
    @param      font
                The font to render <code>aString</code> in.
 
    @return     A <code>CTLineRef</code> representing the rendered string.
 */
- (CTLineRef)createLineForString:(NSString *)aString usingFont:(NSFont *)font;


/*!
    @property   flipped
    @abstract   This property indicates whether the receiver uses a flipped
                layout.
 
    @discussion The value of this property should be the same in the receiver,
                its parent and all of its children.
 */
@property (nonatomic, getter = isFlipped) BOOL flipped;


/*!
    @property   usesSmallSize
    @abstract   This property indicates whether the receiver uses the small font
                size.
 
    @discussion This property is used to determine the <code>@link
                //apple_ref/occ/instm/MPLayout/contextInferredFontSize@/link</code>.
                If this value is <code>YES</code> all of the receiver's children
                should also use the small size. If not the children may or may
                not (depending on the actual layout implementation) use the small
                size.
 */
@property (nonatomic) BOOL usesSmallSize;


/*!
    @method     bounds
    @abstract   Returns the receiver's bounds.
 
    @discussion If the receiver has cached bounds it will return the cached
                value. The cached value can be removed using the <code>@link
                //apple_ref/occ/instm/MPLayout/invalidate@/link</code> method. If
                there is no cached value this method will generate a new cached
                value by calling the <code>@link
                //apple_ref/occ/instm/MPLayout(MPSubclassImplement)/generateBounds@/link</code>
                method.

    @return     The receiver's bounds.
 */
- (NSRect)bounds;


/*!
    @method     boundingRectForRangePath:
    @abstract   Returns the rect relative to the origin of the receiver's bounds
                that contains the symbols identified by the specified range path.
 
    @discussion Use this method to determine where the user's selection should be
                drawn at.
 
    @param      rangePath
                The range path whose bounds are needed.
 
    @return     The rectangle containing the symbols identified by
                <code>rangePath</code>.
 */
- (NSRect)boundingRectForRangePath:(MPRangePath *)rangePath; /* if rangePath.length is 0 the returned rect will have a width of 0 */


/*!
    @method     drawAtPoint:
    @abstract   Draws the receiver at the specified point.
 
    @discussion If the receiver returns <code>NO</code> from its <code>@link
                //apple_ref/occ/instm/MPLayout(MPSubclassImplement)/drawsChildrenManually@/link</code>
                method this method also draws every single child of the receiver.
                The location the children are drawn at is determined by the
                <code>@link
                //apple_ref/occ/instm/MPLayout(MPSubclassImplement)/offsetOfChildLayoutAtIndex:@/link</code>
                method.
 
    @param      point
                The point the receiver is to be drawn at.
 */
- (void)drawAtPoint:(NSPoint)point;

@end



/*!
    @category   MPLayout (MPSubclassImplement)
    @abstract   The methods in this category must be implemented by any concrete
                subclass of <code>MPLayout</code>.
 */
@interface MPLayout (MPSubclassImplement)

/*!
    @method     numberOfChildren
    @abstract   Returns the number of sublayouts of the receiver.
 
    @discussion This method must be implemented by subclasses.
 */
- (NSUInteger)numberOfChildren;


/*!
    @method     drawsChildrenManually
    @abstract   Returns whether the receiver wants to take over responsibility of
                drawing its children.
 
    @discussion If you return <code>YES</code> from this method
                <code>MPLayout</code> does not draw the receiver's children. The
                receiver's <code>//apple_ref/occ/intfm/MPLayout/draw</code>
                method has to implement that functionality. If you return
                <code>NO</code> from this method the receiver must not draw its
                children in the <code>//apple_ref/occ/intfm/MPLayout/draw</code>
                method.
 
                This method may be implemented by subclasses to opt in and change
                the default behaviour.

    @return     <code>YES</code> if the receiver draws its children
                automatically, <code>NO</code> otherwise. The default
                implementation returns <code>NO</code>.
 */
- (BOOL)drawsChildrenManually;


/*!
    @method     childLayoutAtIndex:
    @abstract   Returns the receiver's child at the specified index.
 
    @discussion As long as the specified <code>index</code> does not exceed the
                receiver's number of children, this method should always return a
                value based on the following criteria:
                1. If the receiver represents a <code>@link
                   //apple_ref/occ/cl/MPFunction@/link</code> instance it should
                   always return a valid <code>MPLayout</code> instance.
                2. If the receiver represents a <code>@link
                   //apple_ref/occ/cl/MPExpression@/link</code> instance it
                   returns only a valid <code>MPLayout</code> instance if the
                   represented expression's element at <code>index</code> is a
                   function.

                This method should cache any generated child layout and use the
                cache whenever possible in order to reduce resources needed for
                rendering.
 
                This method must be implemented by subclasses.
 
    @param      index
                The index of the requested child layout.
 
    @return     A <code>MPLayout</code> instance representing the respective
                child of the object that is represented by the receiver, or
                <code>nil</code> if the child represents a string element in an
                expression.
 */
- (MPLayout *)childLayoutAtIndex:(NSUInteger)index;


/*!
    @method     generateBounds
    @abstract   Calculates the receiever's bounds.
 
    @discussion This method should not use any cached values. Caching of the
                calculated bounds is done automatically by the
                <code>MPLayout</code> class. This method is only called if the
                receiving <code>MPLayout</code> instance has been invalidated and
                the receiver's bounds have been requested.
 
                This method must be implemented by subclasses.

    @return     The receiver's bounds.
 */
- (NSRect)generateBounds;


/*!
    @method     boundingRectForRange:
    @abstract   Returns the rectangle that encloses the symbols in the specified
                range.

    @discussion This method is called to calculate the rectangle that encloses
                the user's selection. If the specified <code>range</code> has a
                length of <code>0</code> the returned rectangle should be the
                bounds of the caret (which has a width of <code>0</code>). The
                height and y-origin of the returned rectangle should be equal to
                the respective values of the receiver.
 
                This method must be implemented by subclasses.
 
    @param      range
                The range of the selection whose bounds should be calculated.
 
    @return     A rectangle enclosing the user's selection.
 */
- (NSRect)boundingRectForRange:(NSRange)range;


/*!
    @method     offsetOfChildLayoutAtIndex:
    @abstract   Returns the location of the child layout at the specified index.
 
    @discussion This method must be implemented by subclasses even if the
                subclass returns <code>YES</code> from the <code>@link
                //apple_ref/occ/instm/MPLayout(MPSubclassImplement)/drawsChildrenManually@/link</code>
                method.
 
    @param      index
                The index of the child whose location is to be calculated.
 
    @return     The location of the child layout at the specified index relative
                to the receiver.
 */
- (NSPoint)offsetOfChildLayoutAtIndex:(NSUInteger)index;


/*!
    @method     indexPathForMousePoint:
    @abstract   Performs hit testing.
 
    @discussion This method tests the specified <code>point</code> against all of
                its children and returns the index path (relative to the index
                path of the receiver) that identifies the location inside the
                expression tree the user selected. The specified point must be
                inside of the receiver and must be specified relatively to the
                receiver. If the specified point is not inside any of the
                receiver's children but in the receiver itself the returned index
                path must contain one index specifiying the location in the
                receiver.
 
                This method must be implemented by subclasses.
 
    @param      point
                The point inside the receiver that is to be tested.
 
    @return     The index path that points to the point the user selected. All
                indexes in the path except for the last one are specified in the
                element reference frame. The last index is specified in the
                symbol reference frame.
 */
- (NSIndexPath *)indexPathForMousePoint:(NSPoint)point;


/*!
    @method     draw
    @abstract   Draws the receiver.
 
    @discussion If the receiver returns <code>YES</code> from its <code>@link
                //apple_ref/occ/instm/MPLayout(MPSubclassImplement)/drawsChildrenManually@/link</code>
                method the implementation of this method must also draw the
                receiver's children.
 
                This method must be implemented by subclasses. If the receiver
                does not have anything to draw except for its children you should
                implement this method with an empty method body.
 */
- (void)draw;

@end