<commit_before>//
//  MarkDownManager.swift
//  String Attributes test
//
//  Created by <NAME> on 9/15/17.
//  Copyright © 2017 <NAME>. All rights reserved.
//

import Foundation

#if os(iOS)
    import UIKit
    
    let none = UIFont.systemFont(ofSize: 16)
    let bold = UIFont.boldSystemFont(ofSize: 16)
    let italic = UIFont.italicSystemFont(ofSize: 15)
    let boldItalic = UIFont(name: "Helvetica-BoldOblique", size: 16)
    let code = UIFont(name: "Courier", size: 14)
    let forgroundColor = UIColor.codeForeground
    
    extension UIColor {
        
        class var codeForeground: UIColor {
            return UIColor(red: 206 / 255.0 , green: 26 / 255.0 , blue: 83 / 255.0 , alpha: 1)
        }
        
    }
    
#elseif os(macOS)
    import Cocoa
    import AppKit
    
    let none = NSFont.systemFont(ofSize: 16)
    let bold = NSFont.boldSystemFont(ofSize: 16)
    let italic = NSFont(name: "Helvetica-LightOblique", size: 15)
    let boldItalic = NSFont(name: "Helvetica-BoldOblique", size: 16)
    let code = NSFont(name: "Courier", size: 14)
    let forgroundColor = NSColor.codeForeground
    
    extension NSColor {
        
        class var codeForeground: NSColor {
            return NSColor(red: 206 / 255.0 , green: 26 / 255.0 , blue: 83 / 255.0 , alpha: 1)
        }
    }
    
#endif


class MarkDown {
    
    private var text: String!
    private var attrText: NSMutableAttributedString!
    
    // MARK: - Mark Styles
    
    enum MarkStyles : String {
        case bold       = "*"
        case italic     = "_"
        case code       = "`"
        case link       = "["
        case boldItalic
        case none
    }
    
    // MARK: - Init
    
    init(string: String) {
        text =  string
        attrText = NSMutableAttributedString(string: text, attributes: [NSFontAttributeName:  none])
    }
    
    // MARK: - Public Marking Functions
    
    func markDown() -> NSAttributedString {
        var components = getMarkDownComponents(.code)
        updateAtterbutedTextFrom(components)
        
        components = getMarkDownComponents(.bold)
        updateAtterbutedTextFrom(components)
        
        components = getMarkDownComponents(.italic)
        updateAtterbutedTextFrom(components)
        
        return attrText
    }
    
    // MARK: - Text Parsing
    
    private func updateAtterbutedTextFrom (_ components: [NSMutableAttributedString]) {
        let newAttributedText = NSMutableAttributedString()
        for comp in components {
            newAttributedText.append(comp)
        }
        attrText = newAttributedText
        text = attrText.string
    }
    
    private func getMarkDownComponents (_ style: MarkStyles) -> [NSMutableAttributedString] {
        var components = [NSMutableAttributedString]()
        var openIndex = 0
        var closeIndex = 0
        var lookingForClosingMark = false
        let textSize = text.characters.count
        
        for index in 0..<textSize {
            closeIndex = index
            guard String(text[index]) == style.rawValue && charFontAttributeAt(index) != code else {
                continue
            }
            if isClosingMarkAt(index, mark: style.rawValue) && lookingForClosingMark {
                lookingForClosingMark = false
                components.append(getMutableAttrString(lowerB: openIndex + 1, upperB: index, style: style))
                openIndex = index + 1
            } else if isOpenMarkAt(index, mark: style.rawValue) {
                guard index != openIndex else {
                    components.append(NSMutableAttributedString(string: ""))
                    lookingForClosingMark = true
                    continue
                }
                components.append(getMutableAttrString(lowerB: openIndex, upperB: index, style: .none))
                openIndex = index
                lookingForClosingMark = true
            }
        }
        components.append(getMutableAttrString(lowerB: openIndex, upperB: closeIndex + 1, style: .none))
        return components
    }

    // MARK: - Attributes Applying
    
    private func getMutableAttrString(lowerB: Int, upperB: Int, style: MarkStyles) -> NSMutableAttributedString {
        
        let nsRangeLength = upperB - lowerB
        let range = NSMakeRange(lowerB, nsRangeLength)
        
        switch style {
        case .code:
            attrText.setAttributes(attributesFor(.code), range: range)
        case .none:
            break
        default:
            for i in lowerB...upperB {
                let charRangeLength = 1
                let charRange = NSMakeRange(i, charRangeLength)
                
                if let font = charFontAttributeAt(i) {
                    switch font {
                    case none:
                        attrText.setAttributes(attributesFor(style), range: charRange)
                    default:
                        attrText.setAttributes(attributesFor(.boldItalic), range: charRange)
                    }
                }
            }
        }
        
        let res = attrText.attributedSubstring(from: range)
        return NSMutableAttributedString(attributedString: res)
    }
    
    func attributesFor(_ style: MarkStyles) -> [String: AnyObject] {
        switch style {
        case .bold:
            return [NSFontAttributeName: bold]
        case .italic:
            return [NSFontAttributeName: italic]
        case .boldItalic:
            return [NSFontAttributeName: boldItalic!]
        case .code:
            return [NSFontAttributeName: code!,
                    NSForegroundColorAttributeName: forgroundColor]
        default:
            return [NSFontAttributeName: none]
        }
    }
    
    // MARK: - Open/Close Mark Check
    
    private func isOpenMarkAt(_ index: Int, mark: String) -> Bool {
        guard index < text.characters.count else {
            return false
        }
        return String(text[index + 1]) != mark && text[index + 1] != " "
    }
    
    private func isClosingMarkAt(_ index: Int, mark: String) -> Bool {
        
        guard index > 0 else {
            return false
        }
        return String(text[index - 1]) != mark && text[index - 1] != " "
    }
    
    private func charFontAttributeAt(_ index: Int) -> UIFont? {
        
        let charAttributes = attrText.attributes(at: index, effectiveRange: nil)
        return charAttributes[NSFontAttributeName] as? UIFont
    }
}


// ----- debug indication code -----
// #################################

//let lowerBound = text.index(at: lowerB)
//let upperBound = text.index(at: upperB)
//let range = Range(uncheckedBounds: (lowerBound, upperBound))
//print("res from text: - " + text.substring(with: range))

<commit_msg>charFontAttributeAt func moved to extension by platform<commit_after>//
//  MarkDownManager.swift
//  String Attributes test
//
//  Created by <NAME> on 9/15/17.
//  Copyright © 2017 <NAME>. All rights reserved.
//

import Foundation

#if os(iOS)
    import UIKit
    
    let none = UIFont.systemFont(ofSize: 16)
    let bold = UIFont.boldSystemFont(ofSize: 16)
    let italic = UIFont.italicSystemFont(ofSize: 15)
    let boldItalic = UIFont(name: "Helvetica-BoldOblique", size: 16)
    let code = UIFont(name: "Courier", size: 14)
    let forgroundColor = UIColor.codeForeground
    
    extension UIColor {
        
        class var codeForeground: UIColor {
            return UIColor(red: 206 / 255.0 , green: 26 / 255.0 , blue: 83 / 255.0 , alpha: 1)
        }
    }
    
    extension MarkDown {
        func charFontAttributeAt(_ index: Int) -> UIFont? {
            
            let charAttributes = attrText.attributes(at: index, effectiveRange: nil)
            return charAttributes[NSFontAttributeName] as? UIFont
        }
    }
    
#elseif os(macOS)
    import Cocoa
    import AppKit
    
    let none = NSFont.systemFont(ofSize: 16)
    let bold = NSFont.boldSystemFont(ofSize: 16)
    let italic = NSFont(name: "Helvetica-LightOblique", size: 15)
    let boldItalic = NSFont(name: "Helvetica-BoldOblique", size: 16)
    let code = NSFont(name: "Courier", size: 14)
    let forgroundColor = NSColor.codeForeground
    
    extension NSColor {
        
        class var codeForeground: NSColor {
            return NSColor(red: 206 / 255.0 , green: 26 / 255.0 , blue: 83 / 255.0 , alpha: 1)
        }
    }
    
    extension MarkDown {
        func charFontAttributeAt(_ index: Int) -> NSFont? {
            
            let charAttributes = attrText.attributes(at: index, effectiveRange: nil)
            return charAttributes[NSFontAttributeName] as? NSFont
        }
    }
    
#endif


class MarkDown {
    
    private var text: String!
    var attrText: NSMutableAttributedString!
    
    // MARK: - Mark Styles
    
    enum MarkStyles : String {
        case bold       = "*"
        case italic     = "_"
        case code       = "`"
        case link       = "["
        case boldItalic
        case none
    }
    
    // MARK: - Init
    
    init(string: String) {
        text =  string
        attrText = NSMutableAttributedString(string: text, attributes: [NSFontAttributeName:  none])
    }
    
    // MARK: - Public Marking Functions
    
    func markDown() -> NSAttributedString {
        var components = getMarkDownComponents(.code)
        updateAtterbutedTextFrom(components)
        
        components = getMarkDownComponents(.bold)
        updateAtterbutedTextFrom(components)
        
        components = getMarkDownComponents(.italic)
        updateAtterbutedTextFrom(components)
        
        return attrText
    }
    
    // MARK: - Text Parsing
    
    private func updateAtterbutedTextFrom (_ components: [NSMutableAttributedString]) {
        let newAttributedText = NSMutableAttributedString()
        for comp in components {
            newAttributedText.append(comp)
        }
        attrText = newAttributedText
        text = attrText.string
    }
    
    private func getMarkDownComponents (_ style: MarkStyles) -> [NSMutableAttributedString] {
        var components = [NSMutableAttributedString]()
        var openIndex = 0
        var closeIndex = 0
        var lookingForClosingMark = false
        let textSize = text.characters.count
        
        for index in 0..<textSize {
            closeIndex = index
            guard String(text[index]) == style.rawValue && charFontAttributeAt(index) != code else {
                continue
            }
            if isClosingMarkAt(index, mark: style.rawValue) && lookingForClosingMark {
                lookingForClosingMark = false
                components.append(getMutableAttrString(lowerB: openIndex + 1, upperB: index, style: style))
                openIndex = index + 1
            } else if isOpenMarkAt(index, mark: style.rawValue) {
                guard index != openIndex else {
                    components.append(NSMutableAttributedString(string: ""))
                    lookingForClosingMark = true
                    continue
                }
                components.append(getMutableAttrString(lowerB: openIndex, upperB: index, style: .none))
                openIndex = index
                lookingForClosingMark = true
            }
        }
        components.append(getMutableAttrString(lowerB: openIndex, upperB: closeIndex + 1, style: .none))
        return components
    }

    // MARK: - Attributes Applying
    
    private func getMutableAttrString(lowerB: Int, upperB: Int, style: MarkStyles) -> NSMutableAttributedString {
        
        let nsRangeLength = upperB - lowerB
        let range = NSMakeRange(lowerB, nsRangeLength)
        
        switch style {
        case .code:
            attrText.setAttributes(attributesFor(.code), range: range)
        case .none:
            break
        default:
            for i in lowerB...upperB {
                let charRangeLength = 1
                let charRange = NSMakeRange(i, charRangeLength)
                
                if let font = charFontAttributeAt(i) {
                    switch font {
                    case none:
                        attrText.setAttributes(attributesFor(style), range: charRange)
                    default:
                        attrText.setAttributes(attributesFor(.boldItalic), range: charRange)
                    }
                }
            }
        }
        
        let res = attrText.attributedSubstring(from: range)
        return NSMutableAttributedString(attributedString: res)
    }
    
    func attributesFor(_ style: MarkStyles) -> [String: AnyObject] {
        switch style {
        case .bold:
            return [NSFontAttributeName: bold]
        case .italic:
            return [NSFontAttributeName: italic]
        case .boldItalic:
            return [NSFontAttributeName: boldItalic!]
        case .code:
            return [NSFontAttributeName: code!,
                    NSForegroundColorAttributeName: forgroundColor]
        default:
            return [NSFontAttributeName: none]
        }
    }
    
    // MARK: - Open/Close Mark Check
    
    private func isOpenMarkAt(_ index: Int, mark: String) -> Bool {
        guard index < text.characters.count else {
            return false
        }
        return String(text[index + 1]) != mark && text[index + 1] != " "
    }
    
    private func isClosingMarkAt(_ index: Int, mark: String) -> Bool {
        
        guard index > 0 else {
            return false
        }
        return String(text[index - 1]) != mark && text[index - 1] != " "
    }
    

}


// ----- debug indication code -----
// #################################

//let lowerBound = text.index(at: lowerB)
//let upperBound = text.index(at: upperB)
//let range = Range(uncheckedBounds: (lowerBound, upperBound))
//print("res from text: - " + text.substring(with: range))


{'fraction_non_alphanumeric': 0.09317755661329871, 'fraction_numerical': 0.011683254002596278, 'mean_word_length': 1.877567960157709, 'pattern_counts': {'":': 0, '<': 11, '<?xml version=': 0, '>': 24, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '107602', 'n_tokens_mistral': 4098, 'n_tokens_neox': 3798, 'n_words': 949}