//
//  StringUtil.swift
//  Runner
//
//  Created by Yayan Dwi on 20/04/20.
//  Copyright © 2020 The Chromium Authors. All rights reserved.
//

import Foundation
import UIKit
import SDWebImage

extension Date {
    
    public func currentTimeMillis() -> Int {
        return Int(self.timeIntervalSince1970 * 1000)
    }
    
    func format(dateFormat: String) -> String {
        let formatter = DateFormatter()
        formatter.dateFormat = dateFormat
        return formatter.string(from: self)
    }
    
    var millisecondsSince1970:Int64 {
        return Int64((self.timeIntervalSince1970 * 1000.0).rounded())
    }
    
    public init(milliseconds:Int64) {
        self = Date(timeIntervalSince1970: TimeInterval(milliseconds) / 1000)
    }
}

extension String {
    
    func toNormalString() -> String {
        let _source = self.replacingOccurrences(of: "+", with: "%20")
        if var result = _source.removingPercentEncoding {
            result = result.replacingOccurrences(of: "<NL>", with: "\n")
            result = result.replacingOccurrences(of: "<CR>", with: "\r")
            return decrypt(source: result)
        }
        return self
    }
    
    func toStupidString() -> String {
        if var result = self.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) {
            result = result.replacingOccurrences(of: "\n", with: "<NL>")
            result = result.replacingOccurrences(of: "\r", with: "<CR>")
            result = result.replacingOccurrences(of: "+", with: "%2B")
            return result
        }
        return self
    }
    
    private func decrypt(source : String) -> String {
        if let result = source.removingPercentEncoding {
            return result
        }
        return source
    }
    
    public func matches(_ regex: String) -> Bool {
        return self.range(of: regex, options: .regularExpression, range: nil, locale: nil) != nil
    }
    
}

extension Int {
    
    func toHex() -> String {
        return String(format: "%02X", self)
    }
    
}

extension UIApplication {
    public static var appVersion: String? {
        return Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String
    }
    
    var rootViewController: UIViewController? {
        return UIApplication.shared.windows.filter {$0.isKeyWindow}.first?.rootViewController
    }
    
    public var visibleViewController: UIViewController? {
        let keyWindow = UIApplication.shared.windows.filter {$0.isKeyWindow}.first
        if var topController = keyWindow?.rootViewController {
            while let presentedViewController = topController.presentedViewController {
                topController = presentedViewController
            }
            return topController
        }
        return nil
    }
}

extension UIView {
    
    public func anchor(top: NSLayoutYAxisAnchor? = nil,
                left: NSLayoutXAxisAnchor? = nil,
                bottom: NSLayoutYAxisAnchor? = nil,
                right: NSLayoutXAxisAnchor? = nil,
                paddingTop: CGFloat = 0,
                paddingLeft: CGFloat = 0,
                paddingBottom: CGFloat = 0,
                paddingRight: CGFloat = 0,
                centerX: NSLayoutXAxisAnchor? = nil,
                centerY: NSLayoutYAxisAnchor? = nil,
                width: CGFloat = 0,
                height: CGFloat = 0,
                minHeight: CGFloat = 0,
                maxHeight: CGFloat = 0,
                minWidth: CGFloat = 0,
                maxWidth: CGFloat = 0,
                dynamicLeft: Bool = false,
                dynamicRight: Bool = false) {
        
        translatesAutoresizingMaskIntoConstraints = false
        
        if let top = top {
            topAnchor.constraint(equalTo: top, constant: paddingTop).isActive = true
        }
        if let left = left {
            if dynamicLeft {
                leftAnchor.constraint(greaterThanOrEqualTo: left, constant: paddingLeft).isActive = true
            } else {
                leftAnchor.constraint(equalTo: left, constant: paddingLeft).isActive = true
            }
        }
        if let right = right {
            if dynamicRight {
                leftAnchor.constraint(lessThanOrEqualTo: right, constant: -paddingRight).isActive = true
            } else {
                rightAnchor.constraint(equalTo: right, constant: -paddingRight).isActive = true
            }
        }
        if let bottom = bottom {
            bottomAnchor.constraint(equalTo: bottom, constant: -paddingBottom).isActive = true
        }
        if let centerX = centerX {
            centerXAnchor.constraint(equalTo: centerX).isActive = true
        }
        if let centerY = centerY {
            centerYAnchor.constraint(equalTo: centerY).isActive = true
        }
        if height != 0 || minHeight != 0 || maxHeight != 0 {
            if minHeight != 0 && maxHeight != 0 {
                heightAnchor.constraint(greaterThanOrEqualToConstant: minHeight).isActive = true
                heightAnchor.constraint(lessThanOrEqualToConstant: maxHeight).isActive = true
            } else if minHeight != 0 && maxHeight == 0 {
                heightAnchor.constraint(greaterThanOrEqualToConstant: minHeight).isActive = true
            } else if minHeight == 0 && maxHeight != 0 {
                heightAnchor.constraint(lessThanOrEqualToConstant: maxHeight).isActive = true
            } else {
                heightAnchor.constraint(equalToConstant: height).isActive = true
            }
        }
        if width != 0 || minWidth != 0 || maxWidth != 0 {
            if minWidth != 0 && maxWidth != 0 {
                heightAnchor.constraint(greaterThanOrEqualToConstant: minWidth).isActive = true
                heightAnchor.constraint(lessThanOrEqualToConstant: maxWidth).isActive = true
            } else if minWidth != 0 && maxWidth == 0 {
                heightAnchor.constraint(greaterThanOrEqualToConstant: minWidth).isActive = true
            } else if minWidth == 0 && maxWidth != 0 {
                heightAnchor.constraint(lessThanOrEqualToConstant: maxWidth).isActive = true
            } else {
                widthAnchor.constraint(equalToConstant: width).isActive = true
            }
        }
    }
    
    public func addTopBorder(with color: UIColor?, andWidth borderWidth: CGFloat, view: UIView = UIView()) {
        let border = view
        border.backgroundColor = color
        border.autoresizingMask = [.flexibleWidth, .flexibleBottomMargin]
        border.frame = CGRect(x: 0, y: 0, width: frame.size.width, height: borderWidth)
        addSubview(border)
    }

    public func addBottomBorder(with color: UIColor?, andWidth borderWidth: CGFloat, x: CGFloat = 0, view: UIView = UIView()) {
        let border = view
        border.backgroundColor = color
        border.autoresizingMask = [.flexibleWidth, .flexibleTopMargin]
        border.frame = CGRect(x: x, y: frame.size.height - borderWidth, width: frame.size.width, height: borderWidth)
        addSubview(border)
    }

    public func addLeftBorder(with color: UIColor?, andWidth borderWidth: CGFloat) {
        let border = UIView()
        border.backgroundColor = color
        border.frame = CGRect(x: 0, y: 0, width: borderWidth, height: frame.size.height)
        border.autoresizingMask = [.flexibleHeight, .flexibleRightMargin]
        addSubview(border)
    }

    public func addRightBorder(with color: UIColor?, andWidth borderWidth: CGFloat) {
        let border = UIView()
        border.backgroundColor = color
        border.autoresizingMask = [.flexibleHeight, .flexibleLeftMargin]
        border.frame = CGRect(x: frame.size.width - borderWidth, y: 0, width: borderWidth, height: frame.size.height)
        addSubview(border)
    }
    
}

extension UIViewController {
    
    var previousViewController: UIViewController? {
        guard let navigationController = navigationController else { return nil }
        let count = navigationController.viewControllers.count
        return count < 2 ? nil : navigationController.viewControllers[count - 2]
    }
    
}

extension UIImage {
    func resize(target: CGSize) -> UIImage {
        // Determine the scale factor that preserves aspect ratio
        let widthRatio = target.width / size.width
        let heightRatio = target.height / size.height
        
        let scaleFactor = min(widthRatio, heightRatio)
        
        // Compute the new image size that preserves aspect ratio
        let scaledImageSize = CGSize(
            width: size.width * scaleFactor,
            height: size.height * scaleFactor
        )
        
        // Draw and return the resized UIImage
        let renderer = UIGraphicsImageRenderer(
            size: scaledImageSize
        )
        
        let scaledImage = renderer.image { _ in
            self.draw(in: CGRect(
                origin: .zero,
                size: scaledImageSize
            ))
        }
        
        return scaledImage
    }
}

extension UIImage {
    
    var isPortrait:  Bool    { size.height > size.width }
    var isLandscape: Bool    { size.width > size.height }
    var breadth:     CGFloat { min(size.width, size.height) }
    var breadthSize: CGSize  { .init(width: breadth, height: breadth) }
    var breadthRect: CGRect  { .init(origin: .zero, size: breadthSize) }
    public var circleMasked: UIImage? {
        guard let cgImage = cgImage?
                .cropping(to: .init(origin: .init(x: isLandscape ? ((size.width-size.height)/2).rounded(.down) : 0,
                                                  y: isPortrait  ? ((size.height-size.width)/2).rounded(.down) : 0),
                                    size: breadthSize)) else { return nil }
        let format = imageRendererFormat
        format.opaque = false
        return UIGraphicsImageRenderer(size: breadthSize, format: format).image { _ in
            UIBezierPath(ovalIn: breadthRect).addClip()
            UIImage(cgImage: cgImage, scale: format.scale, orientation: imageOrientation)
                .draw(in: .init(origin: .zero, size: breadthSize))
        }
    }
    
}

extension NSObject {
    
    private static var urlStore = [String:String]()

    public func getImage(name url: String, placeholderImage: UIImage? = nil, isCircle: Bool = false, tableView: UITableView? = nil, indexPath: IndexPath? = nil, completion: @escaping (Bool, Bool, UIImage?)->()) {
        let tmpAddress = String(format: "%p", unsafeBitCast(self, to: Int.self))
        type(of: self).urlStore[tmpAddress] = url
        if url.isEmpty {
            completion(false, false, placeholderImage)
            return
        }
        do {
            let documentDir = try FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
            let file = documentDir.appendingPathComponent(url)
            if FileManager().fileExists(atPath: file.path) {
                let image = UIImage(contentsOfFile: file.path)?.sd_resizedImage(with: CGSize(width: 400, height: 400), scaleMode: .aspectFill)
                completion(true, false, isCircle ? image?.circleMasked : image)
            } else {
                completion(false, false, placeholderImage)
                Download().start(forKey: url) { (name, progress) in
                    guard progress == 100 else {
                        return
                    }
                    
                    DispatchQueue.main.async {
                        if tableView != nil {
                            tableView!.beginUpdates()
                            tableView!.reloadRows(at: [indexPath!], with: .none)
                            tableView!.endUpdates()
                        }
                        if type(of: self).urlStore[tmpAddress] == name {
                            let image = UIImage(contentsOfFile: file.path)?.sd_resizedImage(with: CGSize(width: 400, height: 400), scaleMode: .aspectFill)
                            completion(true, true, isCircle ? image?.circleMasked : image)
                        }
                    }
                }
            }
        } catch {}
    }
    
    func loadImage(named: String, placeholderImage: UIImage?, completion: @escaping (UIImage?, Bool) -> ()) {
        guard !named.isEmpty else {
            completion(placeholderImage, true)
            return
        }
        SDWebImageManager.shared.loadImage(with: URL.palioImage(named: named), options: .highPriority, progress: .none) { image, data, error, type, finish, url in
            completion(image, finish)
        }
    }
    
    public func deleteAllRecordDatabase() {
        Database.shared.database?.inTransaction({ fmdb, rollback in
            _ = Database.shared.deleteRecord(fmdb: fmdb, table: "BUDDY", _where: "")
            _ = Database.shared.deleteRecord(fmdb: fmdb, table: "GROUPZ", _where: "")
            _ = Database.shared.deleteRecord(fmdb: fmdb, table: "MESSAGE", _where: "")
            _ = Database.shared.deleteRecord(fmdb: fmdb, table: "GROUPZ_MEMBER", _where: "")
            _ = Database.shared.deleteRecord(fmdb: fmdb, table: "DISCUSSION_FORUM", _where: "")
            _ = Database.shared.deleteRecord(fmdb: fmdb, table: "POST", _where: "")
            _ = Database.shared.deleteRecord(fmdb: fmdb, table: "MESSAGE_STATUS", _where: "")
            _ = Database.shared.deleteRecord(fmdb: fmdb, table: "MESSAGE_SUMMARY", _where: "")
            _ = Database.shared.deleteRecord(fmdb: fmdb, table: "OUTGOING", _where: "")
            _ = Database.shared.deleteRecord(fmdb: fmdb, table: "FOLLOW", _where: "")
            _ = Database.shared.deleteRecord(fmdb: fmdb, table: "MESSAGE_FAVORITE", _where: "")
            _ = Database.shared.deleteRecord(fmdb: fmdb, table: "LINK_PREVIEW", _where: "")
            _ = Database.shared.deleteRecord(fmdb: fmdb, table: "PULL_DB", _where: "")
            _ = Database.shared.deleteRecord(fmdb: fmdb, table: "PREFS", _where: "")
            _ = Database.shared.deleteRecord(fmdb: fmdb, table: "CALL_CENTER_HISTORY", _where: "")
            _ = Database.shared.deleteRecord(fmdb: fmdb, table: "FORM_DATA", _where: "")
            _ = Database.shared.deleteRecord(fmdb: fmdb, table: "TASK_PIC", _where: "")
            _ = Database.shared.deleteRecord(fmdb: fmdb, table: "TASK_DETAIL", _where: "")
        })
    }
    
}

extension URL {
    
    static func palioImage(named: String) -> URL? {
        return URL(string: "http://202.158.33.26/filepalio/image/\(named)")
    }
    
}

extension UIColor {
    public static var mainColor: UIColor {
        renderColor(hex: "#3669ad")
    }
    
    public static var secondaryColor: UIColor {
        return renderColor(hex: "#FAFAFF")
    }
    
    public static var orangeColor: UIColor {
        return renderColor(hex: "#FFA03E")
    }
    
    public static var orangeBNI: UIColor {
        return renderColor(hex: "#EE6600")
    }
    
    public static var greenColor: UIColor {
        return renderColor(hex: "#C7EA46")
    }
    
    public static var grayColor: UIColor {
        return renderColor(hex: "#F5F5F5")
    }
    
    public static var docColor: UIColor {
        return renderColor(hex: "#798F9A")
    }
    
    public static var mentionColor: UIColor {
        return renderColor(hex: "#53bdea")
    }
    
    public static var blueBubbleColor: UIColor {
        return renderColor(hex: "#C5D1E1")
    }
    
    public static var officialColor: UIColor {
        return renderColor(hex: "#4c87ef")
    }
    
    public static var verifiedColor: UIColor {
        return renderColor(hex: "#00b333")
    }
    
    public static var ccColor: UIColor {
        return renderColor(hex: "#FFF1A353")
    }
    
    public static var internalColor: UIColor {
        return renderColor(hex: "#ff0000")
    }
    
    public class func renderColor(hex: String) -> UIColor {
        var cString:String = hex.trimmingCharacters(in: .whitespacesAndNewlines).uppercased()

        if (cString.hasPrefix("#")) {
            cString.remove(at: cString.startIndex)
        }

        if ((cString.count) != 6) {
            return UIColor.gray
        }

        var rgbValue:UInt64 = 0
        Scanner(string: cString).scanHexInt64(&rgbValue)

        return UIColor(
            red: CGFloat((rgbValue & 0xFF0000) >> 16) / 255.0,
            green: CGFloat((rgbValue & 0x00FF00) >> 8) / 255.0,
            blue: CGFloat(rgbValue & 0x0000FF) / 255.0,
            alpha: CGFloat(1.0)
        )
    }
}

extension UIView {
    
    public func circle() {
        layer.cornerRadius = 0.5 * bounds.size.width
        clipsToBounds = true
    }
    
    public func maxCornerRadius() -> CGFloat {
        return (self.frame.width > self.frame.height) ? self.frame.height / 2 : self.frame.width / 2
    }
    
}

extension String {
    
    public func localized(uppercased: Bool = false) -> String {
        if let _ = UserDefaults.standard.string(forKey: "i18n_language") {} else {
            // we set a default, just in case
            let langDefault = UserDefaults.standard.stringArray(forKey: "AppleLanguages")
            if langDefault![0].contains("id") {
                UserDefaults.standard.set("id", forKey: "i18n_language")
                UserDefaults.standard.synchronize()
            } else {
                UserDefaults.standard.set("en", forKey: "i18n_language")
                UserDefaults.standard.synchronize()
            }
        }
        let lang = UserDefaults.standard.string(forKey: "i18n_language")
        let bundle = Bundle.resourceBundle(for: Nexilis.self).path(forResource: lang, ofType: "lproj")
        let bundlePath = Bundle(path: bundle!)
        let result = NSLocalizedString(
            self,
            tableName: "Localizable",
            bundle: bundlePath!,
            value: self,
            comment: self)
        if uppercased {
            return result.uppercased()
        }
        return result
    }
    
}

extension UIViewController {
    
    public func showToast(message : String, font: UIFont = UIFont.systemFont(ofSize: 12, weight: .medium), controller: UIViewController) {
        
        let toastContainer = UIView(frame: CGRect())
        toastContainer.backgroundColor = UIColor.mainColor.withAlphaComponent(0.6)
        toastContainer.alpha = 0.0
        toastContainer.layer.cornerRadius = 25;
        toastContainer.clipsToBounds  =  true
        
        let toastLabel = UILabel(frame: CGRect())
        toastLabel.textColor = UIColor.white
        toastLabel.textAlignment = .center;
        toastLabel.font = font
        toastLabel.text = message
        toastLabel.clipsToBounds  =  true
        toastLabel.numberOfLines = 0
        
        toastContainer.addSubview(toastLabel)
        controller.view.addSubview(toastContainer)
        controller.view.bringSubviewToFront(toastContainer)
        
        toastLabel.translatesAutoresizingMaskIntoConstraints = false
        toastContainer.translatesAutoresizingMaskIntoConstraints = false
        
        let a1 = NSLayoutConstraint(item: toastLabel, attribute: .leading, relatedBy: .equal, toItem: toastContainer, attribute: .leading, multiplier: 1, constant: 15)
        let a2 = NSLayoutConstraint(item: toastLabel, attribute: .trailing, relatedBy: .equal, toItem: toastContainer, attribute: .trailing, multiplier: 1, constant: -15)
        let a3 = NSLayoutConstraint(item: toastLabel, attribute: .bottom, relatedBy: .equal, toItem: toastContainer, attribute: .bottom, multiplier: 1, constant: -15)
        let a4 = NSLayoutConstraint(item: toastLabel, attribute: .top, relatedBy: .equal, toItem: toastContainer, attribute: .top, multiplier: 1, constant: 15)
        toastContainer.addConstraints([a1, a2, a3, a4])
        
        let c1 = NSLayoutConstraint(item: toastContainer, attribute: .leading, relatedBy: .equal, toItem: controller.view, attribute: .leading, multiplier: 1, constant: 65)
        let c2 = NSLayoutConstraint(item: toastContainer, attribute: .trailing, relatedBy: .equal, toItem: controller.view, attribute: .trailing, multiplier: 1, constant: -65)
        let c3 = NSLayoutConstraint(item: toastContainer, attribute: .bottom, relatedBy: .equal, toItem: controller.view, attribute: .bottom, multiplier: 1, constant: -75)
        controller.view.addConstraints([c1, c2, c3])
        
        UIView.animate(withDuration: 0.5, delay: 0.0, options: .curveEaseIn, animations: {
            toastContainer.alpha = 1.0
        }, completion: { _ in
            UIView.animate(withDuration: 0.5, delay: 1.5, options: .curveEaseOut, animations: {
                toastContainer.alpha = 0.0
            }, completion: {_ in
                toastContainer.removeFromSuperview()
            })
        })
    }
    
    public func resizeImage(image: UIImage, targetSize: CGSize) -> UIImage {
        UIGraphicsBeginImageContextWithOptions(targetSize, false, 0.0);
        image.draw(in: CGRect(x: 0, y: 0, width: targetSize.width, height: targetSize.height))
        let newImage:UIImage = UIGraphicsGetImageFromCurrentImageContext()!
        UIGraphicsEndImageContext()
        return newImage
    }
    
}

extension UITextView {

    enum ShouldChangeCursor {
        case incrementCursor
        case preserveCursor
    }

    func preserveCursorPosition(withChanges mutatingFunction: (UITextPosition?) -> (ShouldChangeCursor)) {

        //save the cursor positon
        var cursorPosition: UITextPosition? = nil
        if let selectedRange = self.selectedTextRange {
            let offset = self.offset(from: self.beginningOfDocument, to: selectedRange.start)
            cursorPosition = self.position(from: self.beginningOfDocument, offset: offset)
        }

        //make mutaing changes that may reset the cursor position
        let shouldChangeCursor = mutatingFunction(cursorPosition)

        //restore the cursor
        if var cursorPosition = cursorPosition {

            if shouldChangeCursor == .incrementCursor {
                cursorPosition = self.position(from: cursorPosition, offset: 1) ?? cursorPosition
            }

            if let range = self.textRange(from: cursorPosition, to: cursorPosition) {
                self.selectedTextRange = range
            }
        }

    }

}

extension String {
    
    public func substring(from: Int?, to: Int?) -> String {
        if let start = from {
            guard start < self.count else {
                return ""
            }
        }
        
        if let end = to {
            guard end >= 0 else {
                return ""
            }
        }
        
        if let start = from, let end = to {
            guard end - start >= 0 else {
                return ""
            }
        }
        
        let startIndex: String.Index
        if let start = from, start >= 0 {
            startIndex = self.index(self.startIndex, offsetBy: start)
        } else {
            startIndex = self.startIndex
        }
        
        let endIndex: String.Index
        if let end = to, end >= 0, end < self.count {
            endIndex = self.index(self.startIndex, offsetBy: end + 1)
        } else {
            endIndex = self.endIndex
        }
        
        return String(self[startIndex ..< endIndex])
    }
    
    func countEmojiCharacter() -> Int {
        
        func isEmoji(s:NSString) -> Bool {
            
            let high:Int = Int(s.character(at: 0))
            if 0xD800 <= high && high <= 0xDBFF {
                let low:Int = Int(s.character(at: 1))
                let codepoint: Int = ((high - 0xD800) * 0x400) + (low - 0xDC00) + 0x10000
                return (0x1D000 <= codepoint && codepoint <= 0x1F9FF)
            }
            else {
                return (0x2100 <= high && high <= 0x27BF)
            }
        }
        
        let nsString = self as NSString
        var length = 0
        
        nsString.enumerateSubstrings(in: NSMakeRange(0, nsString.length), options: NSString.EnumerationOptions.byComposedCharacterSequences) { (subString, substringRange, enclosingRange, stop) -> Void in
            
            if isEmoji(s: subString! as NSString) {
                length+=1
            }
        }
        
        return length
    }
    
    public func richText(isEditing: Bool = false, isSearching: Bool = false, textSearch: String = "", group_id: String = "", listMentionInTextField: [User] = []) -> NSMutableAttributedString {
        let font = UIFont.systemFont(ofSize: 12)
        let textUTF8 = String(self.utf8)
        let finalText = NSMutableAttributedString(string: textUTF8, attributes: [NSAttributedString.Key.font: font])
        let boldSign: Character = "*"
        let italicSign: Character = "_"
        let underlineSign: Character = "^"
        let strikethroughSign: Character = "~"
        
        //Bold
        let rangeBold = getRangeOfWordWithSign(sentence: textUTF8, sign: boldSign)
        if rangeBold.count > 0 {
            var lastFirstRange = 0
            var countRemoveBoldSign = 0
            var continueCheckingBold = false
            var totalEmoji = 0
            for i in 0..<rangeBold.count {
                if lastFirstRange == 0 {
                    if (rangeBold[i].startIndex == 0 || checkCharBeforeAfter(char: textUTF8.substring(from: rangeBold[i].startIndex - 1, to: rangeBold[i].startIndex - 1))) {
                        lastFirstRange = rangeBold[i].startIndex
                        continueCheckingBold = true
                    } else {
                        continueCheckingBold = false
                    }
                }
                if !continueCheckingBold {
                    continue
                }
                if (rangeBold[i].endIndex == textUTF8.count - 1 || checkCharBeforeAfter(char: textUTF8.substring(from: rangeBold[i].endIndex + 1, to: rangeBold[i].endIndex + 1))) {
                    let countEmojiBefore = finalText.string.substring(from: 0, to: lastFirstRange - (2*countRemoveBoldSign)).countEmojiCharacter()
                    let countEmoji = finalText.string.substring(from: lastFirstRange - (2*countRemoveBoldSign), to: rangeBold[i].endIndex - (2*countRemoveBoldSign)).countEmojiCharacter()
                    totalEmoji = countEmoji + countEmojiBefore
                    finalText.addAttribute(.font, value: font.bold, range: NSRange(location: lastFirstRange - (2*countRemoveBoldSign) + countEmojiBefore, length: (rangeBold[i].endIndex + countEmoji + 1) - lastFirstRange))
                    if !isEditing{
                        finalText.mutableString.replaceOccurrences(of: "\(boldSign)", with: "", options: .literal, range: NSRange(location: lastFirstRange + countEmojiBefore - (2*countRemoveBoldSign), length: 1))
                        finalText.mutableString.replaceOccurrences(of: "\(boldSign)", with: "", options: .literal, range: NSRange(location: rangeBold[i].endIndex + totalEmoji - (2*countRemoveBoldSign) - 1, length: 1))
                        countRemoveBoldSign += 1
                    } else {
                        finalText.addAttribute(.foregroundColor, value: UIColor.gray, range: NSRange(location: lastFirstRange + countEmojiBefore, length: 1))
                        finalText.addAttribute(.foregroundColor, value: UIColor.gray, range: NSRange(location: rangeBold[i].endIndex + totalEmoji, length: 1))
                    }
                    lastFirstRange = 0
                }
            }
        }
        
        //Italic
        let textAfterbold = finalText.string
        let rangeItalic = getRangeOfWordWithSign(sentence: textAfterbold, sign: italicSign)
        if rangeItalic.count > 0 {
            var lastFirstRange = 0
            var countRemoveItalicSign = 0
            var continueCheckingItalic = false
            var totalEmoji = 0
            for i in 0..<rangeItalic.count {
                if lastFirstRange == 0 {
                    if (rangeItalic[i].startIndex == 0 || checkCharBeforeAfter(char: textAfterbold.substring(from: rangeItalic[i].startIndex - 1, to: rangeItalic[i].startIndex - 1))) {
                        lastFirstRange = rangeItalic[i].startIndex
                        continueCheckingItalic = true
                    } else {
                        continueCheckingItalic = false
                    }
                }
                if !continueCheckingItalic {
                    continue
                }
                if (rangeItalic[i].endIndex == textAfterbold.count - 1 || checkCharBeforeAfter(char: textAfterbold.substring(from: rangeItalic[i].endIndex + 1, to: rangeItalic[i].endIndex + 1))) {
                    let countEmojiBefore = finalText.string.substring(from: 0, to: lastFirstRange - (2*countRemoveItalicSign)).countEmojiCharacter()
                    let countEmoji = finalText.string.substring(from: lastFirstRange - (2*countRemoveItalicSign), to: rangeItalic[i].endIndex - (2*countRemoveItalicSign)).countEmojiCharacter()
                    totalEmoji = countEmoji + countEmojiBefore
                    finalText.addAttribute(.font, value: font.italic, range: NSRange(location: lastFirstRange - (2*countRemoveItalicSign) + countEmojiBefore, length: (rangeItalic[i].endIndex + countEmoji + 1) - lastFirstRange))
                    if !isEditing{
                        finalText.mutableString.replaceOccurrences(of: "\(italicSign)", with: "", options: .literal, range: NSRange(location: lastFirstRange + countEmojiBefore - (2*countRemoveItalicSign), length: 1))
                        finalText.mutableString.replaceOccurrences(of: "\(italicSign)", with: "", options: .literal, range: NSRange(location: rangeItalic[i].endIndex + totalEmoji - (2*countRemoveItalicSign) - 1, length: 1))
                        countRemoveItalicSign += 1
                    } else {
                        finalText.addAttribute(.foregroundColor, value: UIColor.gray, range: NSRange(location: lastFirstRange + countEmojiBefore, length: 1))
                        finalText.addAttribute(.foregroundColor, value: UIColor.gray, range: NSRange(location: rangeItalic[i].endIndex + totalEmoji, length: 1))
                    }
                    lastFirstRange = 0
                }
            }
        }
        
        //Underline
        let textAfterItalic = finalText.string
        let rangeUnderline = getRangeOfWordWithSign(sentence: textAfterItalic, sign: underlineSign)
        if rangeUnderline.count > 0 {
            var lastFirstRange = 0
            var countRemoveUnderlineSign = 0
            var continueCheckingUnderline = false
            var totalEmoji = 0
            for i in 0..<rangeUnderline.count {
                if lastFirstRange == 0 {
                    if (rangeUnderline[i].startIndex == 0 || checkCharBeforeAfter(char: textAfterItalic.substring(from: rangeUnderline[i].startIndex - 1, to: rangeUnderline[i].startIndex - 1))) {
                        lastFirstRange = rangeUnderline[i].startIndex
                        continueCheckingUnderline = true
                    } else {
                        continueCheckingUnderline = false
                    }
                }
                if !continueCheckingUnderline {
                    continue
                }
                if (rangeUnderline[i].endIndex == textAfterItalic.count - 1 || checkCharBeforeAfter(char: textAfterItalic.substring(from: rangeUnderline[i].endIndex + 1, to: rangeUnderline[i].endIndex + 1))) {
                    let countEmojiBefore = finalText.string.substring(from: 0, to: lastFirstRange - (2*countRemoveUnderlineSign)).countEmojiCharacter()
                    let countEmoji = finalText.string.substring(from: lastFirstRange - (2*countRemoveUnderlineSign), to: rangeUnderline[i].endIndex - (2*countRemoveUnderlineSign)).countEmojiCharacter()
                    totalEmoji = countEmoji + countEmojiBefore
                    finalText.addAttribute(.underlineStyle, value: NSUnderlineStyle.thick.rawValue, range: NSRange(location: lastFirstRange - (2*countRemoveUnderlineSign) + countEmojiBefore + 1, length: (rangeUnderline[i].endIndex + countEmoji - 1) - lastFirstRange))
                    if !isEditing{
                        finalText.mutableString.replaceOccurrences(of: "\(underlineSign)", with: "", options: .literal, range: NSRange(location: lastFirstRange + countEmojiBefore - (2*countRemoveUnderlineSign), length: 1))
                        finalText.mutableString.replaceOccurrences(of: "\(underlineSign)", with: "", options: .literal, range: NSRange(location: rangeUnderline[i].endIndex + totalEmoji - (2*countRemoveUnderlineSign) - 1, length: 1))
                        countRemoveUnderlineSign += 1
                    } else {
                        finalText.addAttribute(.foregroundColor, value: UIColor.gray, range: NSRange(location: lastFirstRange + countEmojiBefore, length: 1))
                        finalText.addAttribute(.foregroundColor, value: UIColor.gray, range: NSRange(location: rangeUnderline[i].endIndex + totalEmoji, length: 1))
                    }
                    lastFirstRange = 0
                }
            }
        }
        
        //Strikethrough
        let textAfterUnderline = finalText.string
        let rangeStrikethrough = getRangeOfWordWithSign(sentence: textAfterUnderline, sign: strikethroughSign)
        if rangeStrikethrough.count > 0 {
            var lastFirstRange = 0
            var countRemoveStrikethroughSign = 0
            var continueCheckingStrikethrough = false
            var totalEmoji = 0
            for i in 0..<rangeStrikethrough.count {
                if lastFirstRange == 0 {
                    if (rangeStrikethrough[i].startIndex == 0 || checkCharBeforeAfter(char: textAfterUnderline.substring(from: rangeStrikethrough[i].startIndex - 1, to: rangeStrikethrough[i].startIndex - 1))) {
                        lastFirstRange = rangeStrikethrough[i].startIndex
                        continueCheckingStrikethrough = true
                    } else {
                        continueCheckingStrikethrough = false
                    }
                }
                if !continueCheckingStrikethrough {
                    continue
                }
                if (rangeStrikethrough[i].endIndex == textAfterUnderline.count - 1 || checkCharBeforeAfter(char: textAfterUnderline.substring(from: rangeStrikethrough[i].endIndex + 1, to: rangeStrikethrough[i].endIndex + 1))) {
                    let countEmojiBefore = finalText.string.substring(from: 0, to: lastFirstRange - (2*countRemoveStrikethroughSign)).countEmojiCharacter()
                    let countEmoji = finalText.string.substring(from: lastFirstRange - (2*countRemoveStrikethroughSign), to: rangeStrikethrough[i].endIndex - (2*countRemoveStrikethroughSign)).countEmojiCharacter()
                    totalEmoji = countEmoji + countEmojiBefore
                    finalText.addAttribute(.strikethroughStyle, value: 2, range: NSRange(location: lastFirstRange - (2*countRemoveStrikethroughSign) + countEmojiBefore + 1, length: (rangeStrikethrough[i].endIndex + countEmoji - 1) - lastFirstRange))
                    if !isEditing{
                        finalText.mutableString.replaceOccurrences(of: "\(strikethroughSign)", with: "", options: .literal, range: NSRange(location: lastFirstRange + countEmojiBefore - (2*countRemoveStrikethroughSign), length: 1))
                        finalText.mutableString.replaceOccurrences(of: "\(strikethroughSign)", with: "", options: .literal, range: NSRange(location: rangeStrikethrough[i].endIndex + totalEmoji - (2*countRemoveStrikethroughSign) - 1, length: 1))
                        countRemoveStrikethroughSign += 1
                    } else {
                        finalText.addAttribute(.foregroundColor, value: UIColor.gray, range: NSRange(location: lastFirstRange + countEmojiBefore, length: 1))
                        finalText.addAttribute(.foregroundColor, value: UIColor.gray, range: NSRange(location: rangeStrikethrough[i].endIndex + totalEmoji, length: 1))
                    }
                    lastFirstRange = 0
                }
            }
        }
        
        //Check Mention
        let finalTextAfterRichText = finalText.string
        if finalTextAfterRichText.contains("@") {
            if !group_id.isEmpty {
                let listMembers = Member.getAllMember(group_id: group_id)
                if listMembers.count > 0 {
                    for i in 0..<listMembers.count {
                        if isEditing {
                            if listMentionInTextField.count > 0 {
                                let name = (listMembers[i].firstName + " " + listMembers[i].lastName).trimmingCharacters(in: .whitespaces)
                                if listMentionInTextField.firstIndex(where: { ($0.firstName + " " + $0.lastName).trimmingCharacters(in: .whitespaces) == name }) != nil {
                                    let range = NSString(string: finalTextAfterRichText).range(of: "@\(name)", options: .caseInsensitive)
                                    finalText.addAttribute(.foregroundColor, value: UIColor.mentionColor, range: range)
                                }
                            }
                        } else {
                            let name = (listMembers[i].firstName + " " + listMembers[i].lastName).trimmingCharacters(in: .whitespaces)
                            let range = NSString(string: finalTextAfterRichText).range(of: listMembers[i].pin, options: .caseInsensitive)
                            if range.lowerBound != range.upperBound {
                                finalText.mutableString.replaceOccurrences(of: listMembers[i].pin, with: name, options: .literal, range: range)
                                finalText.addAttribute(.foregroundColor, value: UIColor.mentionColor, range: NSString(string: finalText.string).range(of: "@\(name)"))
                            }
                        }
                    }
                }
            } else {
                let listTextEnter = finalText.string.split(separator: "\n")
                for j in 0...listTextEnter.count - 1 {
                    let listText = listTextEnter[j].split(separator: " ")
                    let listMention = listText.filter({ $0.starts(with: "@")})
                    if listMention.count > 0 {
                        for i in 0..<listMention.count {
                            let f_pin = (String(listMention[i])).substring(from: 1, to: listMention[i].count)
                            let member = Member.getMember(f_pin: f_pin)
                            if member != nil {
                                let name = (member!.firstName + " " + member!.lastName).trimmingCharacters(in: .whitespaces)
                                finalText.mutableString.replaceOccurrences(of: f_pin, with: name, options: .literal, range: NSString(string: finalText.string).range(of: f_pin))
                            }
                        }
                    }
                }
            }
        }
        if isSearching {
            let range = NSString(string: finalText.string).range(of: textSearch, options: .caseInsensitive) // 2
            let highlightColor = UIColor.systemYellow // 3
            let highlightedAttributes: [NSAttributedString.Key: Any] = [NSAttributedString.Key.backgroundColor: highlightColor] // 4
            
            finalText.addAttributes(highlightedAttributes, range: range) // 5
        }
        
        return finalText
    }
    
    func checkCharBeforeAfter(char: String) -> Bool {
        return char == " " || char == "\n" || char == "*" || char == "_" || char == "^" || char == "~"
    }
    
    func checkStartWithLink() -> Bool {
        return self.starts(with: "https://") || self.starts(with: "http://") || self.starts(with: "www.")
        //|| self.starts(with: "*https://") || self.starts(with: "*http://") || self.starts(with: "*www.") || self.starts(with: "_https://") || self.starts(with: "_http://") || self.starts(with: "_www.") || self.starts(with: "^https://") || self.starts(with: "^http://") || self.starts(with: "^www.") || self.starts(with: "~https://") || self.starts(with: "~http://") || self.starts(with: "~www.")
    }
    
    func getRangeOfWordWithSign(sentence: String, sign: Character) -> [Range<Int>] {
        let asterisk: Character = sign
        var ranges = [Range<Int>]()
        
        var startIndex = sentence.startIndex
        
        while let startRange = sentence[startIndex...].firstIndex(of: asterisk),
              let endRange = sentence[startRange...].dropFirst().firstIndex(of: asterisk) {
            
            let range = startRange..<endRange
            let word = sentence[range].replacingOccurrences(of: "\(sign)", with: "")
            
            if !word.isEmpty {
                let lower = sentence.distance(from: sentence.startIndex, to: startRange)
                let upper = sentence.distance(from: sentence.startIndex, to: endRange)
                ranges.append(Range(uncheckedBounds: (lower: lower, upper: upper)))
            }
            
            startIndex = endRange
        }
        
        return ranges
    }
    
}

extension UIFont {
    var bold: UIFont {
        return with(traits: .traitBold)
    } // bold
    
    var italic: UIFont {
        return with(traits: .traitItalic)
    } // italic
    
    func with(traits: UIFontDescriptor.SymbolicTraits) -> UIFont {
        guard let descriptor = self.fontDescriptor.withSymbolicTraits(traits) else {
            return self
        } // guard
        
        return UIFont(descriptor: descriptor, size: 0)
    } // with(traits:)
}

extension UILabel {
    public func set(image: UIImage, with text: String, size: CGFloat, y: CGFloat) {
        let attachment = NSTextAttachment()
        attachment.image = image
        attachment.bounds = CGRect(x: 0, y: y, width: size, height: size)
        let attachmentStr = NSAttributedString(attachment: attachment)
        
        let mutableAttributedString = NSMutableAttributedString()
        mutableAttributedString.append(attachmentStr)
        
        let textString = NSAttributedString(string: text, attributes: [.font: self.font!])
        mutableAttributedString.append(textString)
        
        self.attributedText = mutableAttributedString
    }
    
    public func setAttributeText(image: UIImage, with textMutable: NSAttributedString, size: CGFloat, y: CGFloat) {
        let attachment = NSTextAttachment()
        attachment.image = image
        attachment.bounds = CGRect(x: 0, y: y, width: size, height: size)
        let attachmentStr = NSAttributedString(attachment: attachment)
        
        let mutableAttributedString = NSMutableAttributedString()
        mutableAttributedString.append(attachmentStr)
        
        mutableAttributedString.append(textMutable)
        
        self.attributedText = mutableAttributedString
    }
}

extension Bundle {

    public static func resourceBundle(for frameworkClass: AnyClass) -> Bundle {
        guard let moduleName = String(reflecting: frameworkClass).components(separatedBy: ".").first else {
            fatalError("Couldn't determine module name from class \(frameworkClass)")
        }
        
        let frameworkBundle = Bundle(for: frameworkClass)

        guard let resourceBundleURL = frameworkBundle.url(forResource: "NexilisLite", withExtension: "bundle"),
              let resourceBundle = Bundle(url: resourceBundleURL) else {
            print("\(moduleName).bundle not found in \(frameworkBundle)")
            return frameworkBundle
        }

        return resourceBundle
    }
    
}

//extension UIFont {
//
//    static func register(from url: URL) throws {
//        guard let fontDataProvider = CGDataProvider(url: url as CFURL) else {
//            throw fatalError("Could not create font data provider for \(url).")
//        }
//        let font = CGFont(fontDataProvider)
//        var error: Unmanaged<CFError>?
//        guard CTFontManagerRegisterGraphicsFont(font!, &error) else {
//            throw error!.takeUnretainedValue()
//        }
//    }
//
//}

extension UIButton {
    private func actionHandleBlock(action:(() -> Void)? = nil) {
        struct __ {
            static var action :(() -> Void)?
        }
        if action != nil {
            __.action = action
        } else {
            __.action?()
        }
    }
    
    @objc private func triggerActionHandleBlock() {
        self.actionHandleBlock()
    }
    
    public func actionHandle(controlEvents control :UIControl.Event, ForAction action:@escaping () -> Void) {
        self.actionHandleBlock(action: action)
        self.addTarget(self, action: #selector(self.triggerActionHandleBlock), for: control)
    }
}

extension UINavigationController {
    func replaceAllViewController(with viewController: UIViewController, animated: Bool) {
        pushViewController(viewController, animated: animated)
        viewControllers.removeSubrange(1...viewControllers.count - 2)
    }
    
    var rootViewController : UIViewController? {
        return viewControllers.first
    }
    
    func popViewController(animated: Bool, completion: @escaping () -> Void) {
        popViewController(animated: animated)

        if animated, let coordinator = transitionCoordinator {
            coordinator.animate(alongsideTransition: nil) { _ in
                completion()
            }
        } else {
            completion()
        }
    }
}

extension UIImageView {

    private static var taskKey = 0
    private static var urlKey = 0

    private var currentTask: URLSessionTask? {
        get { return objc_getAssociatedObject(self, &UIImageView.taskKey) as? URLSessionTask }
        set { objc_setAssociatedObject(self, &UIImageView.taskKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) }
    }

    private var currentURL: URL? {
        get { return objc_getAssociatedObject(self, &UIImageView.urlKey) as? URL }
        set { objc_setAssociatedObject(self, &UIImageView.urlKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) }
    }

    func loadImageAsync(with urlString: String?) {
        // cancel prior task, if any

        weak var oldTask = currentTask
        currentTask = nil
        oldTask?.cancel()

        // reset imageview's image

        self.image = nil

        // allow supplying of `nil` to remove old image and then return immediately

        guard let urlString = urlString else { return }

        // check cache

        if let cachedImage = ImageCache.shared.image(forKey: urlString) {
            self.image = cachedImage
            return
        }

        // download

        let url = URL(string: urlString)!
        currentURL = url
        let task = URLSession.shared.dataTask(with: url) { [weak self] data, response, error in
            self?.currentTask = nil

            //error handling

            if let error = error {
                // don't bother reporting cancelation errors

                if (error as NSError).domain == NSURLErrorDomain && (error as NSError).code == NSURLErrorCancelled {
                    return
                }

                print(error)
                return
            }

            guard let data = data, let downloadedImage = UIImage(data: data) else {
                print("unable to extract image")
                return
            }

            ImageCache.shared.save(image: downloadedImage, forKey: urlString)

            if url == self?.currentURL {
                DispatchQueue.main.async {
                    self?.image = downloadedImage
                }
            }
        }

        // save and start new task

        currentTask = task
        task.resume()
    }
    
    private func actionHandleBlock(action:(() -> Void)? = nil) {
        struct __ {
            static var action :(() -> Void)?
        }
        if action != nil {
            __.action = action
        } else {
            __.action?()
        }
    }
    
    @objc private func triggerActionHandleBlock() {
        self.actionHandleBlock()
    }
    
    func actionHandle(controlEvents control :UIControl.Event, ForAction action:@escaping () -> Void) {
        self.actionHandleBlock(action: action)
        self.isUserInteractionEnabled = true
        self.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.triggerActionHandleBlock)))
    }

}

extension UITextField {

    public enum PaddingSide {
        case left(CGFloat)
        case right(CGFloat)
        case both(CGFloat)
    }

    public func addPadding(_ padding: PaddingSide) {

        self.leftViewMode = .always
        self.layer.masksToBounds = true


        switch padding {

        case .left(let spacing):
            let paddingView = UIView(frame: CGRect(x: 0, y: 0, width: spacing, height: self.frame.height))
            self.leftView = paddingView
            self.rightViewMode = .always

        case .right(let spacing):
            let paddingView = UIView(frame: CGRect(x: 0, y: 0, width: spacing, height: self.frame.height))
            self.rightView = paddingView
            self.rightViewMode = .always

        case .both(let spacing):
            let paddingView = UIView(frame: CGRect(x: 0, y: 0, width: spacing, height: self.frame.height))
            // left
            self.leftView = paddingView
            self.leftViewMode = .always
            // right
            self.rightView = paddingView
            self.rightViewMode = .always
        }
    }
}

class ImageCache {
    private let cache = NSCache<NSString, UIImage>()
    private var observer: NSObjectProtocol!

    static let shared = ImageCache()

    private init() {
        // make sure to purge cache on memory pressure

        observer = NotificationCenter.default.addObserver(forName: UIApplication.didReceiveMemoryWarningNotification, object: nil, queue: nil) { [weak self] notification in
            self?.cache.removeAllObjects()
        }
    }

    deinit {
        NotificationCenter.default.removeObserver(observer as Any)
    }

    func image(forKey key: String) -> UIImage? {
        return cache.object(forKey: key as NSString)
    }

    func save(image: UIImage, forKey key: String) {
        cache.setObject(image, forKey: key as NSString)
    }
}

extension UISearchBar
{

    func setMagnifyingGlassColorTo(color: UIColor)
    {
        // Search Icon
        let textFieldInsideSearchBar = self.value(forKey: "searchField") as? UITextField
        let glassIconView = textFieldInsideSearchBar?.leftView as? UIImageView
        glassIconView?.image = glassIconView?.image?.withRenderingMode(.alwaysTemplate)
        glassIconView?.tintColor = color
    }

    func setClearButtonColorTo(color: UIColor)
    {
        // Clear Button
        let textFieldInsideSearchBar = self.value(forKey: "searchField") as? UITextField
        let crossIconView = textFieldInsideSearchBar?.value(forKey: "clearButton") as? UIButton
        crossIconView?.setImage(crossIconView?.currentImage?.withRenderingMode(.alwaysTemplate), for: .normal)
        crossIconView?.tintColor = color
    }

    func setPlaceholderTextColorTo(color: UIColor)
    {
        let textFieldInsideSearchBar = self.value(forKey: "searchField") as? UITextField
        textFieldInsideSearchBar?.textColor = color
        let textFieldInsideSearchBarLabel = textFieldInsideSearchBar!.value(forKey: "placeholderLabel") as? UILabel
        textFieldInsideSearchBarLabel?.textColor = color
    }
}

extension String {
    init(unicodeScalar: UnicodeScalar) {
        self.init(Character(unicodeScalar))
    }


    init?(unicodeCodepoint: Int) {
        if let unicodeScalar = UnicodeScalar(unicodeCodepoint) {
            self.init(unicodeScalar: unicodeScalar)
        } else {
            return nil
        }
    }


    static func +(lhs: String, rhs: Int) -> String {
        return lhs + String(unicodeCodepoint: rhs)!
    }


    static func +=(lhs: inout String, rhs: Int) {
        lhs = lhs + rhs
    }
}
