//
//  Copyright (c) 2019 Open Whisper Systems. All rights reserved.
//

import Foundation

@objc
class OWSColorPickerAccessoryView: NeverClearView {
    override var intrinsicContentSize: CGSize {
        return CGSize(width: kSwatchSize, height: kSwatchSize)
    }

    override func sizeThatFits(_ size: CGSize) -> CGSize {
        return self.intrinsicContentSize
    }

    let kSwatchSize: CGFloat = 24

    @objc
    required init(color: UIColor) {
        super.init(frame: .zero)

        let circleView = CircleView()
        circleView.backgroundColor = color
        addSubview(circleView)
        circleView.autoSetDimensions(to: CGSize(width: kSwatchSize, height: kSwatchSize))
        circleView.autoPinEdgesToSuperviewEdges()
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

@objc (OWSCircleView)
class CircleView: UIView {
    override var bounds: CGRect {
        didSet {
            self.layer.cornerRadius = self.bounds.size.height / 2
        }
    }
}

protocol ColorViewDelegate: class {
    func colorViewWasTapped(_ colorView: ColorView)
}

class ColorView: UIView {
    public weak var delegate: ColorViewDelegate?
    public let conversationColor: OWSConversationColor

    private let swatchView: CircleView
    private let selectedRing: CircleView
    public var isSelected: Bool = false {
        didSet {
            self.selectedRing.isHidden = !isSelected
        }
    }

    required init(conversationColor: OWSConversationColor) {
        self.conversationColor = conversationColor
        self.swatchView = CircleView()
        self.selectedRing = CircleView()

        super.init(frame: .zero)
        self.addSubview(selectedRing)
        self.addSubview(swatchView)

        // Selected Ring
        let cellHeight: CGFloat = ScaleFromIPhone5(60)
        selectedRing.autoSetDimensions(to: CGSize(width: cellHeight, height: cellHeight))

        selectedRing.layer.borderColor = Theme.secondaryColor.cgColor
        selectedRing.layer.borderWidth = 2
        selectedRing.autoPinEdgesToSuperviewEdges()
        selectedRing.isHidden = true

        // Color Swatch
        swatchView.backgroundColor = conversationColor.primaryColor

        let swatchSize: CGFloat = ScaleFromIPhone5(46)
        swatchView.autoSetDimensions(to: CGSize(width: swatchSize, height: swatchSize))

        swatchView.autoCenterInSuperview()

        // gestures
        let tapGesture = UITapGestureRecognizer(target: self, action: #selector(didTap))
        self.addGestureRecognizer(tapGesture)
    }

    required init?(coder aDecoder: NSCoder) {
        notImplemented()
    }

    // MARK: Actions

    @objc
    func didTap() {
        delegate?.colorViewWasTapped(self)
    }
}

@objc
protocol ColorPickerDelegate: class {
    func colorPicker(_ colorPicker: ColorPicker, didPickConversationColor conversationColor: OWSConversationColor)
}

@objc(OWSColorPicker)
class ColorPicker: NSObject, ColorPickerViewDelegate {

    @objc
    public weak var delegate: ColorPickerDelegate?

    @objc
    let sheetViewController: SheetViewController

    @objc
    init(thread: TSThread) {
        let colorName = thread.conversationColorName
        let currentConversationColor = OWSConversationColor.conversationColorOrDefault(colorName: colorName)
        sheetViewController = SheetViewController()

        super.init()

        let colorPickerView = ColorPickerView(thread: thread)
        colorPickerView.delegate = self
        colorPickerView.select(conversationColor: currentConversationColor)

        sheetViewController.contentView.addSubview(colorPickerView)
        colorPickerView.autoPinEdgesToSuperviewEdges()
    }

    // MARK: ColorPickerViewDelegate

    func colorPickerView(_ colorPickerView: ColorPickerView, didPickConversationColor conversationColor: OWSConversationColor) {
        self.delegate?.colorPicker(self, didPickConversationColor: conversationColor)
    }
}

protocol ColorPickerViewDelegate: class {
    func colorPickerView(_ colorPickerView: ColorPickerView, didPickConversationColor conversationColor: OWSConversationColor)
}

class ColorPickerView: UIView, ColorViewDelegate {

    private let colorViews: [ColorView]
    let conversationStyle: ConversationStyle
    var outgoingMessageView = OWSMessageBubbleView(forAutoLayout: ())
    var incomingMessageView = OWSMessageBubbleView(forAutoLayout: ())
    weak var delegate: ColorPickerViewDelegate?

    // This is mostly a developer convenience - OWSMessageCell asserts at some point
    // that the available method width is greater than 0.
    // We ultimately use the width of the picker view which will be larger.
    let kMinimumConversationWidth: CGFloat = 300
    override var bounds: CGRect {
        didSet {
            updateMockConversationView()
        }
    }

    let mockConversationView: UIView = UIView()

    init(thread: TSThread) {
        let allConversationColors = OWSConversationColor.conversationColorNames.map { OWSConversationColor.conversationColorOrDefault(colorName: $0) }

        self.colorViews = allConversationColors.map { ColorView(conversationColor: $0) }

        self.conversationStyle = ConversationStyle(thread: thread)

        super.init(frame: .zero)

        colorViews.forEach { $0.delegate = self }

        let headerView = self.buildHeaderView()
        mockConversationView.layoutMargins = UIEdgeInsets(top: 16, left: 16, bottom: 16, right: 16)
        mockConversationView.backgroundColor = Theme.backgroundColor
        self.updateMockConversationView()

        let paletteView = self.buildPaletteView(colorViews: colorViews)

        let rowsStackView = UIStackView(arrangedSubviews: [headerView, mockConversationView, paletteView])
        rowsStackView.axis = .vertical
        addSubview(rowsStackView)
        rowsStackView.autoPinEdgesToSuperviewEdges()
    }

    required init?(coder aDecoder: NSCoder) {
        notImplemented()
    }

    // MARK: ColorViewDelegate

    func colorViewWasTapped(_ colorView: ColorView) {
        self.select(conversationColor: colorView.conversationColor)
        self.delegate?.colorPickerView(self, didPickConversationColor: colorView.conversationColor)
        updateMockConversationView()
    }

    fileprivate func select(conversationColor selectedConversationColor: OWSConversationColor) {
        colorViews.forEach { colorView in
            colorView.isSelected = colorView.conversationColor == selectedConversationColor
        }
    }

    // MARK: View Building

    private func buildHeaderView() -> UIView {
        let headerView = UIView()
        headerView.layoutMargins = UIEdgeInsets(top: 15, left: 16, bottom: 15, right: 16)

        let titleLabel = UILabel()
        titleLabel.text = NSLocalizedString("COLOR_PICKER_SHEET_TITLE", comment: "Modal Sheet title when picking a conversation color.")
        titleLabel.textAlignment = .center
        titleLabel.font = UIFont.ows_dynamicTypeBody.ows_mediumWeight()
        titleLabel.textColor = Theme.primaryColor

        headerView.addSubview(titleLabel)
        titleLabel.ows_autoPinToSuperviewMargins()

        let bottomBorderView = UIView()
        bottomBorderView.backgroundColor = Theme.hairlineColor
        headerView.addSubview(bottomBorderView)
        bottomBorderView.autoPinEdgesToSuperviewEdges(with: .zero, excludingEdge: .top)
        bottomBorderView.autoSetDimension(.height, toSize: CGHairlineWidth())

        return headerView
    }

    private func updateMockConversationView() {
        conversationStyle.viewWidth = max(bounds.size.width, kMinimumConversationWidth)
        mockConversationView.subviews.forEach { $0.removeFromSuperview() }

        // outgoing
        outgoingMessageView = OWSMessageBubbleView(forAutoLayout: ())
        let outgoingItem = MockConversationViewItem()
        let outgoingText = NSLocalizedString("COLOR_PICKER_DEMO_MESSAGE_1", comment: "The first of two messages demonstrating the chosen conversation color, by rendering this message in an outgoing message bubble.")
        outgoingItem.interaction = MockOutgoingMessage(messageBody: outgoingText)
        outgoingItem.displayableBodyText = DisplayableText.displayableText(outgoingText)
        outgoingItem.interactionType = .outgoingMessage

        outgoingMessageView.viewItem = outgoingItem
        outgoingMessageView.cellMediaCache = NSCache()
        outgoingMessageView.conversationStyle = conversationStyle
        outgoingMessageView.configureViews()
        outgoingMessageView.loadContent()
        let outgoingCell = UIView()
        outgoingCell.addSubview(outgoingMessageView)
        outgoingMessageView.autoPinEdgesToSuperviewEdges(with: .zero, excludingEdge: .leading)
        let outgoingSize = outgoingMessageView.measureSize()
        outgoingMessageView.autoSetDimensions(to: outgoingSize)

        // incoming
        incomingMessageView = OWSMessageBubbleView(forAutoLayout: ())
        let incomingItem = MockConversationViewItem()
        let incomingText = NSLocalizedString("COLOR_PICKER_DEMO_MESSAGE_2", comment: "The second of two messages demonstrating the chosen conversation color, by rendering this message in an incoming message bubble.")
        incomingItem.interaction = MockIncomingMessage(messageBody: incomingText)
        incomingItem.displayableBodyText = DisplayableText.displayableText(incomingText)
        incomingItem.interactionType = .incomingMessage

        incomingMessageView.viewItem = incomingItem
        incomingMessageView.cellMediaCache = NSCache()
        incomingMessageView.conversationStyle = conversationStyle
        incomingMessageView.configureViews()
        incomingMessageView.loadContent()
        let incomingCell = UIView()
        incomingCell.addSubview(incomingMessageView)
        incomingMessageView.autoPinEdgesToSuperviewEdges(with: .zero, excludingEdge: .trailing)
        let incomingSize = incomingMessageView.measureSize()
        incomingMessageView.autoSetDimensions(to: incomingSize)

        let messagesStackView = UIStackView(arrangedSubviews: [outgoingCell, incomingCell])
        messagesStackView.axis = .vertical
        messagesStackView.spacing = 12

        mockConversationView.addSubview(messagesStackView)
        messagesStackView.autoPinEdgesToSuperviewMargins()
    }

    private func buildPaletteView(colorViews: [ColorView]) -> UIView {
        let paletteView = UIView()
        let paletteMargin = ScaleFromIPhone5(12)
        paletteView.layoutMargins = UIEdgeInsets(top: paletteMargin, left: paletteMargin, bottom: 0, right: paletteMargin)

        let kRowLength = 4
        let rows: [UIView] = colorViews.chunked(by: kRowLength).map { colorViewsInRow in
            let row = UIStackView(arrangedSubviews: colorViewsInRow)
            row.distribution = UIStackView.Distribution.equalSpacing
            return row
        }
        let rowsStackView = UIStackView(arrangedSubviews: rows)
        rowsStackView.axis = .vertical
        rowsStackView.spacing = ScaleFromIPhone5To7Plus(12, 30)

        paletteView.addSubview(rowsStackView)
        rowsStackView.ows_autoPinToSuperviewMargins()

        // no-op gesture to keep taps from dismissing SheetView
        paletteView.addGestureRecognizer(UITapGestureRecognizer(target: nil, action: nil))
        return paletteView
    }
}

// MARK: Mock Classes for rendering demo conversation

@objc
private class MockConversationViewItem: NSObject, ConversationViewItem {
    var userCanDeleteGroupMessage: Bool = false
    var isRSSFeed: Bool = false
    var interaction: TSInteraction = TSMessage()
    var interactionType: OWSInteractionType = OWSInteractionType.unknown
    var quotedReply: OWSQuotedReplyModel?
    var isGroupThread: Bool = false
    var hasBodyText: Bool = true
    var isQuotedReply: Bool = false
    var hasQuotedAttachment: Bool = false
    var hasQuotedText: Bool = false
    var hasCellHeader: Bool = false
    var isExpiringMessage: Bool = false
    var shouldShowDate: Bool = false
    var shouldShowSenderAvatar: Bool = false
    var senderName: NSAttributedString?
    var shouldHideFooter: Bool = false
    var isFirstInCluster: Bool = true
    var isLastInCluster: Bool = true
    var unreadIndicator: OWSUnreadIndicator?
    var lastAudioMessageView: OWSAudioMessageView?
    var audioDurationSeconds: CGFloat = 0
    var audioProgressSeconds: CGFloat = 0
    var messageCellType: OWSMessageCellType = .textOnlyMessage
    var displayableBodyText: DisplayableText?
    var attachmentStream: TSAttachmentStream?
    var attachmentPointer: TSAttachmentPointer?
    var mediaSize: CGSize  = .zero
    var displayableQuotedText: DisplayableText?
    var quotedAttachmentMimetype: String?
    var quotedRecipientId: String?
    var didCellMediaFailToLoad: Bool = false
    var contactShare: ContactShareViewModel?
    var systemMessageText: String?
    var authorConversationColorName: String?
    var hasBodyTextActionContent: Bool = false
    var hasMediaActionContent: Bool = false
    var mediaAlbumItems: [ConversationMediaAlbumItem]?
    var hasCachedLayoutState: Bool = false
    var linkPreview: OWSLinkPreview?
    var linkPreviewAttachment: TSAttachment?

    override init() {
        super.init()
    }

    func itemId() -> String {
        return interaction.uniqueId!
    }

    func dequeueCell(for collectionView: UICollectionView, indexPath: IndexPath) -> ConversationViewCell {
        owsFailDebug("unexpected invocation")
        return ConversationViewCell(forAutoLayout: ())
    }

    func replace(_ interaction: TSInteraction, transaction: YapDatabaseReadTransaction) {
        owsFailDebug("unexpected invocation")
        return
    }

    func clearCachedLayoutState() {
        owsFailDebug("unexpected invocation")
        return
    }

    func copyMediaAction() {
        owsFailDebug("unexpected invocation")
        return
    }

    func copyTextAction() {
        owsFailDebug("unexpected invocation")
        return
    }

    func shareMediaAction() {
        owsFailDebug("unexpected invocation")
        return
    }

    func shareTextAction() {
        owsFailDebug("unexpected invocation")
        return
    }

    func saveMediaAction() {
        owsFailDebug("unexpected invocation")
        return
    }

    func deleteAction() {
        owsFailDebug("unexpected invocation")
        return
    }

    func canCopyMedia() -> Bool {
        owsFailDebug("unexpected invocation")
        return false
    }

    func canSaveMedia() -> Bool {
        owsFailDebug("unexpected invocation")
        return false
    }

    func audioPlaybackState() -> AudioPlaybackState {
        owsFailDebug("unexpected invocation")
        return AudioPlaybackState.paused
    }

    func setAudioPlaybackState(_ state: AudioPlaybackState) {
        owsFailDebug("unexpected invocation")
        return
    }

    func setAudioProgress(_ progress: CGFloat, duration: CGFloat) {
        owsFailDebug("unexpected invocation")
        return
    }

    func cellSize() -> CGSize {
        owsFailDebug("unexpected invocation")
        return CGSize.zero
    }

    func vSpacing(withPreviousLayoutItem previousLayoutItem: ConversationViewLayoutItem) -> CGFloat {
        owsFailDebug("unexpected invocation")
        return 2
    }

    func firstValidAlbumAttachment() -> TSAttachmentStream? {
        owsFailDebug("unexpected invocation")
        return nil
    }

    func mediaAlbumHasFailedAttachment() -> Bool {
        owsFailDebug("unexpected invocation")
        return false
    }
}

private class MockIncomingMessage: TSIncomingMessage {
    init(messageBody: String) {
        super.init(incomingMessageWithTimestamp: NSDate.ows_millisecondTimeStamp(),
                   in: TSThread(),
                   authorId: "+fake-id",
                   sourceDeviceId: 1,
                   messageBody: messageBody,
                   attachmentIds: [],
                   expiresInSeconds: 0,
                   quotedMessage: nil,
                   contactShare: nil,
                   linkPreview: nil,
                   serverTimestamp: nil,
                   wasReceivedByUD: false)
    }

    required init(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    required init(dictionary dictionaryValue: [String: Any]!) throws {
        fatalError("init(dictionary:) has not been implemented")
    }

    override func save(with transaction: YapDatabaseReadWriteTransaction) {
        // no - op
        owsFailDebug("shouldn't save mock message")
    }
}

private class MockOutgoingMessage: TSOutgoingMessage {
    init(messageBody: String) {
        super.init(outgoingMessageWithTimestamp: NSDate.ows_millisecondTimeStamp(),
                   in: nil,
                   messageBody: messageBody,
                   attachmentIds: [],
                   expiresInSeconds: 0,
                   expireStartedAt: 0,
                   isVoiceMessage: false,
                   groupMetaMessage: .unspecified,
                   quotedMessage: nil,
                   contactShare: nil,
                   linkPreview: nil)
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    required init(dictionary dictionaryValue: [String: Any]!) throws {
        fatalError("init(dictionary:) has not been implemented")
    }

    override func save(with transaction: YapDatabaseReadWriteTransaction) {
        // no - op
        owsFailDebug("shouldn't save mock message")
    }

    class MockOutgoingMessageRecipientState: TSOutgoingMessageRecipientState {
        override var state: OWSOutgoingMessageRecipientState {
            return OWSOutgoingMessageRecipientState.sent
        }

        override var deliveryTimestamp: NSNumber? {
            return NSNumber(value: NSDate.ows_millisecondTimeStamp())
        }

        override var readTimestamp: NSNumber? {
            return NSNumber(value: NSDate.ows_millisecondTimeStamp())
        }
    }

    override func readRecipientIds() -> [String] {
        // makes message appear as read
        return ["fake-non-empty-id"]
    }

    override func recipientState(forRecipientId recipientId: String) -> TSOutgoingMessageRecipientState? {
        return MockOutgoingMessageRecipientState()
    }
}
