 /// defined style.
 ///
 protocol AttributeFormatter {
-    /// Checks if the attribute is present in a dictionary of attributes.
+
+    /// Toggles an attribute in the specified range of a text storage.
     ///
-    func present(in attributes: [String: AnyObject]) -> Bool
+    /// The application range might be different than the passed range, as
+    /// explained in `applicationRange(for:in:)`
+    ///
+    func toggle(in text: NSMutableAttributedString, at range: NSRange)
 
     /// Apply the compound attributes to the provided attributes dictionary
     ///

    ///
    func remove(from attributes: [String: Any]) -> [String: Any]

    /// The range to apply the attributes to.
    ///
    /// By default, this returns the passed `range`, but implementations of this

    /// different range (e.g. a paragraph)
    ///
    func applicationRange(for range: NSRange, in string: NSAttributedString) -> NSRange
}


// MARK: - Default Implementations
//
extension AttributeFormatter {

    /// Indicates whether the Formatter's Attributes are present in a given string, at a specified Index.
    ///
    func present(in text: NSAttributedString, at index: Int) -> Bool {
        let safeIndex = max(min(index, text.length - 1), 0)
        let attributes = text.attributes(at: safeIndex, effectiveRange: nil) as [String : AnyObject]
        return present(in: attributes)
    }

    /// The range to apply the attributes to.
    ///
    func applicationRange(for range: NSRange, in string: NSAttributedString) -> NSRange {
        return range
    }
}


// MARK: - Private Helpers
//
private extension AttributeFormatter {

    /// The string to be used when adding attributes to an empty line.
    ///
    var placeholderForAttributedEmptyLine: String {
        return "\u{200B}"
    }

    /// Inserts an empty placeholder, into a given string, at the specified index.
    ///
    func insertEmptyAttribute(in string: NSMutableAttributedString, at index: Int) {
        let attributes = apply(to: [:])
        let attributedSpace = NSAttributedString(string: placeholderForAttributedEmptyLine, attributes: attributes)
        string.insert(attributedSpace, at: index)
    }

    /// Toggles the Attribute Format, into a given string, at the specified range.
    ///
    func toggleAttributes(in string: NSMutableAttributedString, at range: NSRange) {
        guard range.length > 0 || string.length > 0 else {
            return
        }

        if present(in: string, at: range.location) {
            removeAttributes(from: string, at: range)
        } else {
            applyAttributes(to: string, at: range)
        }
    }

    /// Applies the Formatter's Attributes into a given string, at the specified range.
    ///
    func applyAttributes(to string: NSMutableAttributedString, at range: NSRange) {
        let currentAttributes = string.attributes(at: range.location, effectiveRange: nil)
        let attributes = apply(to: currentAttributes)
        string.addAttributes(attributes, range: range)
    }

    /// Removes the Formatter's Attributes from a given string, at the specified range.
    ///
    func removeAttributes(from string: NSMutableAttributedString, at range: NSRange) {
        let currentAttributes = string.attributes(at: range.location, effectiveRange: nil)
        let attributes = remove(from: currentAttributes)
        string.addAttributes(attributes, range: range)
    }
}


// MARK: - Character Attribute Formatter
//
protocol CharacterAttributeFormatter: AttributeFormatter {
}

extension CharacterAttributeFormatter {
    func toggle(in text: NSMutableAttributedString, at range: NSRange) {
        let applicationRange = self.applicationRange(for: range, in: text)
        toggleAttributes(in: text, at: applicationRange)
    }
}


// MARK: - Paragraph Attribute Formatter
//
protocol ParagraphAttributeFormatter: AttributeFormatter {
}

