//
//  GSShapeGroup.h
//  GlyphsCore
//
//  Created by Georg Seifert on 07.04.19.
//  Copyright © 2019 schriftgestaltung.de. All rights reserved.
//

#import <GlyphsCore/GSContainerProtocol.h>
#import <GlyphsCore/GSShape.h>

NS_ASSUME_NONNULL_BEGIN

/// A shape group combines zero or more shapes into a single shape, forming a shape tree.
///
/// - The shapes in the `shapes` array of a group are its **direct members**.
///   Direct members of a group have a relative depth of `1`.
/// - The **members** of a group are all shapes that the group contains, directly, or more deeply in nested groups.
///   Members of a group have a relative depth greater than or equal to `1`.
@interface GSShapeGroup : GSShape <GSContainerProtocol>

/// Creates a new shapes group with a given ID.
///
/// The ``shapes`` array is set by the ``GSLayer`` depending on the ``GSShapeKeyParentGroupId`` attribute value of other shapes.
///
/// - Parameter groupId: Use a per-layer unique value, for example, `[[NSUUID new] UUIDString]`. A group ID of the empty string is reserved for root groups (``GSShapeGroupRootGroupId``, use ``rootGroupWithParent:`` instead).
- (instancetype)initWithId:(GSShapeGroupID)groupId;

/// Creates the root group for a layer.
///
/// A layer has exactly one root group.
///
/// - Parameter parent: The layer that owns the root group.
+ (instancetype)rootGroupWithParent:(nullable GSLayer *)parent;

/// The per-layer identifier of the group.
///
/// The empty string is reserved for the root group.
@property (strong) GSShapeGroupID groupId;

/// The direct members of the group.
///
/// Use ``enumerateMembersWithBlock:`` to process all members, including nested shapes.
///
/// Shapes are externally immutable.
/// Use ``GSLayer/createShapeGroupFromShapes:`` to create a group with shapes.
@property (strong) NSArray<GSShape *> *shapes;

/// Whether the group is the root of the shape tree.
///
/// - Complexity: O(1)
@property (nonatomic, readonly, getter=isRoot) BOOL root;

/// Returns the relative depth of the shape within the group.
///
/// `0` if the shape is the receiver and `-1` if the group does not contain the shape.
///
/// - Complexity: O(*d*) where *d* is the group nesting depth of the shape.
///   O(*n*) when the group or shape has a layer of `nil` where *n* is the number of members of the group.
- (NSInteger)depthOfShape:(GSShape *)shape NS_REFINED_FOR_SWIFT;

/// Returns whether the given shape is a member of the group.
///
/// - Complexity: O(log(*n*)) where *n* is the number of members of the group.
///   O(*n*) when the group or shape has a layer of `nil`.
- (BOOL)containsShape:(GSShape *)shape;

/// The range of layer shape indices that includes all group members and the index of the group shape itself (which is always at the last index of the range).
///
/// Returns a range with a location of `NSNotFound` if the group is not part of a layer.
///
/// - Complexity: O(*n*) where *n* is the number of shapes in the layer.
- (NSRange)groupIndexRange NS_REFINED_FOR_SWIFT;

/// Creates a copy of the group and all of its members.
///
/// The copy receives a new group ID.
- (instancetype)deepCopy;

/// Calls the block with every member of the group, including nested shapes.
///
/// The order is the same as the order on `layer.shapes`, i.e., the compositing order (bottom to top).
///
/// This enumeration also works when the shape group does not belong to a layer.
- (void)enumerateMembersWithBlock:(void (^)(GSShape *member))code;

/// Draw a subset of the group into pens.
///
/// This is the same as the regular ``GSShape/drawInPen:openPen:secondaryPen:extraHandles:`` API, but allowing for filtering based on the base shape of the composition units (see ``enumerateMembersWithBlock:``).
/// Shapes for which `NO` is returned from `filter` are ignored in the main pen drawing.
/// Masks are always drawn into the secondary pen, including the masks of shapes for which `filter` returned `NO`.
- (void)drawInPen:(nonnull NSObject<GSPenProtocol> *)pen openPen:(nullable NSObject<GSPenProtocol> *)openPen supporting:(GSDrawStackFeature)supportedFeatures secondaryPen:(nullable NSObject<GSPenProtocol> *)secondPen extraHandles:(nullable NSMutableArray *)extraHandles filter:(nullable BOOL (^)(GSShape *))filter;

@end

NS_ASSUME_NONNULL_END
