//
//  CYRTextStorage.m
//
//  Version 0.2.0
//
//  Created by Illya Busigin on 01/05/2014.
//  Copyright (c) 2014 Cyrillian, Inc.
//
//  Distributed under MIT license.
//  Get the latest version from here:
//
//  https://github.com/illyabusigin/CYRTextView
//
// The MIT License (MIT)
//
// Copyright (c) 2014 Cyrillian, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
// the Software, and to permit persons to whom the Software is furnished to do so,
// subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

#import "CYRTextStorage.h"
#import "CYRToken.h"

@interface CYRTextStorage ()

@property (nonatomic, strong) NSMutableAttributedString *attributedString;
@property (nonatomic, strong) NSMutableDictionary *regularExpressionCache;

@end

@implementation CYRTextStorage

#pragma mark - Initialization & Setup

- (id)init
{
    if (self = [super init])
    {
        _defaultFont = [UIFont systemFontOfSize:12.0f];
        _attributedString = [NSMutableAttributedString new];
        
        _tokens = @[];
        _regularExpressionCache = @{}.mutableCopy;
    }
    
    return self;
}


#pragma mark - Overrides

- (void)setTokens:(NSMutableArray *)tokens
{
    _tokens = tokens;
    
    // Clear the regular expression cache
    [self.regularExpressionCache removeAllObjects];
    
    // Redraw all text
    [self update];
}

- (NSString *)string
{
    return [_attributedString string];
}

- (NSDictionary *)attributesAtIndex:(NSUInteger)location effectiveRange:(NSRangePointer)range
{
    return [_attributedString attributesAtIndex:location effectiveRange:range];
}

- (void)replaceCharactersInRange:(NSRange)range withString:(NSString*)str
{
    [self beginEditing];
    
    [_attributedString replaceCharactersInRange:range withString:str];
    
    [self edited:NSTextStorageEditedCharacters | NSTextStorageEditedAttributes range:range changeInLength:str.length - range.length];
    [self endEditing];
}

- (void)setAttributes:(NSDictionary*)attrs range:(NSRange)range
{
    [self beginEditing];
    
    [_attributedString setAttributes:attrs range:range];
    
    [self edited:NSTextStorageEditedAttributes range:range changeInLength:0];
    [self endEditing];
}

-(void)processEditing
{
    [self performReplacementsForRange:[self editedRange]];
    [super processEditing];
}

- (void)performReplacementsForRange:(NSRange)changedRange
{
    NSRange extendedRange = NSUnionRange(changedRange, [[_attributedString string] lineRangeForRange:NSMakeRange(NSMaxRange(changedRange), 0)]);
    
    [self applyStylesToRange:extendedRange];
}


-(void)update
{    
    [self addAttributes:@{NSFontAttributeName : self.defaultFont} range:NSMakeRange(0, self.length)];
    
    [self applyStylesToRange:NSMakeRange(0, self.length)];
}

- (void)applyStylesToRange:(NSRange)searchRange
{
    if (self.editedRange.location == NSNotFound)
    {
        return;
    }
    
    NSRange paragaphRange = [self.string paragraphRangeForRange: self.editedRange];
    
    // Reset the text attributes
    [self setAttributes:@{NSForegroundColorAttributeName : [UIColor blackColor]} range:paragaphRange];
    
    [self setAttributes:@{NSFontAttributeName : self.defaultFont} range:paragaphRange];
    
    for (CYRToken *attribute in self.tokens)
    {
        NSRegularExpression *regex = [self expressionForDefinition:attribute.name];
        [regex enumerateMatchesInString:self.string options:0 range:paragaphRange
                             usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) {
                                 
                                 [attribute.attributes enumerateKeysAndObjectsUsingBlock:^(NSString *attributeName, id attributeValue, BOOL *stop) {
                                     [self addAttribute:attributeName value:attributeValue range:result.range];
                                 }];
                             }];
    }
}

- (NSRegularExpression *)expressionForDefinition:(NSString *)definition
{
    __block CYRToken *attribute = nil;
    
    [self.tokens enumerateObjectsUsingBlock:^(CYRToken *enumeratedAttribute, NSUInteger idx, BOOL *stop) {
        if ([enumeratedAttribute.name isEqualToString:definition])
        {
            attribute = enumeratedAttribute;
            *stop = YES;
        }
    }];
    
    NSRegularExpression *expression = self.regularExpressionCache[attribute.expression];
    
    if (!expression)
    {
        expression = [NSRegularExpression regularExpressionWithPattern:attribute.expression
                                                               options:NSRegularExpressionCaseInsensitive error:nil];
        
        [self.regularExpressionCache setObject:expression forKey:definition];
    }
    
    return expression;
}

@end
