/** \file GSNode.h */
//
//  GSNode.h
//  GlyphsCore
//
//  Created by Georg Seifert on 05.04.09.
//  Copyright 2009 schriftgestaltung.de. All rights reserved.
//

#import <GlyphsCore/GSBaseObject.h>

NS_ASSUME_NONNULL_BEGIN

extern NSString *const GSShapeKeyStrokeAttributes;
extern NSString *const GSShapeKeyStrokeAngleIn;
extern NSString *const GSShapeKeyStrokeAngleOut;
extern NSString *const GSShapeKeyStrokeWidthIn;
extern NSString *const GSShapeKeyStrokeWidthOut;
extern NSString *const GSShapeKeyStrokeHeightIn;
extern NSString *const GSShapeKeyStrokeHeightOut;
extern NSString *const GSShapeKeyStrokePosIn;
extern NSString *const GSShapeKeyStrokePosOut;
extern NSString *const GSShapeKeyStrokeAngle;

extern NSString *const GSNodeKeyCornerRadius;

#ifndef GLYPHS_LITE
extern NSString *const GSPenPointsKey;
#endif

@class GSPath;
@class GSHobbyNodeInfo;
@class GSPenPoint;

/// A side of a node along a path.
typedef NS_CLOSED_ENUM(uint8_t, GSNodeSide) {
	/// The side before the node on the path.
	GSNodeSideIncoming,
	/// The side after the node on the path.
	GSNodeSideOutgoing,
} NS_SWIFT_NAME(GSNode.Side);

/// An on-curve or off-curve point of a path.
///
/// Nodes form the ordered sequence that describes a path.
/// There is a single off-curve node type and multiple different on-curve node types.
/// An on-curve node lies on the path, and its type describes the path segment ending at that node.
/// An off-curve node, also known as a handle, controls the curvature of a path segment and does not necessarily lie on the path.
///
/// For every on-curve node, a incoming path segment ends and an outgoing path segment begins.
/// The node connection describes the relationship between these two segments.
///
/// A node can exist without a parent path, in which case its layer and glyph are also unavailable.
///
/// Use path APIs to add or remove nodes so that the node and path remain consistent.
/// Regular mutation APIs register undo and notify the path when applicable.
/// Fast APIs omit this behavior and require the caller to manage related changes.
///
/// Conceptually, `GSNode` is a ``GSElement``, but it does not inherit from `GSElement` for optimizations reasons.
@interface GSNode : NSObject <NSCoding, NSCopying, GSUserDataProtocol, GSSelectableElementProtocol> {
#if DEBUG
	__unsafe_unretained GSPath *_parent;
#else
	__weak GSPath *_parent;
#endif
	NSMutableDictionary <GSShapeKey, id>*_attributes;

  @public
	NSPoint _position;
	NSPoint _positionPrecise;
	GSNodeType _type;
	GSNodeConnection _connection;
	BOOL _locked;
	GSElementOrientation _orientation;
}

/// The orientation of the node position.
@property (nonatomic) GSElementOrientation orientation;

#ifndef GLYPHS_VIEWER
/// Returns the undo manager of the containing glyph.
@property (nonatomic, readonly, nullable) NSUndoManager *undoManager;
/// Returns the undo manager of the containing glyph, if undo is enabled for the glyph.
@property (nonatomic, readonly, nullable) NSUndoManager *undoManagerCheck;
#endif

/// Notifies the path that the node changed.
- (void)elementDidChange:(id)element;

#if DEBUG
/// The path containing the node, or `nil` when the node is not part of a path.
@property (unsafe_unretained, nonatomic, nullable) GSPath *parent;
#else
/// The path containing the node, or `nil` when the node is not part of a path.
@property (weak, nonatomic, nullable) GSPath *parent;
#endif

/// The glyph containing the node, or `nil` when the node is not part of a glyph.
@property (nonatomic, readonly, nullable) GSGlyph *glyph;

/// The layer containing the node, or `nil` when the node is not part of a layer.
@property (nonatomic, readonly, nullable) GSLayer *layer;

/// Returns a Property List representation of the node.
///
/// - Parameter format: The Glyphs file format version for which to encode the node.
- (nullable NSDictionary<NSString *, id> *)propertyListValueFormat:(GSFormatVersion)format error:(out NSError **)error;

/// Creates a node from a Glyphs file dictionary representation.
///
/// - Parameter formatVersion: The Glyphs file format version used to interpret the representation.
- (instancetype)initWithDict:(NSDictionary<NSString *, id> *)dict format:(GSFormatVersion)formatVersion;


// MARK: Position

/// The position in layer coordinates.
///
/// For a node in a path, setting the position registers undo, aligns it to the layer grid, and notifies the path.
@property (assign, nonatomic) NSPoint position;

/// The position without orientation adjustment.
@property (assign, nonatomic, readonly) NSPoint positionFast;

/// The unrounded position in layer coordinates.
///
/// Use this position for consecutive geometry operations to avoid accumulating rounding errors.
@property (nonatomic, readonly) NSPoint positionPrecise;

/// The unrounded position in layer coordinates for a layer width.
///
/// - Parameter width: The layer width used for orientation adjustment.
- (NSPoint)positionPreciseForLayerWidth:(CGFloat)width;

/// The unrounded position without orientation adjustment.
- (NSPoint)positionPreciseFast;

/// Sets the position without registering undo or notifying the path.
///
/// Use this method when managing undo and change notifications separately.
- (void)setPositionFast:(NSPoint)position;

/// Sets the unrounded position and rounds the position to a grid without registering undo or notifying the path.
///
/// Use this method when managing undo and change notifications separately.
///
/// - Parameter grid: The grid settings used to round the position.
- (void)setPositionFast:(NSPoint)position grid:(GSGridSettings)grid;

/// The position in layer coordinates for a layer width.
///
/// - Parameter width: The layer width used for orientation adjustment.
- (NSPoint)positionForLayerWidth:(CGFloat)width;

/// Sets the position in layer coordinates for a layer width.
///
/// For a node in a path, setting the position registers undo, aligns it to the layer grid, and notifies the path.
///
/// - Parameter width: The layer width used for orientation adjustment.
- (void)setPosition:(NSPoint)position forLayerWidth:(CGFloat)width;

// MARK: Locked

/// Whether the node should reject edits.
///
/// The node does not enforce this locking itself; instead, higher-level API like ``GSPath`` use this flag to inform their operations.
@property (nonatomic, getter=isLocked) BOOL locked;

/// The type of the node.
@property (assign, nonatomic) GSNodeType type;

/// More efficient access to the node type.
///
/// Setting this property ignores the undo manager, unlike ``type``.
///
/// Prefer reading the node type using this property, but use ``type`` for setting a different node type unless ignoring the undo manager is the intended behavior.
@property (nonatomic, direct) GSNodeType typeFast;

/// A character representing the type of the node.
@property (readonly) char typeTag;

/// Hobby node information associated with the node.
@property (nullable, nonatomic, strong) GSHobbyNodeInfo *hobbyInfo;

/// Higher-order interpolation definitions where the keys are axis tags.
@property (nullable, nonatomic, strong) NSDictionary<NSString *, NSDictionary<NSString *, id> *> *hoiInfo;

/// A user-assigned name of the node.
///
/// The node name is displayed in Edit View but not used otherwise.
@property (nullable, nonatomic, strong) NSString *name;

/// Returns an element string representation of the node.
- (CFStringRef)elementString CF_RETURNS_RETAINED;

/// Creates a node from a Glyphs file array representation.
///
/// - Precondition: `formatVersion >= GSFormatVersion3`.
/// - Parameter formatVersion: The Glyphs file format version used to interpret the representation.
- (instancetype)initWithArray:(NSArray *)list format:(GSFormatVersion)formatVersion;

/// Creates a node from a Glyphs file element string representation.
- (instancetype)initWithElementString:(NSString *)elementString;

/// Checks if the node is equal to another node.
///
/// Regular `isEqual:` compares object identity.
- (BOOL)isEqualToNode:(GSNode *)other;

/// Whether a corner component can be attached to the node.
- (BOOL)canAttachCorner;

/// Creates a node.
- (instancetype)initWithPosition:(NSPoint)position type:(GSNodeType)type connection:(GSNodeConnection)connection;

/// The connection between the incoming and outgoing path segments.
@property (assign, nonatomic) GSNodeConnection connection;

/// Convenience to simplify checking the connection.
@property (readonly) BOOL isSmooth;

/// Toggles the connection type between ``Connection/sharp`` and ``Connection/smooth``.
///
/// - Parameter makeSmooth: Whether to enforce aligned handles in case of a smooth connection.
- (void)toggleConnection:(BOOL)makeSmooth NS_SWIFT_NAME(toggleConnection(makeSmooth:));

/// Toggles the connection type between ``Connection/sharp`` and ``Connection/smooth``/``Connection/superSmooth``.
///
/// - Parameter makeSmooth: Whether to enforce aligned handles in case of a smooth connection.
/// - Parameter superSmooth: Whether to consider a super-smooth connection instead of a regular smooth connection.
- (void)toggleConnection:(BOOL)makeSmooth superSmooth:(BOOL)superSmooth NS_SWIFT_NAME(toggleConnection(makeSmooth:superSmooth:));

/// Automatically sets the connection for the node depending on the angle of the incoming and outgoing path segments.
- (void)checkConnection;

/// Makes the node the start node of its path.
///
/// For open paths, this method is only effective for the end node, reversing the path direction.
///
/// See also ``GSPath/makeNodeFirst:``.
- (void)makeNodeFirst;

/// The index of the node in its path.
///
/// Returns `0` if the node has no parent path.
- (NSUInteger)nodeIndex NS_REFINED_FOR_SWIFT;

/// The next on-curve node in the path.
///
/// For a closed path, navigation wraps around the path.
/// For an open path, this is `nil` when no next on-curve node exists.
/// For a path consisting solely off-curve nodes, returns `nil`.
///
/// See also ``GSPath/nextOncurveNodeFromIndex:``.
- (nullable GSNode *)nextOncurveNode;

/// The previous on-curve node in the path.
///
/// For a closed path, navigation wraps around the path.
/// For an open path, this is `nil` when no previous on-curve node exists.
/// For a path consisting solely off-curve nodes, returns `nil`.
///
/// See also ``GSPath/previousOncurveNodeFromIndex:``.
- (nullable GSNode *)previousOncurveNode;

/// Rounds the position to a grid.
///
/// The unrounded position remains unchanged.
///
/// - Parameter grid: The grid settings used to round the position.
- (void)roundToGrid:(GSGridSettings)grid;

/// Rounds the position to a grid without registering undo or notifying the path.
///
/// The unrounded position remains unchanged.
///
/// - Parameter grid: The grid settings used to round the position.
- (void)roundToGridFast:(GSGridSettings)grid;


#pragma mark Attributes

/// Additional values associated with the node.
///
/// Attribute values must support property-list encoding.
@property (strong, nonatomic) NSDictionary<GSShapeKey, id> *attributes;

/// Replaces all attributes using values encoded for a file format version.
///
/// Replacing the attributes with a dictionary registers undo and notifies the path.
///
/// - Parameter format: The file format version used to decode attribute values.
- (void)setAttributes:(NSDictionary<GSShapeKey, id> *)attributes format:(GSFormatVersion)format;

/// Replaces all attributes without registering undo or notifying the path.
///
/// - Parameter format: The file format version used to decode attribute values.
- (void)setAttributesFast:(NSDictionary<GSShapeKey, id> *)attributes format:(GSFormatVersion)format;

/// The value for an attribute key, or `nil` if no value is set.
- (nullable id)attributeForKey:(GSShapeKey)key;

/// Sets or removes an attribute.
///
/// Setting an attribute registers undo and notifies the path.
/// Passing `nil` as the value removes the attribute.
///
/// - Parameter value: A value that supports property-list encoding, or `nil` to remove the attribute.
- (void)setAttribute:(nullable id)value forKey:(GSShapeKey)key;

/// Removes an attribute.
- (void)removeAttributeForKey:(GSShapeKey)key;

/// The number of attributes.
@property (readonly, nonatomic) NSUInteger countOfAttributes;

/// The pen point at the node, or `nil` if none exists.
- (nullable GSPenPoint *)penPoint;

#pragma mark TempData

/// Values associated with the node for transient use.
///
/// Temporary data is not intended for persistence.
@property (nonatomic, strong, nullable) NSDictionary<NSString *, id> *tempData;

/// Sets or removes a temporary value.
///
/// Passing `nil` as the value removes the temporary value.
///
/// - Parameter value: The value to set, or `nil` to remove the temporary value.
- (void)setTempData:(nullable id)value forKey:(nonnull NSString *)key;

/// The number of temporary values.
@property (readonly, nonatomic) NSInteger countOfTempData;

/// The temporary value for a key, or `nil` if no value is set.
- (nullable id)tempDataForKey:(nonnull NSString *)key;

@end

NS_ASSUME_NONNULL_END
