// MIT License
//
// Copyright (c) 2017-2019 MessageKit
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

import Foundation
import MapKit
import UIKit

// MARK: - MessagesDisplayDelegate

/// A protocol used by the `MessagesViewController` to customize the appearance of a `MessageContentCell`.
public protocol MessagesDisplayDelegate: AnyObject {
  // MARK: - All Messages

  /// Specifies the `MessageStyle` to be used for a `MessageContainerView`.
  ///
  /// - Parameters:
  ///   - message: The `MessageType` that will be displayed by this cell.
  ///   - indexPath: The `IndexPath` of the cell.
  ///   - messagesCollectionView: The `MessagesCollectionView` in which this cell will be displayed.
  ///
  /// - Note:
  ///   The default value returned by this method is `MessageStyle.bubble`.
  func messageStyle(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView)
    -> MessageStyle

  /// Specifies the background color of the `MessageContainerView`.
  ///
  /// - Parameters:
  ///   - message: The `MessageType` that will be displayed by this cell.
  ///   - indexPath: The `IndexPath` of the cell.
  ///   - messagesCollectionView: The `MessagesCollectionView` in which this cell will be displayed.
  ///
  /// - Note:
  ///   The default value is `UIColor.clear` for emoji messages.
  ///   For all other `MessageKind` cases, the color depends on the `SenderType`.
  ///
  ///   Current sender: Green
  ///
  ///   All other senders: Gray
  func backgroundColor(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView)
    -> UIColor

  /// The section header to use for a given `IndexPath`.
  ///
  /// - Parameters:
  ///   - message: The `MessageType` that will be displayed for this header.
  ///   - indexPath: The `IndexPath` of the header.
  ///   - messagesCollectionView: The `MessagesCollectionView` in which this header will be displayed.
  func messageHeaderView(for indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> MessageReusableView

  /// The section footer to use for a given `IndexPath`.
  ///
  /// - Parameters:
  ///   - indexPath: The `IndexPath` of the footer.
  ///   - messagesCollectionView: The `MessagesCollectionView` in which this footer will be displayed.
  func messageFooterView(for indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> MessageReusableView

  /// Used to configure the `AvatarView`‘s image in a `MessageContentCell` class.
  ///
  /// - Parameters:
  ///   - avatarView: The `AvatarView` of the cell.
  ///   - message: The `MessageType` that will be displayed by this cell.
  ///   - indexPath: The `IndexPath` of the cell.
  ///   - messagesCollectionView: The `MessagesCollectionView` in which this cell will be displayed.
  ///
  /// - Note:
  ///   The default image configured by this method is `?`.
  func configureAvatarView(
    _ avatarView: AvatarView,
    for message: MessageType,
    at indexPath: IndexPath,
    in messagesCollectionView: MessagesCollectionView)

  /// Used to configure the `AccessoryView` in a `MessageContentCell` class.
  ///
  /// - Parameters:
  ///   - accessoryView: The `AccessoryView` of the cell.
  ///   - message: The `MessageType` that will be displayed by this cell.
  ///   - indexPath: The `IndexPath` of the cell.
  ///   - messagesCollectionView: The `MessagesCollectionView` in which this cell will be displayed.
  ///
  /// - Note:
  ///   The default image configured by this method is `?`.
  func configureAccessoryView(
    _ accessoryView: UIView,
    for message: MessageType,
    at indexPath: IndexPath,
    in messagesCollectionView: MessagesCollectionView)

  // MARK: - Text Messages

  /// Specifies the color of the text for a `TextMessageCell`.
  ///
  /// - Parameters:
  ///   - message: A `MessageType` with a `MessageKind` case of `.text` to which the color will apply.
  ///   - indexPath: The `IndexPath` of the cell.
  ///   - messagesCollectionView: The `MessagesCollectionView` in which this cell will be displayed.
  ///
  /// - Note:
  ///   The default value returned by this method is determined by the messages `SenderType`.
  ///
  ///   Current sender: UIColor.white
  ///
  ///   All other senders: UIColor.darkText
  func textColor(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView)
    -> UIColor

  /// Specifies the `DetectorType`s to check for the `MessageType`'s text against.
  ///
  /// - Parameters:
  ///   - message: A `MessageType` with a `MessageKind` case of `.text` or `.attributedText` to which the detectors will apply.
  ///   - indexPath: The `IndexPath` of the cell.
  ///   - messagesCollectionView: The `MessagesCollectionView` in which this cell will be displayed.
  ///
  /// - Note:
  ///   This method returns an empty array by default.
  func enabledDetectors(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView)
    -> [DetectorType]

  /// Specifies the attributes for a given `DetectorType`
  ///
  /// - Parameters:
  ///   - detector: The `DetectorType` for the applied attributes.
  ///   - message: A `MessageType` with a `MessageKind` case of `.text` or `.attributedText` to which the detectors will apply.
  ///   - indexPath: The `IndexPath` of the cell.
  func detectorAttributes(for detector: DetectorType, and message: MessageType, at indexPath: IndexPath)
    -> [NSAttributedString.Key: Any]

  // MARK: - Location Messages

  /// Used to configure a `LocationMessageSnapshotOptions` instance to customize the map image on the given location message.
  ///
  /// - Parameters:
  ///   - message: A `MessageType` with a `MessageKind` case of `.location`.
  ///   - indexPath: The `IndexPath` of the cell.
  ///   - messagesCollectionView: The `MessagesCollectionView` requesting the information.
  /// - Returns: The LocationMessageSnapshotOptions instance with the options to customize map style.
  func snapshotOptionsForLocation(
    message: MessageType,
    at indexPath: IndexPath,
    in messagesCollectionView: MessagesCollectionView) -> LocationMessageSnapshotOptions

  /// Used to configure the annotation view of the map image on the given location message.
  ///
  /// - Parameters:
  ///   - message: A `MessageType` with a `MessageKind` case of `.location`.
  ///   - indexPath: The `IndexPath` of the cell.
  ///   - messagesCollectionView: The `MessagesCollectionView` requesting the information.
  /// - Returns: The `MKAnnotationView` to use as the annotation view.
  func annotationViewForLocation(
    message: MessageType,
    at indexPath: IndexPath,
    in messageCollectionView: MessagesCollectionView) -> MKAnnotationView?

  /// Ask the delegate for a custom animation block to run when whe map screenshot is ready to be displaied in the given location message.
  /// The animation block is called with the `UIImageView` to be animated.
  ///
  /// - Parameters:
  ///   - message: A `MessageType` with a `MessageKind` case of `.location`.
  ///   - indexPath: The `IndexPath` of the cell.
  ///   - messagesCollectionView: The `MessagesCollectionView` requesting the information.
  /// - Returns: The animation block to use to apply the location image.
  func animationBlockForLocation(
    message: MessageType,
    at indexPath: IndexPath,
    in messagesCollectionView: MessagesCollectionView) -> ((UIImageView) -> Void)?

  // MARK: - Media Messages

  /// Used to configure the `UIImageView` of a `MediaMessageCell`.
  ///
  /// - Parameters:
  ///   - imageView: The `UIImageView` of the cell.
  ///   - message: The `MessageType` that will be displayed by this cell.
  ///   - indexPath: The `IndexPath` of the cell.
  ///   - messagesCollectionView: The `MessagesCollectionView` in which this cell will be displayed.
  func configureMediaMessageImageView(
    _ imageView: UIImageView,
    for message: MessageType,
    at indexPath: IndexPath,
    in messagesCollectionView: MessagesCollectionView)

  // MARK: - Audio Message

  /// Used to configure the audio cell UI:
  ///     1. play button selected state;
  ///     2. progressView progress;
  ///     3. durationLabel text;
  ///
  /// - Parameters:
  ///   - cell: The `AudioMessageCell` that needs to be configure.
  ///   - message: The `MessageType` that configures the cell.
  ///
  /// - Note:
  ///   This protocol method is called by MessageKit every time an audio cell needs to be configure
  func configureAudioCell(_ cell: AudioMessageCell, message: MessageType)

  /// Specifies the tint color of play button and progress bar for an `AudioMessageCell`.
  ///
  /// - Parameters:
  ///   - message: A `MessageType` with a `MessageKind` case of `.audio` to which the color will apply.
  ///   - indexPath: The `IndexPath` of the cell.
  ///   - messagesCollectionView: The `MessagesCollectionView` in which this cell will be displayed.
  ///
  /// - Note:
  ///   The default value returned by this method is UIColor.sendButtonBlue
  func audioTintColor(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView)
    -> UIColor

  /// Used to format the audio sound duration in a readable string
  ///
  /// - Parameters:
  ///   - duration: The audio sound duration is seconds.
  ///   - audioCell: The `AudioMessageCell` that ask for formated duration.
  ///   - messagesCollectionView: The `MessagesCollectionView` in which this cell will be displayed.
  ///
  /// - Note:
  ///   The default value is computed like fallow:
  ///     1. return the time as 0:ss if duration is up to 59 seconds                         (e.g. 0:03     means 0 minutes and 3 seconds)
  ///     2. return the time as m:ss if duration is greater than 59 and lower than 3600      (e.g. 12:23    means 12 minutes and 23 seconds)
  ///     3. return the time as h:mm:ss for anything longer that 3600 seconds                (e.g. 1:19:08  means 1 hour 19 minutes and 8 seconds)
  func audioProgressTextFormat(
    _ duration: Float,
    for audioCell: AudioMessageCell,
    in messageCollectionView: MessagesCollectionView) -> String

  /// Used to configure the `UIImageView` of a `LinkPreviewMessageCell`.
  /// - Parameters:
  ///   - imageView: The `UIImageView` of the cell.
  ///   - message: The `MessageType` that will be displayed by this cell.
  ///   - indexPath: The `IndexPath` of the cell.
  ///   - messagesCollectionView: The `MessagesCollectionView` in which this cell will be displayed.
  func configureLinkPreviewImageView(
    _ imageView: UIImageView,
    for message: MessageType,
    at indexPath: IndexPath,
    in messagesCollectionView: MessagesCollectionView)
}

extension MessagesDisplayDelegate {
  // MARK: - All Messages Defaults

  public func messageStyle(for _: MessageType, at _: IndexPath, in _: MessagesCollectionView) -> MessageStyle {
    .bubble
  }

  public func backgroundColor(
    for message: MessageType,
    at _: IndexPath,
    in messagesCollectionView: MessagesCollectionView)
    -> UIColor
  {
    switch message.kind {
    case .emoji:
      return .clear
    default:
      guard let dataSource = messagesCollectionView.messagesDataSource else {
        return .white
      }
      return dataSource.isFromCurrentSender(message: message) ? .outgoingMessageBackground : .incomingMessageBackground
    }
  }

  public func messageHeaderView(
    for indexPath: IndexPath,
    in messagesCollectionView: MessagesCollectionView)
    -> MessageReusableView
  {
    messagesCollectionView.dequeueReusableHeaderView(MessageReusableView.self, for: indexPath)
  }

  public func messageFooterView(
    for indexPath: IndexPath,
    in messagesCollectionView: MessagesCollectionView)
    -> MessageReusableView
  {
    messagesCollectionView.dequeueReusableFooterView(MessageReusableView.self, for: indexPath)
  }

  public func configureAvatarView(_ avatarView: AvatarView, for _: MessageType, at _: IndexPath, in _: MessagesCollectionView) {
    avatarView.initials = "?"
  }

  public func configureAccessoryView(_: UIView, for _: MessageType, at _: IndexPath, in _: MessagesCollectionView) { }

  // MARK: - Text Messages Defaults

  public func textColor(
    for message: MessageType,
    at _: IndexPath,
    in messagesCollectionView: MessagesCollectionView)
    -> UIColor
  {
    guard let dataSource = messagesCollectionView.messagesDataSource else {
      fatalError(MessageKitError.nilMessagesDataSource)
    }
    return dataSource.isFromCurrentSender(message: message) ? .outgoingMessageLabel : .incomingMessageLabel
  }

  public func enabledDetectors(for _: MessageType, at _: IndexPath, in _: MessagesCollectionView) -> [DetectorType] {
    []
  }

  public func detectorAttributes(for _: DetectorType, and _: MessageType, at _: IndexPath) -> [NSAttributedString.Key: Any] {
    MessageLabel.defaultAttributes
  }

  // MARK: - Location Messages Defaults

  public func snapshotOptionsForLocation(
    message _: MessageType,
    at _: IndexPath,
    in _: MessagesCollectionView)
    -> LocationMessageSnapshotOptions
  {
    LocationMessageSnapshotOptions()
  }

  public func annotationViewForLocation(
    message _: MessageType,
    at _: IndexPath,
    in _: MessagesCollectionView)
    -> MKAnnotationView?
  {
    MKPinAnnotationView(annotation: nil, reuseIdentifier: nil)
  }

  public func animationBlockForLocation(
    message _: MessageType,
    at _: IndexPath,
    in _: MessagesCollectionView) -> ((UIImageView) -> Void)?
  {
    nil
  }

  // MARK: - Media Message Defaults

  public func configureMediaMessageImageView(
    _: UIImageView,
    for _: MessageType,
    at _: IndexPath,
    in _: MessagesCollectionView) { }

  // MARK: - Audio Message Defaults

  public func configureAudioCell(_: AudioMessageCell, message _: MessageType) { }

  public func audioTintColor(
    for message: MessageType,
    at _: IndexPath,
    in messagesCollectionView: MessagesCollectionView)
    -> UIColor
  {
    guard let dataSource = messagesCollectionView.messagesDataSource else {
      fatalError(MessageKitError.nilMessagesDataSource)
    }
    return dataSource.isFromCurrentSender(message: message) ? .outgoingAudioMessageTint : .incomingAudioMessageTint
  }

  public func audioProgressTextFormat(_ duration: Float, for _: AudioMessageCell, in _: MessagesCollectionView) -> String {
    var returnValue = "0:00"
    // print the time as 0:ss if duration is up to 59 seconds
    // print the time as m:ss if duration is up to 59:59 seconds
    // print the time as h:mm:ss for anything longer
    if duration < 60 {
      returnValue = String(format: "0:%.02d", Int(duration.rounded(.up)))
    } else if duration < 3600 {
      returnValue = String(format: "%.02d:%.02d", Int(duration / 60), Int(duration) % 60)
    } else if duration.isFinite {
      let hours = Int(duration / 3600)
      let remainingMinutesInSeconds = Int(duration) - hours * 3600
      returnValue = String(
        format: "%.02d:%.02d:%.02d",
        hours,
        Int(remainingMinutesInSeconds / 60),
        Int(remainingMinutesInSeconds) % 60)
    }
    return returnValue
  }

  // MARK: - LinkPreview Message Defaults

  public func configureLinkPreviewImageView(
    _: UIImageView,
    for _: MessageType,
    at _: IndexPath,
    in _: MessagesCollectionView) { }
}
