// 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 UIKit

// MARK: - AvatarView

open class AvatarView: UIImageView {
  // MARK: Lifecycle

  // MARK: - Initializers
  public override init(frame: CGRect) {
    super.init(frame: frame)
    prepareView()
  }

  required public init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
    prepareView()
  }

  convenience public init() {
    self.init(frame: .zero)
    prepareView()
  }

  // MARK: Open

  open var fontMinimumScaleFactor: CGFloat = 0.5

  open var adjustsFontSizeToFitWidth = true

  // MARK: - Properties

  open var initials: String? {
    didSet {
      setImageFrom(initials: initials)
    }
  }

  open var placeholderFont = UIFont.preferredFont(forTextStyle: .caption1) {
    didSet {
      setImageFrom(initials: initials)
    }
  }

  open var placeholderTextColor: UIColor = .white {
    didSet {
      setImageFrom(initials: initials)
    }
  }

  // MARK: - Overridden Properties
  open override var frame: CGRect {
    didSet {
      setCorner(radius: self.radius)
    }
  }

  open override var bounds: CGRect {
    didSet {
      setCorner(radius: self.radius)
    }
  }

  // MARK: - Open setters

  open func set(avatar: Avatar) {
    if let image = avatar.image {
      self.image = image
    } else {
      initials = avatar.initials
    }
  }

  open func setCorner(radius: CGFloat?) {
    guard let radius = radius else {
      // if corner radius not set default to Circle
      let cornerRadius = min(frame.width, frame.height)
      layer.cornerRadius = cornerRadius / 2
      return
    }
    self.radius = radius
    layer.cornerRadius = radius
  }

  // MARK: Internal

  // MARK: - Internal methods

  internal func prepareView() {
    backgroundColor = .avatarViewBackground
    contentMode = .scaleAspectFill
    layer.masksToBounds = true
    clipsToBounds = true
    setCorner(radius: nil)
  }

  // MARK: Private

  private var radius: CGFloat?

  private var minimumFontSize: CGFloat {
    placeholderFont.pointSize * fontMinimumScaleFactor
  }

  private func setImageFrom(initials: String?) {
    guard let initials = initials else { return }
    image = getImageFrom(initials: initials)
  }

  private func getImageFrom(initials: String) -> UIImage {
    let width = frame.width
    let height = frame.height
    if width == 0 || height == 0 { return UIImage() }
    var font = placeholderFont

    UIGraphicsBeginImageContextWithOptions(CGSize(width: width, height: height), false, UIScreen.main.scale)
    defer { UIGraphicsEndImageContext() }
    let context = UIGraphicsGetCurrentContext()!

    //// Text Drawing
    let textRect = calculateTextRect(outerViewWidth: width, outerViewHeight: height)
    let initialsText = NSAttributedString(string: initials, attributes: [.font: font])
    if
      adjustsFontSizeToFitWidth,
      initialsText.width(considering: textRect.height) > textRect.width
    {
      let newFontSize = calculateFontSize(text: initials, font: font, width: textRect.width, height: textRect.height)
      font = placeholderFont.withSize(newFontSize)
    }

    let textStyle = NSMutableParagraphStyle()
    textStyle.alignment = .center
    let textFontAttributes: [NSAttributedString.Key: Any] = [
      NSAttributedString.Key.font: font,
      NSAttributedString.Key.foregroundColor: placeholderTextColor,
      NSAttributedString.Key.paragraphStyle: textStyle,
    ]

    let textTextHeight: CGFloat = initials.boundingRect(
      with: CGSize(width: textRect.width, height: CGFloat.infinity),
      options: .usesLineFragmentOrigin,
      attributes: textFontAttributes,
      context: nil).height
    context.saveGState()
    context.clip(to: textRect)
    initials.draw(
      in: CGRect(textRect.minX, textRect.minY + (textRect.height - textTextHeight) / 2, textRect.width, textTextHeight),
      withAttributes: textFontAttributes)
    context.restoreGState()
    guard let renderedImage = UIGraphicsGetImageFromCurrentImageContext()
    else { assertionFailure("Could not create image from context"); return UIImage() }
    return renderedImage
  }

  /// Recursively find the biggest size to fit the text with a given width and height
  private func calculateFontSize(text: String, font: UIFont, width: CGFloat, height: CGFloat) -> CGFloat {
    let attributedText = NSAttributedString(string: text, attributes: [.font: font])
    if attributedText.width(considering: height) > width {
      let newFont = font.withSize(font.pointSize - 1)
      if newFont.pointSize > minimumFontSize {
        return font.pointSize
      } else {
        return calculateFontSize(text: text, font: newFont, width: width, height: height)
      }
    }
    return font.pointSize
  }

  /// Calculates the inner circle's width.
  /// - Note: Assumes corner radius cannot be more than width/2 (this creates circle).
  private func calculateTextRect(outerViewWidth: CGFloat, outerViewHeight: CGFloat) -> CGRect {
    guard outerViewWidth > 0 else {
      return CGRect.zero
    }
    let shortEdge = min(outerViewHeight, outerViewWidth)
    // Converts degree to radian degree and calculate the
    // Assumes, it is a perfect circle based on the shorter part of ellipsoid
    // calculate a rectangle
    let w = shortEdge * sin(CGFloat(45).degreesToRadians) * 2
    let h = shortEdge * cos(CGFloat(45).degreesToRadians) * 2
    let startX = (outerViewWidth - w) / 2
    let startY = (outerViewHeight - h) / 2
    // In case the font exactly fits to the region, put 2 pixel both left and right
    return CGRect(startX + 2, startY, w - 4, h)
  }
}

extension FloatingPoint {
  // MARK: Fileprivate

  fileprivate var degreesToRadians: Self { self * .pi / 180 }

  // MARK: Private

  private var radiansToDegrees: Self { self * 180 / .pi }
}
