//
//  GSBaseObject.h
//  GlyphsCore
//
//  Created by Georg Seifert on 04.08.24.
//  Copyright © 2024 schriftgestaltung.de. All rights reserved.
//

#import <Foundation/Foundation.h>
#import <GlyphsCore/GSContainerProtocol.h>
#import <GlyphsCore/GSSelectableElementProtocol.h>
#import <GlyphsCore/GSUserDataProtocol.h>
#import <GlyphsCore/GSSaveContext.h>
#import <os/lock.h>

@class GSReadWriteLock;

NS_ASSUME_NONNULL_BEGIN

typedef NSString * GSShapeKey NS_TYPED_EXTENSIBLE_ENUM;
extern GSShapeKey const GSShapeKeyFillColor;
/// Boolean whether to fill the path.
extern GSShapeKey const GSShapeKeyFill;
extern GSShapeKey const GSShapeKeyFillPatternFile;
extern GSShapeKey const GSShapeKeyFillPatternBlendMode;
extern GSShapeKey const GSShapeKeyFillPatternOffset;
extern GSShapeKey const GSShapeKeyFillPatternScale;
extern GSShapeKey const GSShapeKeyFillPatternAngle;
extern GSShapeKey const GSShapeKeyFillGradient;
extern GSShapeKey const GSShapeKeyStrokeColor;
extern GSShapeKey const GSShapeKeyStrokeGradient;
/// If this attribute is not set, no stroke is applied and thus all other stroke attributes are ignored.
extern GSShapeKey const GSShapeKeyStrokeWidth;
extern GSShapeKey const GSShapeKeyStrokeHeight;
/// A floating point number ranging from `-1` (left side of path) to `1` (right side of path) with default value `0`.
extern GSShapeKey const GSShapeKeyStrokePosition;
extern GSShapeKey const GSShapeKeyStrokeOffsetAngle;
extern GSShapeKey const GSShapeKeyStrokeLineCapStart;
extern GSShapeKey const GSShapeKeyStrokeLineCapEnd;
extern GSShapeKey const GSShapeKeyStrokeLineJoin;
extern GSShapeKey const GSShapeKeyShadow;
extern GSShapeKey const GSShapeKeyInnerShadow;
extern GSShapeKey const GSShapeKeyNoise;
/// The key for the shape attribute value defining the alpha transparency (`-doubleValue` returning `double` in the range `0.0`, fully transparent, to `1.0`, fully opaque).
extern GSShapeKey const GSShapeKeyOpacity;
/// The key for the shape attribute value defining the compositing operation (`GSCompositingOperation`).
extern GSShapeKey const GSShapeKeyCompositingOperation;
/// The value for this key provides a method `-intValue` returning an `int` conforming to `GSMaskType`.
extern GSShapeKey const GSShapeKeyMask;
/// Value of type `NSString`.
extern GSShapeKey const GSShapeKeyParentGroupId;
extern GSShapeKey const GSShapeKeyReversePaths;

extern GSShapeKey const GSShapeKeyColorLabel;
extern GSShapeKey const GSShapeKeyLabel;

@interface GSBaseObject : NSObject <NSCopying, GSSelectableElementProtocol, GSUserDataProtocol> {

	__weak GSContainer *_parent;

	NSMutableDictionary *_tempData;
	os_unfair_lock _tempDataLock;

#pragma GSAutoCodeStart ivars

	NSMutableDictionary <GSShapeKey, id>*_attributes;

#pragma GSAutoCodeEnd ivars

  @public
	NSPoint _position;
	NSPoint _positionPrecise;
	BOOL _locked;
	GSElementOrientation _orientation;
}

@property (weak, nullable) GSContainer *parent;

@property (nonatomic) GSElementOrientation orientation;

#ifndef GLYPHS_VIEWER
/// Returns the undoManager of the containing glyph.
@property (nonatomic, readonly, nullable) NSUndoManager *undoManager;

@property (nonatomic, readonly, nullable) NSUndoManager *undoManagerCheck;
#endif

- (void)initLock;

- (void)elementDidChange:(id)element;

- (void)setParent:(nullable GSContainer *)parent;

#ifndef GLYPHS_VIEWER
/**
 writes that shape to file

 @param file Nullable for testing purposes.
 @param formatVersion The file format version
 @param error error
 @return return value description
 */
- (BOOL)saveToFile:(FILE *)file format:(GSFormatVersion)formatVersion context:(nullable GSSaveContext *)context error:(out NSError **)error;
#endif

/** Returns the content of the object to store it in pList.

 This is used to store the data in the .glyphs file.
 @param format the version of the dict
 */
- (nullable NSDictionary *)propertyListValueFormat:(GSFormatVersion)format error:(out NSError **)error;

@property (nonatomic, readonly, nullable) GSGlyph *glyph;

@property (nonatomic, readonly, nullable) GSLayer *layer;

// MARK: Position

/// The position of the element.
@property (assign, nonatomic) NSPoint position;

/**
 returns position without any processing of the alignments
 */
- (NSPoint)positionFast;

/** The unrounded position of the element.

 This is used for consecutive operations to avoid rounding errors
 */
@property (nonatomic, readonly) NSPoint positionPrecise;

/**
 returns positionPrecise without any processing of the alignments
 */
- (NSPoint)positionPreciseFast;

/** Sets the position without notifying the path and no undo.

 This is mostly used to build a new element list that is later added to the layer. Otherwise you need to make sure to announce the changes. e.g. by calling `[GSLayer elementDidChange:]`.

 @param position The new position
 */

- (void)setPositionFast:(NSPoint)position;

- (void)setPositionFast:(NSPoint)position grid:(GSGridSettings)grid;

- (NSPoint)positionForLayerWidth:(CGFloat)width;

- (void)setPosition:(NSPoint)aPoint forLayerWidth:(CGFloat)width;

// MARK: Locked

@property (nonatomic, getter=isLocked) BOOL locked;

/**
 Returns the bounding box of the receiver’s shape.

 @return The rectangle that encloses the shape of the receiver. If the shape contains curve segments, the bounding box encloses the curve but may not enclose the control points used to calculate the curve.
*/
- (NSRect)bounds;

- (NSRect)boundsAngle:(nullable NSAffineTransform *)transform;
/**
 Returns the bounding box of the receiver’s shape.

 Calculates the bounds from the bare shapes, not removing outside overlaps and such.

 @return The rectangle that encloses the path of the receiver. If the path contains curve segments, the bounding box encloses control points.
*/
- (NSRect)fastBounds;

- (NSRect)fastBoundsTransform:(nullable NSAffineTransform *)transform;

/** rounds the position of the element to grid
 @param grid the grid to round to.
 */
- (void)roundToGrid:(GSGridSettings)grid;

- (void)roundToGridFast:(GSGridSettings)grid;

#pragma mark TempData
/**
 a  dictionary that stores data. It will not be written to disk.
 */
@property (nonatomic, strong, nullable) NSDictionary *tempData;

/**
 Adds key/value to tempData. Pass nil as value to remove previous set data

 @param value and object or nil
 @param key the key
 */
- (void)setTempData:(nullable id)value forKey:(nonnull NSString *)key;

- (NSInteger)countOfTempData;
/**
 return value for key in tempData

 @param key the key
 @return a value or nil
 */
- (nullable id)tempDataForKey:(nonnull NSString *)key;

#pragma GSAutoCodeStart methods

#pragma mark Attributes

/// @name Attributes
/** Place to store data.

 Here it is possible to store something. Please use a unique key.
 The objects should be able to serialize to a plist. If they are not, they should return all infos to store in description (as String).
 */
@property (strong, nonatomic, nullable) NSDictionary<GSShapeKey, id> *attributes;

- (void)setAttributes:(nullable NSDictionary<GSShapeKey, id> *)attributes format:(GSFormatVersion)format;

- (void)setAttributesFast:(NSDictionary *)attributes format:(GSFormatVersion)format;

/**
 convenience accessor to get to the content of the attributes dict

 @param key the key
 @return the data stored with key
 */
- (nullable id)attributeForKey:(GSShapeKey)key;

/** Adds something to the fonts attributes.

 This also triggers undo/document dirty state.

 @param value The object should be able to serialize to a plist. If they are not, they should return all infos to store in description (as String).
 @param key   Please use an unique key that gives some indication who put it there (e.g. prefix it with your name or project).
 */
- (void)setAttribute:(nullable id)value forKey:(GSShapeKey)key;

/** Removed the object with this key.

 @param key The key to remove
 */
- (void)removeAttributeForKey:(GSShapeKey)key;

@property (readonly, nonatomic) NSUInteger countOfAttributes;

#pragma GSAutoCodeEnd methods

@end

NS_ASSUME_NONNULL_END
