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

#import <GlyphsCore/GSBase.h>
#import <GlyphsCore/GSBaseObject.h>
#import <GlyphsCore/GSContainerProtocol.h>
#import <GlyphsCore/GSCompositingOperation.h>

#ifndef GLYPHS_LITE
@class GSGradient;
@class GSShadow;
#endif

/// All shape types when filtering a collection of shapes.
#define GSShapeTypeAll 0xff

/// The kind of content represented by a shape.
typedef NS_ENUM(unsigned short, GSShapeType) {
	/// A ``GSPath``.
	GSShapeTypePath = 1 << 1,
	/// A ``GSComponent``.
	GSShapeTypeComponent = 1 << 2,
	/// A ``GSImage``.
	GSShapeTypeImage = 1 << 3,
	/// A ``GSTextElement``.
	GSShapeTypeText = 1 << 4,
	/// A ``GSShapeGroup``.
	GSShapeTypeGroup = 1 << 7,
};

/// The construction used at an open stroke endpoint.
typedef NS_ENUM(uint8_t, GSLineCapStyle) {
	/// Ends the stroke at the path endpoint.
	///
	/// Equivalent to `NSLineCapStyleButt`.
	GSLineCapStyleButt = 0,
	/// Extends the stroke with a semicircular cap.
	///
	/// Equivalent to `NSLineCapStyleRound`.
	GSLineCapStyleRound = 1,
	/// Extends the stroke with a square cap.
	///
	/// Equivalent to `NSLineCapStyleSquare`.
	GSLineCapStyleSquare = 2,
	/// Ends the stroke with an inset semicircular cap.
	GSLineCapStyleRoundInset = 3,
	/// Aligns the cap to the nearest coordinate axis.
	GSLineCapStyleAlignToAxis = 4,
	/// Aligns the cap to the x-axis.
	GSLineCapStyleAlignToAxisX = 5,
	/// Aligns the cap to the y-axis.
	GSLineCapStyleAlignToAxisY = 6,
};

/// The construction used to connect consecutive stroke segments.
typedef NS_ENUM(uint8_t, GSLineJoinStyle) {
	/// Extends the segment edges until they intersect.
	GSLineJoinStyleMiter = 0,
	/// Connects the segment edges with a circular arc.
	GSLineJoinStyleRound = 1,
	/// Connects the segment edges with a straight edge.
	GSLineJoinStyleBevel = 2,
	/// Connects the complete segment ends with a circular arc.
	GSLineJoinStyleRoundFull = 3,
};

/// How a shape masks preceding shapes in the same composition unit.
typedef NS_ENUM(uint8_t, GSMaskType) {
	/// Does not use the shape as a mask.
	GSMaskTypeNone = 0,
	/// Removes the area covered by the shape.
	GSMaskTypeSubtract = 1,
	/// Retains only the area covered by the shape.
	GSMaskTypeIntersect = 2,
};

NS_ASSUME_NONNULL_BEGIN

extern NSString *const GSPathFillColorKey GSDeprecated("GSShapeKeyFillColor");
extern NSString *const GSPathFillKey GSDeprecated("GSShapeKeyFill");
extern NSString *const GSPathFillPatternFileKey GSDeprecated("GSShapeKeyFillPatternFile");
extern NSString *const GSPathFillPatternBlendModeKey GSDeprecated("GSShapeKeyFillPatternBlendMode");
extern NSString *const GSPathFillPatternOffsetKey GSDeprecated("GSShapeKeyFillPatternOffset");
extern NSString *const GSPathFillPatternScaleKey GSDeprecated("GSShapeKeyFillPatternScale");
extern NSString *const GSPathFillPatternAngleKey GSDeprecated("GSShapeKeyFillPatternAngle");
extern NSString *const GSPathGradientKey GSDeprecated("GSShapeKeyFillGradient");
extern NSString *const GSPathStrokeColorKey GSDeprecated("GSShapeKeyStrokeColor");
extern NSString *const GSPathStrokeGradientKey GSDeprecated("GSShapeKeyStrokeGradient");
extern NSString *const GSPathStrokeWidthKey GSDeprecated("GSShapeKeyStrokeWidth");
extern NSString *const GSPathStrokeHeightKey GSDeprecated("GSShapeKeyStrokeHeight");
extern NSString *const GSPathStrokePositionKey GSDeprecated("GSShapeKeyStrokePosition");
extern NSString *const GSPathStrokeLineCapStartKey GSDeprecated("GSShapeKeyStrokeLineCapStart");
extern NSString *const GSPathStrokeLineCapEndKey GSDeprecated("GSShapeKeyStrokeLineCapEnd");
extern NSString *const GSPathStrokeLineJoinKey GSDeprecated("GSShapeKeyStrokeLineJoin");
extern NSString *const GSPathShadowKey GSDeprecated("GSShapeKeyShadow");
extern NSString *const GSPathInnerShadowKey GSDeprecated("GSShapeKeyInnerShadow");
extern NSString *const GSPathNoiseKey GSDeprecated("GSShapeKeyNoise");
extern NSString *const GSShapeMaskKey GSDeprecated("GSShapeKeyMask");
extern NSString *const GSComponentReversePathsKey GSDeprecated("GSShapeKeyReversePaths");

extern NSString *const GSAttributesKey;
extern NSString *const GSPathKey;
extern NSString *const GSShapeNameKey;

extern NSString *const GSPathMergeOpenPaths;

extern NSString *const GSShapeFocusKey;
extern NSString *const GSPathFocusKey GSDeprecated("GSShapeFocusKey");
extern NSString *const GSShapeIdentifier;
extern NSString *const GSPieceSettingsGroup;

extern NSString *const GSUserDataKey;
extern NSString *const GSHobbyInfoKey;
extern NSString *const GSHOIInfoKey;

typedef NSString *GSShapeGroupID NS_TYPED_EXTENSIBLE_ENUM NS_SWIFT_NAME(GSShapeGroup.ID);
/// The group ID reserved for the root group of a layer.
static GSShapeGroupID const GSShapeGroupIDRoot = @"";

@protocol GSPenProtocol;
@class GSShapeGroup;

/// An abstract base class for graphical elements stored in a glyph layer.
///
/// Shapes are stacked to form the composition of a ``GSLayer``.
///
/// When the parent (layer) of a shape is not set, many of it’s APIs cannot work to their full extend.
/// This is because a shape frequently needs to reference layer metrics, glyph properties, for other font metadata.
@interface GSShape : GSBaseObject

/// Creates a concrete shape from a Glyphs file dictionary representation.
///
/// Call this initializer on a concrete `GSShape` subclass.
///
/// - Parameter formatVersion: The Glyphs file format version used to interpret the representation.
- (nullable instancetype)initWithDict:(NSDictionary<NSString *, id> *)dict format:(GSFormatVersion)formatVersion;

/// Finishes loading after the shape has been attached to its layer.
///
/// Call this method after the complete layer contents have been loaded.
///
/// - Parameter error: On failure, an error describing why loading could not be completed.
/// - Parameter formatVersion: The Glyphs file format version used to interpret the representation.
/// - Returns: Whether loading was completed successfully.
- (BOOL)postRead:(NSError * _Nullable * _Nullable)error format:(GSFormatVersion)formatVersion;

/// Whether another shape has the same concrete class.
- (BOOL)isEqualToShape:(GSShape *)other;

/// The direct containing group in the layer’s shape hierarchy.
///
/// An ungrouped shape in a layer belongs to the layer’s root group.
/// The value is `nil` when the shape does not belong to a layer or its parent group cannot be resolved.
@property (nonatomic, readonly, nullable) GSShapeGroup *parentGroup;

/// Invokes a block for each containing group, from the direct parent through the root group.
///
/// No groups are enumerated when ``parentGroup`` is `nil`.
/// Set `stop` to `YES` to end the enumeration early.
///
/// - Parameter code: The block to invoke for each containing group.
- (void)enumerateParentGroupsWithBlock:(void (^)(GSShapeGroup *group, BOOL *stop))code NS_SWIFT_DISABLE_ASYNC;

/// A structural description used to determine layer compatibility.
///
/// The description does not represent complete shape equality.
/// `nil` indicates that the shape does not participate in compatibility comparisons.
- (nullable NSString *)compareString;

/// Applies an affine transformation and aligns the resulting coordinates to a grid without registering undo or notifying the layer.
///
/// - Parameter grid: The grid settings used to align the coordinates.
- (void)transformFast:(NSAffineTransform *)transform grid:(GSGridSettings)grid;

/// Applies an affine transformation to the complete shape.
- (void)transform:(NSAffineTransform *)transform;

/// Applies an affine transformation to selected elements of the shape.
///
/// A path selection contains nodes, while a transformable shape selection contains the shape itself.
/// Pass `nil` to transform the complete shape.
///
/// - Parameter selection: The elements to transform, or `nil` for the complete shape.
- (void)transform:(NSAffineTransform *)transform selection:(nullable NSOrderedSet *)selection;

/// Copies corresponding positions and transformation values from another shape.
///
/// Both shapes must have the same concrete type and compatible structure.
/// Other shape content is preserved.
///
/// - Parameter otherPath: The shape providing the geometric values.
- (void)getPositionsFromShape:(GSShape *)otherPath;

/// Translates the complete shape by an offset.
- (void)moveWithPoint:(NSPoint)offset;

/// Whether the shape is excluded from drawing and generated outlines.
@property (nonatomic, getter=isHidden) BOOL hidden;

/// An optional label for identifying the shape in user interfaces.
@property (nonatomic, strong, nullable) NSString *nameUI;

/// The kind of content represented by the concrete shape.
@property (readonly) GSShapeType shapeType;

/// Scales the stroke width and explicit stroke height by a factor.
///
/// Shape geometry is not changed.
///
/// - Parameter scale: The factor by which to scale the attributes.
- (void)scaleAttributes:(CGFloat)scale;

/// Reads the shape’s stroke style into optional output parameters.
///
/// Missing settings use a centered stroke with butt caps, miter joins, and no fill or mask.
/// Width and height receive `NSNoSelectionMarker` when their respective expressions are absent.
///
/// - Parameter width: On return, the stroke width expression.
/// - Parameter height: On return, the stroke height expression.
/// - Parameter pos: On return, the stroke position from `-1` on the left to `1` on the right of the path direction.
/// - Parameter angle: On return, the stroke offset angle in degrees.
/// - Parameter capStart: On return, the cap at the start of an open path.
/// - Parameter capEnd: On return, the cap at the end of an open path.
/// - Parameter join: On return, the construction used at joins.
/// - Parameter fill: On return, whether the path remains filled when it has a stroke.
/// - Parameter maskType: On return, the shape’s mask behavior.
/// - Returns: `YES`.
- (BOOL)getStrokeSettingsWidth:(NSString *_Nullable*_Nullable)width height:(NSString *_Nullable*_Nullable)height pos:(nullable CGFloat *)pos angle:(nullable CGFloat *)angle capStart:(nullable GSLineCapStyle *)capStart capEnd:(nullable GSLineCapStyle *)capEnd join:(nullable GSLineJoinStyle *)join fill:(nullable BOOL *)fill maskType:(nullable GSMaskType *)maskType;

/// Writes the shape to a pen.
///
/// Closed and open paths are written to the same pen.
/// No auxiliary output is produced.
- (void)drawInPen:(NSObject<GSPenProtocol> *)pen;

/// Writes the shape to separate pens for closed paths, open paths, and auxiliary output.
///
/// Closed paths are written to `pen` and open paths are written to `openPen`.
/// When `openPen` is `nil`, open paths produce no primary output.
/// The secondary pen receives auxiliary geometry generated while resolving path decorations and masks.
/// The extra handles array is available to drawing preparation callbacks.
///
/// - Parameter openPen: The destination for open paths, or `nil` to omit them.
/// - Parameter secondPen: The destination for auxiliary geometry, or `nil` to omit it.
/// - Parameter extraHandles: A mutable array for additional handles produced while preparing the shape.
- (void)drawInPen:(NSObject<GSPenProtocol> *)pen openPen:(nullable NSObject<GSPenProtocol> *)openPen secondaryPen:(nullable NSObject<GSPenProtocol> *)secondPen extraHandles:(nullable NSMutableArray *)extraHandles;

/// Writes the basic shape representation to a pen without resolving path decorations.
- (void)drawSimpleInPen:(NSObject<GSPenProtocol> *)pen;

/// Writes the basic shape representation to a pen, optionally omitting segment object information.
///
/// The fast option affects paths when the pen implements callbacks that accept objects associated with segments.
///
/// - Parameter fast: Whether to omit segment object information.
- (void)drawSimpleInPen:(NSObject<GSPenProtocol> *)pen fast:(BOOL)fast;

// MARK: Access Attributes
#ifndef GLYPHS_LITE
/// The solid color used for the fill.
///
/// The fill gradient takes precedence when both paints are set.
/// Set the value to `nil` to remove the solid fill paint.
///
/// - SeeAlso: ``GSShapeKeyFillColor``
@property (strong, nonatomic, nullable) NSColor *fillColor;
/// The gradient used for the fill.
///
/// The gradient takes precedence over the fill color.
/// Set the value to `nil` to remove the gradient fill paint.
///
/// - SeeAlso: ``GSShapeKeyFillGradient``
@property (strong, nonatomic, nullable) GSGradient *fillGradient;
/// The effective fill paint.
///
/// Either an `NSColor` for a solid color paint, `GSGradient` for a gradient paint, or `nil` for no paint.
@property (strong, nonatomic, nullable) id fillPaint;

/// The solid color used for the stroke.
///
/// The stroke gradient takes precedence when both paints are set.
/// Set the value to `nil` to remove the solid stroke paint.
///
/// - SeeAlso: ``GSShapeKeyStrokeColor``
@property (strong, nonatomic, nullable) NSColor *strokeColor;
/// The gradient used for the stroke.
///
/// The gradient takes precedence over the stroke color.
/// Set the value to `nil` to remove the gradient stroke paint.
///
/// - SeeAlso: ``GSShapeKeyStrokeGradient``
@property (strong, nonatomic, nullable) GSGradient *strokeGradient;
/// The effective stroke paint.
///
/// Either an `NSColor` for a solid color paint, `GSGradient` for a gradient paint, or `nil` for no paint.
@property (strong, nonatomic, nullable) id strokePaint;
#endif
/// The horizontal size expression used to expand the path into a stroke.
///
/// A value enables the stroke.
/// Set the value to `nil` to remove the stroke.
///
/// - SeeAlso: ``GSShapeKeyStrokeWidth``
@property (assign, nonatomic, nullable) id strokeWidth;
/// The vertical size expression used to expand the path into a stroke.
///
/// The stroke width expression is returned when no separate height expression is set.
///
/// - SeeAlso: ``GSShapeKeyStrokeHeight``
@property (assign, nonatomic, nullable) id strokeHeight;
/// The rotation angle of the stroke expansion in degrees.
///
/// - SeeAlso: ``GSShapeKeyStrokeOffsetAngle``
@property (assign, nonatomic, nullable) id strokeOffsetAngle;
/// The stroke position from `-1` on the left to `1` on the right of the path direction.
///
/// A value of `0` centers the stroke on the path.
///
/// - SeeAlso: ``GSShapeKeyStrokePosition``
@property (assign, nonatomic, nullable) id strokePosition;
/// Whether the path remains filled when it has a stroke.
///
/// This setting has no effect when no stroke width is set.
///
/// - SeeAlso: ``GSShapeKeyFill``
@property (assign, nonatomic, getter=isFilled) BOOL filled;

/// The cap at the end of an open path.
///
/// The default is ``GSLineCapStyle/butt``.
///
/// - SeeAlso: ``GSShapeKeyStrokeLineCapEnd``
@property (assign, nonatomic) GSLineCapStyle lineCapEnd;
/// The cap at the start of an open path.
///
/// The default is ``GSLineCapStyle/butt``.
///
/// - SeeAlso: ``GSShapeKeyStrokeLineCapStart``
@property (assign, nonatomic) GSLineCapStyle lineCapStart;
/// The construction used to connect consecutive stroke segments.
///
/// The default is ``GSLineJoinStyle/miter``.
///
/// - SeeAlso: ``GSShapeKeyStrokeLineJoin``
@property (assign, nonatomic) GSLineJoinStyle lineJoin;

#ifndef GLYPHS_LITE
/// The shadow cast by the shape.
///
/// Set the value to `nil` to remove the shadow.
///
/// - SeeAlso: ``GSShapeKeyShadow``
@property (strong, nonatomic, nullable) GSShadow *outerShadow;
/// The shadow drawn inside the shape.
///
/// Set the value to `nil` to remove the shadow.
///
/// - SeeAlso: ``GSShapeKeyInnerShadow``
@property (strong, nonatomic, nullable) GSShadow *innerShadow;
#endif

/// The alpha applied to the complete rendered shape.
///
/// Values range from `0` for fully transparent to `1` for fully opaque.
/// Values outside this range are clamped.
/// The default is `1`.
///
/// - SeeAlso: ``GSShapeKeyOpacity``
@property (assign, nonatomic) CGFloat opacity;
/// How the rendered shape is combined with the content behind it.
///
/// The default is ``GSCompositingOperationSourceOver``.
///
/// - SeeAlso: ``GSShapeKeyCompositingOperation``
@property (strong, nonatomic) GSCompositingOperation compositingOperation;

/// How the shape masks the preceding shape or shapes in the same composition unit.
///
/// A mask must follow a non-mask shape.
/// Consecutive masks apply to the same preceding composition unit.
/// The default is ``GSMaskType/none``.
///
/// - SeeAlso: ``GSShapeKeyMask``
@property (assign, nonatomic) GSMaskType maskType;

/// The ID of the parent group.
///
/// Returns ``GSShapeGroupIDRoot`` for top-level shapes.
///
/// - SeeAlso: ``GSShapeKeyParentGroupId``
@property (strong, nonatomic) GSShapeGroupID parentGroupId;

@end

NS_ASSUME_NONNULL_END
