//
//  GSPath.h
//  FontBezier Doc
//
//  Created by Georg Seifert on 19.10.05.
//  Copyright 2005 schriftgestaltung.de. All rights reserved.
//

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

NS_ASSUME_NONNULL_BEGIN

@class GSGlyph;
@class GSLayer;
@class GSElement;
@class GSHint;
@class GSPathSegment;
#ifndef GLYPHS_LITE
@class GSPathHash;
#endif
@protocol GSPenProtocol;

/** Possible values retuned by the method */
typedef NS_ENUM(short, GSPathDirection) {
	///	Counter Clock Wise
	GSCounterClockWise = -1,
	/// Clock Wise
	GSClockWise = 1
};

/** The class defining the path object */

@interface GSPath : GSShape <GSContainerProtocol> {
	NSMutableArray<GSNode *> *_nodes;
	BOOL _closed;

#pragma GSAutoCodeStart ivars

#pragma GSAutoCodeEnd ivars
}

/// Set if the path is closed.
@property (nonatomic) BOOL closed;

/**
 Initializes a path with a dictionary loaded from a pList.

 @param pathDict A dictionary
 */
- (instancetype)initWithDict:(NSDictionary *)pathDict format:(GSFormatVersion)formatVersion;

- (instancetype)initWithPathString:(NSString *)pathString;

- (BOOL)isEqualToPath:(GSPath *)otherPath;

#ifndef GLYPHS_VIEWER

/**
 Description

 @param file Nullable for testing purposes.
 @param formatVersion The file format version
 @param compact use shorter representation
 @param error error
 @return return value description
 */
- (BOOL)saveToFile:(FILE *)file format:(GSFormatVersion)formatVersion compact:(BOOL)compact error:(NSError **)error;

/**
 Gives the nearest Point on the path

 @param loc   the point
 @param pathTime On return the "path time" on reference. The integer part is the Index of the next on-curve point. The fractional digits contain the time to the point.

 @return the Point on the path
*/
- (NSPoint)nearestPointOnPath:(NSPoint)loc pathTime:(out CGFloat *)pathTime;
#endif

- (NSPoint)pointAtPathTime:(CGFloat)pathTime;

#ifndef GLYPHS_VIEWER
- (GSNode *)nearestNodeWithPathTime:(CGFloat)pathTime;
/** Tests if pt lays on the path and inserts a new point and corrects the handles of the other points to preserve the paths appearance.

 @param pathTime the "path time" the integer part is the Index of the next on-curve point. The fractional digits contain the time to the point.
 @return If the new point object, or nil if something went wrong.
 */
- (nullable GSNode *)insertNodeWithPathTime:(CGFloat)pathTime;

#endif

/// @name Nodes

/// An array of GSNode objects.
@property (strong, nonatomic, null_resettable) NSArray<GSNode *> *nodes;

- (void)setNodesFast:(NSArray *)nodes;
/**
 Returns the count of nodes in the path.

 @return The count of nodes in the path.
 */
- (NSUInteger)countOfNodes;

/**
 Adds the node to the end of the paths array.

 @param node The node to add.
 */
- (void)addNode:(GSNode *)node;

- (void)addNodeWithPosition:(NSPoint)position type:(GSNodeType)type connection:(GSNodeType)connection;
- (void)addNodeFastWithPosition:(NSPoint)position type:(GSNodeType)type connection:(GSNodeType)connection;
- (void)addCurveWithOff1:(NSPoint)off1 off2:(NSPoint)off2 end:(NSPoint)end connection:(GSNodeType)connection;
- (void)addCurveFastWithOff1:(NSPoint)off1 off2:(NSPoint)off2 end:(NSPoint)end connection:(GSNodeType)connection;

/**
 Adds the node without notifying the layer

 You need to make sure that the layer is notified

 @param node The node to add.
 */
- (void)addNodeFast:(GSNode *)node;

- (void)addNodes:(NSArray<GSNode *> *)nodes;
- (void)removeNodes:(NSArray<GSNode *> *)nodes;
/**
 Inserts a Node at the index into nodes.

 @param node A GSNode object to insert.
 @param idx The index in the receiver at which to insert the Node. This value must not be greater than the count of nodes in the path.
*/
- (void)insertObject:(GSNode *)node inNodesAtIndex:(NSInteger)idx;

/**
 Removes the point at Index from the path.

 @param idx The index of the node to remove.
*/
- (void)removeObjectFromNodesAtIndex:(NSInteger)idx;

/**
 Removes Node from path.

 @param node The node to return from the path.
*/
- (void)removeNode:(GSNode *)node;

- (void)removeNodeFast:(GSNode *)node;
#ifndef GLYPHS_VIEWER

/** Removes Node and tries to keep the shape of the path.

 e.g. If Node is a off-curve node, it removes the second off-curve node and sets the segment to LINE.
 @param node The node to return from the path.
*/
- (BOOL)removeNodeCheckKeepShape:(GSNode *)node;

- (BOOL)removeNodeCheckKeepShape:(GSNode *)node normalizeHandles:(BOOL)normalizeHandles error:(NSError **)error;
/** Removes Node.

 If Node is a off-curve node, it removes the second off-curve node and sets the segment to LINE.
 @param node The node to return from the path.
*/
- (void)removeNodeCheck:(GSNode *)node;

/**
 Is called from node objects if it has changed.

 This triggers the update of the interface

 @param element A GSNode object
*/
- (void)elementDidChange:(GSElement *)element;

#endif

/** Finds the Node that should be used as start Node*/
- (GSNode *)findMinNode;

/** Finds the Node that is used as start Node */
- (GSNode *)findStartNode;

- (void)fixStartNode;

/** Makes the Node the first in the path.

 It checks if the node is in the path. If not, it makes nothing.

 @param node  The node that should be the starting node.
*/
- (void)makeNodeFirst:(GSNode *)node;

#ifndef GLYPHS_LITE
- (BOOL)hasTrueTypeOutline;
- (BOOL)hasTrueTypeCurve;
- (NSUInteger)indexOfTTStartNode;
- (BOOL)hasHobbyCurve;
#endif
/**
 Closes or opens the Path. If necessary it adds or removes points.

 @param closed A BOOL
*/
- (void)setClosePath:(BOOL)closed;

- (void)setClosePath:(BOOL)closed fixStartNode:(BOOL)fixStartNode;

- (void)setLocked:(GSNode *)node withPoint:(NSPoint)point;

- (void)setSmooth:(GSNode *)node withCenterPoint:(NSPoint)centerPoint oppositePoint:(NSPoint)oppositePoint;

- (void)setSmooth:(GSNode *)node withCenterNode:(GSNode *)centerNode oppositeNode:(GSNode *)oppositeNode /*NS_DEPRECATED_MAC(10_0, 10_0)*/;

- (void)setLocked:(GSNode *)node shadow:(GSNode *)shadowNode withPoint:(NSPoint)aPoint;

- (void)setMirrored:(GSNode *)node withCenterPoint:(NSPoint)centerPoint oppositePoint:(NSPoint)oppositePoint;

/// Fixes the connection in all nodes
- (void)checkConnections;

- (void)checkConnectionForNode:(GSNode *)node;
/** Returns the GSNode located at Index.

 The Index is corrected for overflow and underflow. Index = -1 retunes the last node...

 @param idx An index.
 @return A GSNode object.
*/
- (nullable GSNode *)nodeAtIndex:(NSInteger)idx;

/** Returns the index of the Node in nodes.

 @param node A GSNode object
 @return The index of the node in the path.
*/
- (NSUInteger)indexOfNode:(GSNode *)node;

/// For open paths, returns the first node of the path, otherwise `nil`.
@property (nonatomic, readonly, nullable) GSNode *startNode;

/// For open paths, returns the last node of the path, otherwise `nil`.
@property (nonatomic, readonly, nullable) GSNode *endNode;

- (nullable GSNode *)nextOncurveNodeFromIndex:(NSInteger)idx;

- (nullable GSNode *)previousOncurveNodeFromIndex:(NSInteger)idx;

#ifndef GLYPHS_VIEWER
/** The path direction

 @return -1 for counter clock wise and 1 for clock wise
 */
- (GSPathDirection)direction;

- (BOOL)enclosesPath:(GSPath *)smallPath;
#endif

/// Reverses the path direction
- (void)reverse;

- (void)reverseFast;
#ifndef GLYPHS_VIEWER
/** Cleans up the Path

 Removes empty line segments

 Converts flat curves into line

 Rounds coordinates to the gridLength specified in the font
 */
- (void)cleanUp;

- (void)cleanUpGrid:(CGFloat)grid;

/// This finds all nodes that are needed: those a tidy up would keep. It is needed for MM-tidy up
- (void)findRequiredNodeIndexes:(NSMutableIndexSet *)requiredNodeIndexes gridLength:(CGFloat)gridLength;

- (void)removeDuplicateNodes;

- (void)convertToCubic;

#ifndef GLYPHS_LITE
- (void)convertToQuadratic;
#endif

#endif

#ifndef GLYPHS_VIEWER
#ifndef GLYPHS_LITE
#ifndef LIBCORE
- (CGFloat)area;

- (CGFloat)length;
#endif
#endif

/// converts everything into "dumb" outlines (strokes, corners …)
- (GSPath *)flattenedPath;

- (void)roundToGrid:(CGFloat)grid;

/**
 Adds extremes to the path.

 If there is a node closed to actual extreme, it will try to move it. If Force is not set, it does not add extremes if there is not enough space

 @param force Determines if it adds nodes even if there is not enough space and it would add a bump.
 @param checkSelection check selection. At least one node of the segment has to be selected
*/
- (void)addExtremes:(BOOL)force checkSelection:(BOOL)checkSelection;

- (void)addInflections;
#endif

/**
 Calculates the angle of the tangent at the given node

 @param node	  The node where to calculate the angle
 @param direction -1 for the angle to the previous node, 1 of the next node

 @return the angle in degrees
*/
- (CGFloat)tangentAngleAtNode:(GSNode *)node direction:(int)direction;

/**
 Calculates the angle of the tangent at the node at the given Index

 @param idx			The index of the node where to calculate the angle
 @param direction	-1 for the angle to the previous node, 1 of the next node

 @return the angle in degrees
*/
- (CGFloat)tangentAngleAtNodeAtIndex:(NSInteger)idx direction:(int)direction;

- (NSPoint)unitVectorAtNodeAtIndex:(NSInteger)nodeIdx;

- (NSPoint)unitVectorAtNodeAtIndex:(NSInteger)nodeIdx direction:(int)direction;
/**
 Calculates a bezier path representing the path.

 This is a bit expensive call so you might cache the result.

 is is probably better to get the bezier path from the layer

 @return A NSBezierPath representing the path.
*/
- (nullable NSBezierPath *)bezierPath;

/** Calculates a list of segments

 This might simplify path operations in some cases

 @return A NSArray containing GSPathSegment.
 */
@property (nonatomic, strong) NSArray<GSPathSegment *> *segments;

- (nullable GSPathSegment *)segmentAtIndex:(NSInteger)idx;

- (NSInteger)firstOncurveNodeIndex;

#ifndef GLYPHS_LITE
- (NSString *)compareString;
#ifndef LIBCORE
- (GSPathHash *)compareHash;

- (void)nodeIndexForHash:(GSPathHash *)hash callback:(void (^)(NSInteger nodeIdx, CGFloat averageAngle, NSPoint averagePos, BOOL *stop))callbackBlock;

- (nullable NSAffineTransform *)transformForHash:(GSPathHash *)hash;
#endif
#endif

- (NSArray<NSValue *> *)intersectionsWithLineFrom:(NSPoint)startPoint to:(NSPoint)endPoint;

#pragma GSAutoCodeStart methods

#pragma GSAutoCodeEnd methods

@end
NS_ASSUME_NONNULL_END
