// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.

import SignalCoreKit

public extension String {
    var glyphCount: Int {
        let richText = NSAttributedString(string: self)
        let line = CTLineCreateWithAttributedString(richText)
        
        return CTLineGetGlyphCount(line)
    }
    
    var isSingleAlphabet: Bool {
        return (glyphCount == 1 && isAlphabetic)
    }
    
    var isAlphabetic: Bool {
        return !isEmpty && range(of: "[^a-zA-Z]", options: .regularExpression) == nil
    }

    var isSingleEmoji: Bool {
        return (glyphCount == 1 && containsEmoji)
    }

    var containsEmoji: Bool {
        return unicodeScalars.contains { $0.isEmoji }
    }

    var containsOnlyEmoji: Bool {
        return (
            !isEmpty &&
            !unicodeScalars.contains(where: {
                !$0.isEmoji &&
                !$0.isZeroWidthJoiner
            })
        )
    }
    
    func localized() -> String {
        // If the localized string matches the key provided then the localisation failed
        let localizedString = NSLocalizedString(self, comment: "")
        owsAssertDebug(localizedString != self, "Key \"\(self)\" is not set in Localizable.strings")

        return localizedString
    }
    
    func ranges(of substring: String, options: CompareOptions = [], locale: Locale? = nil) -> [Range<Index>] {
        var ranges: [Range<Index>] = []
        
        while
            (ranges.last.map({ $0.upperBound < self.endIndex }) ?? true),
            let range = self.range(
                of: substring,
                options: options,
                range: (ranges.last?.upperBound ?? self.startIndex)..<self.endIndex,
                locale: locale
            )
        {
            ranges.append(range)
        }
        
        return ranges
    }
    
    static func filterNotificationText(_ text: String?) -> String? {
        guard let text = text?.filterStringForDisplay() else { return nil }

        // iOS strips anything that looks like a printf formatting character from
        // the notification body, so if we want to dispay a literal "%" in a notification
        // it must be escaped.
        // see https://developer.apple.com/documentation/uikit/uilocalnotification/1616646-alertbody
        // for more details.
        return text.replacingOccurrences(of: "%", with: "%%")
    }
}

// MARK: - Formatting

public extension String {
    static func formattedDuration(_ duration: TimeInterval, format: TimeInterval.DurationFormat = .short) -> String {
        let secondsPerMinute: TimeInterval = 60
        let secondsPerHour: TimeInterval = (secondsPerMinute * 60)
        let secondsPerDay: TimeInterval = (secondsPerHour * 24)
        let secondsPerWeek: TimeInterval = (secondsPerDay * 7)
        
        switch format {
            case .hoursMinutesSeconds:
                let seconds: Int = Int(duration.truncatingRemainder(dividingBy: 60))
                let minutes: Int = Int((duration / 60).truncatingRemainder(dividingBy: 60))
                let hours: Int = Int(duration / 3600)
                
                guard hours > 0 else { return String(format: "%ld:%02ld", minutes, seconds) }
                
                return String(format: "%ld:%02ld:%02ld", hours, minutes, seconds)
                
            case .short:
                switch duration {
                    case 0..<secondsPerMinute:  // Seconds
                        return String(
                            format: "TIME_AMOUNT_SECONDS_SHORT_FORMAT".localized(),
                            NumberFormatter.localizedString(
                                from: NSNumber(floatLiteral: floor(duration)),
                                number: .none
                            )
                        )
                    
                    case secondsPerMinute..<secondsPerHour:   // Minutes
                        return String(
                            format: "TIME_AMOUNT_MINUTES_SHORT_FORMAT".localized(),
                            NumberFormatter.localizedString(
                                from: NSNumber(floatLiteral: floor(duration / secondsPerMinute)),
                                number: .none
                            )
                        )
                        
                    case secondsPerHour..<secondsPerDay:   // Hours
                        return String(
                            format: "TIME_AMOUNT_HOURS_SHORT_FORMAT".localized(),
                            NumberFormatter.localizedString(
                                from: NSNumber(floatLiteral: floor(duration / secondsPerHour)),
                                number: .none
                            )
                        )
                        
                    case secondsPerDay..<secondsPerWeek:   // Days
                        return String(
                            format: "TIME_AMOUNT_DAYS_SHORT_FORMAT".localized(),
                            NumberFormatter.localizedString(
                                from: NSNumber(floatLiteral: floor(duration / secondsPerDay)),
                                number: .none
                            )
                        )
                        
                    default:   // Weeks
                        return String(
                            format: "TIME_AMOUNT_WEEKS_SHORT_FORMAT".localized(),
                            NumberFormatter.localizedString(
                                from: NSNumber(floatLiteral: floor(duration / secondsPerWeek)),
                                number: .none
                            )
                        )
                }
                
            case .long:
                switch duration {
                    case 0..<secondsPerMinute:  // XX Seconds
                        return String(
                            format: "TIME_AMOUNT_SECONDS".localized(),
                            NumberFormatter.localizedString(
                                from: NSNumber(floatLiteral: floor(duration)),
                                number: .none
                            )
                        )
                    
                    case secondsPerMinute..<(secondsPerMinute * 1.5):   // 1 Minute
                        return String(
                            format: "TIME_AMOUNT_SINGLE_MINUTE".localized(),
                            NumberFormatter.localizedString(
                                from: NSNumber(floatLiteral: floor(duration / secondsPerMinute)),
                                number: .none
                            )
                        )
                        
                    case (secondsPerMinute * 1.5)..<secondsPerHour:   // Multiple Minutes
                        return String(
                            format: "TIME_AMOUNT_MINUTES".localized(),
                            NumberFormatter.localizedString(
                                from: NSNumber(floatLiteral: floor(duration / secondsPerMinute)),
                                number: .none
                            )
                        )
                        
                    case secondsPerHour..<(secondsPerHour * 1.5):   // 1 Hour
                        return String(
                            format: "TIME_AMOUNT_SINGLE_HOUR".localized(),
                            NumberFormatter.localizedString(
                                from: NSNumber(floatLiteral: floor(duration / secondsPerHour)),
                                number: .none
                            )
                        )
                        
                    case (secondsPerHour * 1.5)..<secondsPerDay:   // Multiple Hours
                        return String(
                            format: "TIME_AMOUNT_HOURS".localized(),
                            NumberFormatter.localizedString(
                                from: NSNumber(floatLiteral: floor(duration / secondsPerHour)),
                                number: .none
                            )
                        )
                        
                    case secondsPerDay..<(secondsPerDay * 1.5):   // 1 Day
                        return String(
                            format: "TIME_AMOUNT_SINGLE_DAY".localized(),
                            NumberFormatter.localizedString(
                                from: NSNumber(floatLiteral: floor(duration / secondsPerDay)),
                                number: .none
                            )
                        )
                        
                    case (secondsPerDay * 1.5)..<secondsPerWeek:   // Multiple Days
                        return String(
                            format: "TIME_AMOUNT_DAYS".localized(),
                            NumberFormatter.localizedString(
                                from: NSNumber(floatLiteral: floor(duration / secondsPerDay)),
                                number: .none
                            )
                        )
                        
                    case secondsPerWeek..<(secondsPerWeek * 1.5):   // 1 Week
                        return String(
                            format: "TIME_AMOUNT_SINGLE_WEEK".localized(),
                            NumberFormatter.localizedString(
                                from: NSNumber(floatLiteral: floor(duration / secondsPerWeek)),
                                number: .none
                            )
                        )
                        
                    default:   // Multiple Weeks
                        return String(
                            format: "TIME_AMOUNT_WEEKS".localized(),
                            NumberFormatter.localizedString(
                                from: NSNumber(floatLiteral: floor(duration / secondsPerWeek)),
                                number: .none
                            )
                        )
                }
        }
    }
}