//
//  AttributedString+Trim.swift
//  Tusker
//
//  Created by Shadowfacts on 8/29/18.
//  Copyright © 2018 Shadowfacts. All rights reserved.
//

import Foundation

private let ASCII_NEWLINE: unichar = 10
private let ASCII_SPACE: unichar = 32

extension NSAttributedString {

    var fullRange: NSRange {
        return NSRange(location: 0, length: self.length)
    }

    /// Creates a new string with the whitespace collapsed according to the CSS Text Module Level 3 rules.
    /// See https://www.w3.org/TR/css-text-3/#white-space-phase-1
    func collapsingWhitespace() -> NSAttributedString {
        let mut = NSMutableAttributedString(attributedString: self)
        mut.collapseWhitespace()
        return mut
    }

}

extension NSMutableAttributedString {

    func trimLeadingCharactersInSet(_ charSet: CharacterSet) {
        var range = (string as NSString).rangeOfCharacter(from: charSet)

        while range.length != 0 && range.location == 0 {
            replaceCharacters(in: range, with: "")
            range = (string as NSString).rangeOfCharacter(from: charSet)
        }
    }

    func trimTrailingCharactersInSet(_ charSet: CharacterSet) {
        var range = (string as NSString).rangeOfCharacter(from: charSet, options: .backwards)

        while range.length != 0 && range.length + range.location == length {
            replaceCharacters(in: range, with: "")
            range = (string as NSString).rangeOfCharacter(from: charSet, options: .backwards)
        }
    }

    /// Collapses whitespace in this string according to the CSS Text Module Level 3 rules.
    /// See https://www.w3.org/TR/css-text-3/#white-space-phase-1
    func collapseWhitespace() {
        let str = self.mutableString
        
        var i = 0
        while i < str.length {
            if str.character(at: i) == ASCII_NEWLINE {
                var j: Int
                if i > 0 {
                    // scan backwards to find beginning of space characters preceeding newline
                    j = i - 1
                    while j >= 0 {
                        if str.character(at: j) != ASCII_SPACE {
                            break
                        }
                        j -= 1
                    }
                    // add one after loop completes because start of range is _inclusive_
                    j += 1
                } else {
                    j = 0
                }
                
                var k: Int
                if i < str.length - 1 {
                    // scan forwards to find end of space characters following newline
                    k = i + 1
                    while k < str.length {
                        if str.character(at: k) != ASCII_SPACE {
                            break
                        }
                        k += 1
                    }
                    // don't need to subtract one before breaking out of loop, because end of range is _exclusive_
                } else {
                    // range end is _exclusive_, so use whole string length that way last character is included
                    k = str.length
                }
                
                // if there's only one character to be replaced, that means we'd be replacing the newline with a newline, so don't bother
                if k - j > 1 {
                    str.replaceCharacters(in: NSRange(location: j, length: k - j), with: "\n")
                    
                    // continue scanning through the string starting after the newline we just inserted
                    i = j
                }
            }
            i += 1
        }
    }
    
}
