// 
// Copyright 2022 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

import UIKit
import MatrixSDK

/// Text attachment for pills display.
@available (iOS 15.0, *)
@objcMembers
class PillTextAttachment: NSTextAttachment {
    // MARK: - Properties
    /// Return `PillTextAttachmentData` contained in the text attachment.
    var data: PillTextAttachmentData? {
        get {
            guard let contents = contents else { return nil }
            return try? Self.serializationService.deserialize(contents)
        }
        set {
            guard let newValue = newValue else {
                contents = nil
                return
            }
            contents = try? Self.serializationService.serialize(newValue)
            updateBounds()
        }
    }
    private static let serializationService: SerializationServiceType = SerializationService()

    // MARK: - Init
    override init(data contentData: Data?, ofType uti: String?) {
        super.init(data: contentData, ofType: uti)

        updateBounds()
    }
    
    convenience init?(attachmentData: PillTextAttachmentData) {
        guard let encodedData = try? Self.serializationService.serialize(attachmentData) else { return nil }
        self.init(data: encodedData, ofType: PillsFormatter.pillUTType)
    }

    /// Create a Mention Pill text attachment for given room member.
    ///
    /// - Parameters:
    ///   - roomMember: the room member
    ///   - isHighlighted: whether this pill should be highlighted
    ///   - font: the text font
    convenience init?(withRoomMember roomMember: MXRoomMember,
                      isHighlighted: Bool,
                      font: UIFont) {
        let data = PillTextAttachmentData(pillType: .user(userId: roomMember.userId),
                                          items: [
                                            .avatar(url: roomMember.avatarUrl,
                                                    string: roomMember.displayname,
                                                    matrixId: roomMember.userId),
                                            .text(roomMember.displayname ?? roomMember.userId)
                                          ],
                                          isHighlighted: isHighlighted,
                                          alpha: 1.0,
                                          font: font)

        guard let encodedData = try? Self.serializationService.serialize(data) else { return nil }
        self.init(data: encodedData, ofType: PillsFormatter.pillUTType)
    }

    required init?(coder: NSCoder) {
        super.init(coder: coder)

        updateBounds()
    }
    
    /// Computes size required to display a pill for given display text.
    ///
    /// - Parameters:
    ///   - font: the text font
    /// - Returns: required size for pill
    func size(forFont font: UIFont) -> CGSize {
        guard let data else {
            MXLog.debug("[PillTextAttachment]: data are missing")
            return .zero
        }
        
        let sizes = PillAttachmentViewProvider.pillAttachmentViewSizes

        var width: CGFloat = 0

        var textContent = ""
        for item in data.items {
            switch item {
            case .text(let text):
                textContent += text
            case .avatar, .asset, .spaceAvatar:
                width += sizes.avatarSideLength
            }
        }
                
        // add texts
        if !textContent.isEmpty {
            let label = UILabel(frame: .zero)
            label.font = font
            label.text = textContent
            width += label.sizeThatFits(CGSize(width: CGFloat.greatestFiniteMagnitude,
                                               height: sizes.pillBackgroundHeight)).width
        }
        
        // add spacing
        width += CGFloat(max(0, data.items.count - 1)) * sizes.itemSpacing
        // add margins
        switch data.items.first {
        case .asset, .avatar:
            width += sizes.avatarLeading + sizes.horizontalMargin
        default:
            width += 2 * sizes.horizontalMargin
        }

        width = min(width, data.maxWidth)

        return CGSize(width: width,
                      height: sizes.pillHeight)
    }
}

// MARK: - Private
@available (iOS 15.0, *)
private extension PillTextAttachment {
        
    func updateBounds() {
        guard let data = data else { return }
        let pillSize = size(forFont: data.font)
        // Offset to align pill centerY with text centerY.
        let offset = data.font.descender + (data.font.lineHeight - pillSize.height) / 2.0
        self.bounds = CGRect(origin: CGPoint(x: 0.0, y: offset), size: pillSize)
    }
}
