//
//  ASTextAttribute.mm
//  Texture
//
//  Copyright (c) Facebook, Inc. and its affiliates.  All rights reserved.
//  Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc.  All rights reserved.
//  Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
//

#import "ASTextAttribute.h"
#import <UIKit/UIKit.h>
#import <CoreText/CoreText.h>
#import <AsyncDisplayKit/NSAttributedString+ASText.h>

NSString *const ASTextBackedStringAttributeName = @"ASTextBackedString";
NSString *const ASTextBindingAttributeName = @"ASTextBinding";
NSString *const ASTextShadowAttributeName = @"ASTextShadow";
NSString *const ASTextInnerShadowAttributeName = @"ASTextInnerShadow";
NSString *const ASTextUnderlineAttributeName = @"ASTextUnderline";
NSString *const ASTextStrikethroughAttributeName = @"ASTextStrikethrough";
NSString *const ASTextBorderAttributeName = @"ASTextBorder";
NSString *const ASTextBackgroundBorderAttributeName = @"ASTextBackgroundBorder";
NSString *const ASTextBlockBorderAttributeName = @"ASTextBlockBorder";
NSString *const ASTextAttachmentAttributeName = @"ASTextAttachment";
NSString *const ASTextHighlightAttributeName = @"ASTextHighlight";
NSString *const ASTextGlyphTransformAttributeName = @"ASTextGlyphTransform";

NSString *const ASTextAttachmentToken = @"\uFFFC";
NSString *const ASTextTruncationToken = @"\u2026";


ASTextAttributeType ASTextAttributeGetType(NSString *name){
  if (name.length == 0) return ASTextAttributeTypeNone;
  
  static NSMutableDictionary *dic;
  static dispatch_once_t onceToken;
  dispatch_once(&onceToken, ^{
    dic = [NSMutableDictionary new];
    NSNumber *All = @(ASTextAttributeTypeUIKit | ASTextAttributeTypeCoreText | ASTextAttributeTypeASText);
    NSNumber *CoreText_ASText = @(ASTextAttributeTypeCoreText | ASTextAttributeTypeASText);
    NSNumber *UIKit_ASText = @(ASTextAttributeTypeUIKit | ASTextAttributeTypeASText);
    NSNumber *UIKit_CoreText = @(ASTextAttributeTypeUIKit | ASTextAttributeTypeCoreText);
    NSNumber *UIKit = @(ASTextAttributeTypeUIKit);
    NSNumber *CoreText = @(ASTextAttributeTypeCoreText);
    NSNumber *ASText = @(ASTextAttributeTypeASText);
    
    dic[NSFontAttributeName] = All;
    dic[NSKernAttributeName] = All;
    dic[NSForegroundColorAttributeName] = UIKit;
    dic[(id)kCTForegroundColorAttributeName] = CoreText;
    dic[(id)kCTForegroundColorFromContextAttributeName] = CoreText;
    dic[NSBackgroundColorAttributeName] = UIKit;
    dic[NSStrokeWidthAttributeName] = All;
    dic[NSStrokeColorAttributeName] = UIKit;
    dic[(id)kCTStrokeColorAttributeName] = CoreText_ASText;
    dic[NSShadowAttributeName] = UIKit_ASText;
    dic[NSStrikethroughStyleAttributeName] = UIKit;
    dic[NSUnderlineStyleAttributeName] = UIKit_CoreText;
    dic[(id)kCTUnderlineColorAttributeName] = CoreText;
    dic[NSLigatureAttributeName] = All;
    dic[(id)kCTSuperscriptAttributeName] = UIKit; //it's a CoreText attrubite, but only supported by UIKit...
    dic[NSVerticalGlyphFormAttributeName] = All;
    dic[(id)kCTGlyphInfoAttributeName] = CoreText_ASText;
#if TARGET_OS_IOS
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
    dic[(id)kCTCharacterShapeAttributeName] = CoreText_ASText;
#pragma clang diagnostic pop
#endif
    dic[(id)kCTRunDelegateAttributeName] = CoreText_ASText;
    dic[(id)kCTBaselineClassAttributeName] = CoreText_ASText;
    dic[(id)kCTBaselineInfoAttributeName] = CoreText_ASText;
    dic[(id)kCTBaselineReferenceInfoAttributeName] = CoreText_ASText;
    dic[(id)kCTWritingDirectionAttributeName] = CoreText_ASText;
    dic[NSParagraphStyleAttributeName] = All;
    
    dic[NSStrikethroughColorAttributeName] = UIKit;
    dic[NSUnderlineColorAttributeName] = UIKit;
    dic[NSTextEffectAttributeName] = UIKit;
    dic[NSObliquenessAttributeName] = UIKit;
    dic[NSExpansionAttributeName] = UIKit;
    dic[(id)kCTLanguageAttributeName] = CoreText_ASText;
    dic[NSBaselineOffsetAttributeName] = UIKit;
    dic[NSWritingDirectionAttributeName] = All;
    dic[NSAttachmentAttributeName] = UIKit;
    dic[NSLinkAttributeName] = UIKit;
    dic[(id)kCTRubyAnnotationAttributeName] = CoreText;
    
    dic[ASTextBackedStringAttributeName] = ASText;
    dic[ASTextBindingAttributeName] = ASText;
    dic[ASTextShadowAttributeName] = ASText;
    dic[ASTextInnerShadowAttributeName] = ASText;
    dic[ASTextUnderlineAttributeName] = ASText;
    dic[ASTextStrikethroughAttributeName] = ASText;
    dic[ASTextBorderAttributeName] = ASText;
    dic[ASTextBackgroundBorderAttributeName] = ASText;
    dic[ASTextBlockBorderAttributeName] = ASText;
    dic[ASTextAttachmentAttributeName] = ASText;
    dic[ASTextHighlightAttributeName] = ASText;
    dic[ASTextGlyphTransformAttributeName] = ASText;
  });
  NSNumber *num = dic[name];
  if (num) return num.integerValue;
  return ASTextAttributeTypeNone;
}


@implementation ASTextBackedString

+ (instancetype)stringWithString:(NSString *)string NS_RETURNS_RETAINED {
  ASTextBackedString *one = [self new];
  one.string = string;
  return one;
}

- (void)encodeWithCoder:(NSCoder *)aCoder {
  [aCoder encodeObject:self.string forKey:@"string"];
}

- (id)initWithCoder:(NSCoder *)aDecoder {
  self = [super init];
  _string = [aDecoder decodeObjectForKey:@"string"];
  return self;
}

- (id)copyWithZone:(NSZone *)zone {
  __typeof__(self) one = [self.class new];
  one.string = self.string;
  return one;
}

@end


@implementation ASTextBinding

+ (instancetype)bindingWithDeleteConfirm:(BOOL)deleteConfirm NS_RETURNS_RETAINED {
  ASTextBinding *one = [self new];
  one.deleteConfirm = deleteConfirm;
  return one;
}

- (void)encodeWithCoder:(NSCoder *)aCoder {
  [aCoder encodeObject:@(self.deleteConfirm) forKey:@"deleteConfirm"];
}

- (id)initWithCoder:(NSCoder *)aDecoder {
  self = [super init];
  _deleteConfirm = ((NSNumber *)[aDecoder decodeObjectForKey:@"deleteConfirm"]).boolValue;
  return self;
}

- (id)copyWithZone:(NSZone *)zone {
  __typeof__(self) one = [self.class new];
  one.deleteConfirm = self.deleteConfirm;
  return one;
}

@end


@implementation ASTextShadow

+ (instancetype)shadowWithColor:(UIColor *)color offset:(CGSize)offset radius:(CGFloat)radius NS_RETURNS_RETAINED {
  ASTextShadow *one = [self new];
  one.color = color;
  one.offset = offset;
  one.radius = radius;
  return one;
}

+ (instancetype)shadowWithNSShadow:(NSShadow *)nsShadow NS_RETURNS_RETAINED {
  if (!nsShadow) return nil;
  ASTextShadow *shadow = [self new];
  shadow.offset = nsShadow.shadowOffset;
  shadow.radius = nsShadow.shadowBlurRadius;
  id color = nsShadow.shadowColor;
  if (color) {
    if (CGColorGetTypeID() == CFGetTypeID((__bridge CFTypeRef)(color))) {
      color = [UIColor colorWithCGColor:(__bridge CGColorRef)(color)];
    }
    if ([color isKindOfClass:[UIColor class]]) {
      shadow.color = color;
    }
  }
  return shadow;
}

- (NSShadow *)nsShadow {
  NSShadow *shadow = [NSShadow new];
  shadow.shadowOffset = self.offset;
  shadow.shadowBlurRadius = self.radius;
  shadow.shadowColor = self.color;
  return shadow;
}

- (void)encodeWithCoder:(NSCoder *)aCoder {
  [aCoder encodeObject:self.color forKey:@"color"];
  [aCoder encodeObject:@(self.radius) forKey:@"radius"];
  [aCoder encodeObject:[NSValue valueWithCGSize:self.offset] forKey:@"offset"];
  [aCoder encodeObject:self.subShadow forKey:@"subShadow"];
}

- (id)initWithCoder:(NSCoder *)aDecoder {
  self = [super init];
  _color = [aDecoder decodeObjectForKey:@"color"];
  _radius = ((NSNumber *)[aDecoder decodeObjectForKey:@"radius"]).floatValue;
  _offset = ((NSValue *)[aDecoder decodeObjectForKey:@"offset"]).CGSizeValue;
  _subShadow = [aDecoder decodeObjectForKey:@"subShadow"];
  return self;
}

- (id)copyWithZone:(NSZone *)zone {
  __typeof__(self) one = [self.class new];
  one.color = self.color;
  one.radius = self.radius;
  one.offset = self.offset;
  one.subShadow = self.subShadow.copy;
  return one;
}

@end


@implementation ASTextDecoration

- (instancetype)init {
  self = [super init];
  _style = ASTextLineStyleSingle;
  return self;
}

+ (instancetype)decorationWithStyle:(ASTextLineStyle)style NS_RETURNS_RETAINED {
  ASTextDecoration *one = [self new];
  one.style = style;
  return one;
}
+ (instancetype)decorationWithStyle:(ASTextLineStyle)style width:(NSNumber *)width color:(UIColor *)color NS_RETURNS_RETAINED {
  ASTextDecoration *one = [self new];
  one.style = style;
  one.width = width;
  one.color = color;
  return one;
}

- (void)encodeWithCoder:(NSCoder *)aCoder {
  [aCoder encodeObject:@(self.style) forKey:@"style"];
  [aCoder encodeObject:self.width forKey:@"width"];
  [aCoder encodeObject:self.color forKey:@"color"];
}

- (id)initWithCoder:(NSCoder *)aDecoder {
  self = [super init];
  self.style = ((NSNumber *)[aDecoder decodeObjectForKey:@"style"]).unsignedIntegerValue;
  self.width = [aDecoder decodeObjectForKey:@"width"];
  self.color = [aDecoder decodeObjectForKey:@"color"];
  return self;
}

- (id)copyWithZone:(NSZone *)zone {
  __typeof__(self) one = [self.class new];
  one.style = self.style;
  one.width = self.width;
  one.color = self.color;
  return one;
}

@end


@implementation ASTextBorder

+ (instancetype)borderWithLineStyle:(ASTextLineStyle)lineStyle lineWidth:(CGFloat)width strokeColor:(UIColor *)color NS_RETURNS_RETAINED {
  ASTextBorder *one = [self new];
  one.lineStyle = lineStyle;
  one.strokeWidth = width;
  one.strokeColor = color;
  return one;
}

+ (instancetype)borderWithFillColor:(UIColor *)color cornerRadius:(CGFloat)cornerRadius NS_RETURNS_RETAINED {
  ASTextBorder *one = [self new];
  one.fillColor = color;
  one.cornerRadius = cornerRadius;
  one.insets = UIEdgeInsetsMake(-2, 0, 0, -2);
  return one;
}

- (instancetype)init {
  self = [super init];
  self.lineStyle = ASTextLineStyleSingle;
  return self;
}

- (void)encodeWithCoder:(NSCoder *)aCoder {
  [aCoder encodeObject:@(self.lineStyle) forKey:@"lineStyle"];
  [aCoder encodeObject:@(self.strokeWidth) forKey:@"strokeWidth"];
  [aCoder encodeObject:self.strokeColor forKey:@"strokeColor"];
  [aCoder encodeObject:@(self.lineJoin) forKey:@"lineJoin"];
  [aCoder encodeObject:[NSValue valueWithUIEdgeInsets:self.insets] forKey:@"insets"];
  [aCoder encodeObject:@(self.cornerRadius) forKey:@"cornerRadius"];
  [aCoder encodeObject:self.shadow forKey:@"shadow"];
  [aCoder encodeObject:self.fillColor forKey:@"fillColor"];
}

- (id)initWithCoder:(NSCoder *)aDecoder {
  self = [super init];
  _lineStyle = ((NSNumber *)[aDecoder decodeObjectForKey:@"lineStyle"]).unsignedIntegerValue;
  _strokeWidth = ((NSNumber *)[aDecoder decodeObjectForKey:@"strokeWidth"]).doubleValue;
  _strokeColor = [aDecoder decodeObjectForKey:@"strokeColor"];
  _lineJoin = (CGLineJoin)((NSNumber *)[aDecoder decodeObjectForKey:@"join"]).unsignedIntegerValue;
  _insets = ((NSValue *)[aDecoder decodeObjectForKey:@"insets"]).UIEdgeInsetsValue;
  _cornerRadius = ((NSNumber *)[aDecoder decodeObjectForKey:@"cornerRadius"]).doubleValue;
  _shadow = [aDecoder decodeObjectForKey:@"shadow"];
  _fillColor = [aDecoder decodeObjectForKey:@"fillColor"];
  return self;
}

- (id)copyWithZone:(NSZone *)zone {
  __typeof__(self) one = [self.class new];
  one.lineStyle = self.lineStyle;
  one.strokeWidth = self.strokeWidth;
  one.strokeColor = self.strokeColor;
  one.lineJoin = self.lineJoin;
  one.insets = self.insets;
  one.cornerRadius = self.cornerRadius;
  one.shadow = self.shadow.copy;
  one.fillColor = self.fillColor;
  return one;
}

@end


@implementation ASTextAttachment

+ (instancetype)attachmentWithContent:(id)content NS_RETURNS_RETAINED {
  ASTextAttachment *one = [self new];
  one.content = content;
  return one;
}

- (void)encodeWithCoder:(NSCoder *)aCoder {
  [aCoder encodeObject:self.content forKey:@"content"];
  [aCoder encodeObject:[NSValue valueWithUIEdgeInsets:self.contentInsets] forKey:@"contentInsets"];
  [aCoder encodeObject:self.userInfo forKey:@"userInfo"];
}

- (id)initWithCoder:(NSCoder *)aDecoder {
  self = [super init];
  _content = [aDecoder decodeObjectForKey:@"content"];
  _contentInsets = ((NSValue *)[aDecoder decodeObjectForKey:@"contentInsets"]).UIEdgeInsetsValue;
  _userInfo = [aDecoder decodeObjectForKey:@"userInfo"];
  return self;
}

- (id)copyWithZone:(NSZone *)zone {
  __typeof__(self) one = [self.class new];
  if ([self.content respondsToSelector:@selector(copy)]) {
    one.content = [self.content copy];
  } else {
    one.content = self.content;
  }
  one.contentInsets = self.contentInsets;
  one.userInfo = self.userInfo.copy;
  return one;
}

@end


@implementation ASTextHighlight

+ (instancetype)highlightWithAttributes:(NSDictionary *)attributes NS_RETURNS_RETAINED {
  ASTextHighlight *one = [self new];
  one.attributes = attributes;
  return one;
}

+ (instancetype)highlightWithBackgroundColor:(UIColor *)color NS_RETURNS_RETAINED {
  ASTextBorder *highlightBorder = [ASTextBorder new];
  highlightBorder.insets = UIEdgeInsetsMake(-2, -1, -2, -1);
  highlightBorder.cornerRadius = 3;
  highlightBorder.fillColor = color;
  
  ASTextHighlight *one = [self new];
  [one setBackgroundBorder:highlightBorder];
  return one;
}

- (void)setAttributes:(NSDictionary *)attributes {
  _attributes = attributes.mutableCopy;
}

- (id)copyWithZone:(NSZone *)zone {
  __typeof__(self) one = [self.class new];
  one.attributes = self.attributes.mutableCopy;
  return one;
}

- (void)_makeMutableAttributes {
  if (!_attributes) {
    _attributes = [NSMutableDictionary new];
  } else if (![_attributes isKindOfClass:[NSMutableDictionary class]]) {
    _attributes = _attributes.mutableCopy;
  }
}

- (void)setFont:(UIFont *)font {
  [self _makeMutableAttributes];
  if (font == (id)[NSNull null] || font == nil) {
    ((NSMutableDictionary *)_attributes)[(id)kCTFontAttributeName] = [NSNull null];
  } else {
    CTFontRef ctFont = CTFontCreateWithName((__bridge CFStringRef)font.fontName, font.pointSize, NULL);
    if (ctFont) {
      ((NSMutableDictionary *)_attributes)[(id)kCTFontAttributeName] = (__bridge id)(ctFont);
      CFRelease(ctFont);
    }
  }
}

- (void)setColor:(UIColor *)color {
  [self _makeMutableAttributes];
  if (color == (id)[NSNull null] || color == nil) {
    ((NSMutableDictionary *)_attributes)[(id)kCTForegroundColorAttributeName] = [NSNull null];
    ((NSMutableDictionary *)_attributes)[NSForegroundColorAttributeName] = [NSNull null];
  } else {
    ((NSMutableDictionary *)_attributes)[(id)kCTForegroundColorAttributeName] = (__bridge id)(color.CGColor);
    ((NSMutableDictionary *)_attributes)[NSForegroundColorAttributeName] = color;
  }
}

- (void)setStrokeWidth:(NSNumber *)width {
  [self _makeMutableAttributes];
  if (width == (id)[NSNull null] || width == nil) {
    ((NSMutableDictionary *)_attributes)[(id)kCTStrokeWidthAttributeName] = [NSNull null];
  } else {
    ((NSMutableDictionary *)_attributes)[(id)kCTStrokeWidthAttributeName] = width;
  }
}

- (void)setStrokeColor:(UIColor *)color {
  [self _makeMutableAttributes];
  if (color == (id)[NSNull null] || color == nil) {
    ((NSMutableDictionary *)_attributes)[(id)kCTStrokeColorAttributeName] = [NSNull null];
    ((NSMutableDictionary *)_attributes)[NSStrokeColorAttributeName] = [NSNull null];
  } else {
    ((NSMutableDictionary *)_attributes)[(id)kCTStrokeColorAttributeName] = (__bridge id)(color.CGColor);
    ((NSMutableDictionary *)_attributes)[NSStrokeColorAttributeName] = color;
  }
}

- (void)setTextAttribute:(NSString *)attribute value:(id)value {
  [self _makeMutableAttributes];
  if (value == nil) value = [NSNull null];
  ((NSMutableDictionary *)_attributes)[attribute] = value;
}

- (void)setShadow:(ASTextShadow *)shadow {
  [self setTextAttribute:ASTextShadowAttributeName value:shadow];
}

- (void)setInnerShadow:(ASTextShadow *)shadow {
  [self setTextAttribute:ASTextInnerShadowAttributeName value:shadow];
}

- (void)setUnderline:(ASTextDecoration *)underline {
  [self setTextAttribute:ASTextUnderlineAttributeName value:underline];
}

- (void)setStrikethrough:(ASTextDecoration *)strikethrough {
  [self setTextAttribute:ASTextStrikethroughAttributeName value:strikethrough];
}

- (void)setBackgroundBorder:(ASTextBorder *)border {
  [self setTextAttribute:ASTextBackgroundBorderAttributeName value:border];
}

- (void)setBorder:(ASTextBorder *)border {
  [self setTextAttribute:ASTextBorderAttributeName value:border];
}

- (void)setAttachment:(ASTextAttachment *)attachment {
  [self setTextAttribute:ASTextAttachmentAttributeName value:attachment];
}

@end