//

// TinyConstraints+Extras.swift

// WeThePeople

//

// Created by Joseph Falcone on 4/15/17.

// Copyright © 2017 Joseph Falcone. All rights reserved.

//

import Foundation

import TinyConstraints

public struct TextLayoutOptions : OptionSet

{

public let rawValue : Int

public init ( rawValue : Int ) { self . rawValue = rawValue }

public static let IgnoreAscender = TextLayoutOptions ( rawValue : 1 )

public static let IgnoreDescender = TextLayoutOptions ( rawValue : 2 )

}

public struct ViewEdge : OptionSet , Hashable

{

public let rawValue : Int

public init ( rawValue : Int ) { self . rawValue = rawValue }

public var hashValue : Int {

return self . rawValue

}

static let top = ViewEdge ( rawValue : 1 << 0 )

static let right = ViewEdge ( rawValue : 1 << 1 ) // try not to use right and left

static let bottom = ViewEdge ( rawValue : 1 << 2 )

static let left = ViewEdge ( rawValue : 1 << 3 ) // leading and trailing are better for text

static let leading = ViewEdge ( rawValue : 1 << 4 ) // left for western users

static let trailing = ViewEdge ( rawValue : 1 << 5 ) // right for western users

static func test ( ) { }

}

public extension Constrainable

{

@discardableResult

public func edges ( to view : Constrainable , excludingEdges : ViewEdge = [ ] , insets : EdgeInsets = . zero , priority : ConstraintPriority = . required , isActive : Bool = true ) -> Constraints {

var constraints = [ NSLayoutConstraint ] ( )

if ! excludingEdges. contains ( . top ) {

constraints. append ( topAnchor. constraint ( equalTo : view. topAnchor , constant : insets. top ) . with ( priority ) )

}

if ! excludingEdges. contains ( . leading ) || ! excludingEdges. contains ( . left ) {

constraints. append ( leadingAnchor. constraint ( equalTo : view. leadingAnchor , constant : insets. left ) . with ( priority ) )

}

if ! excludingEdges. contains ( . bottom ) {

constraints. append ( bottomAnchor. constraint ( equalTo : view. bottomAnchor , constant : insets. bottom ) . with ( priority ) )

}

if ! excludingEdges. contains ( . trailing ) || ! excludingEdges. contains ( . right ) {

constraints. append ( trailingAnchor. constraint ( equalTo : view. trailingAnchor , constant : insets. right ) . with ( priority ) )

}

if isActive {

Constraint. activate ( constraints )

}

return constraints

}

// These autolayout extensions are designed for classes that display text. This returns the display font if the class type is valid.

// UILabel

// UITextField

private func getDisplayFont ( ) -> UIFont ?

{

if let v = self as ? UILabel { return v. font }

if let v = self as ? UITextField { return v. font }

//if let v = self as? UITextView {return v.font} UITextView is weird...the top align represents where the text gets cut off...

return nil

}

// NOTE: The font must be set before this returns anything of value

// NOTE: All values are positive since they are insets, even though descender is normally negative

private func typographyInsets ( ) -> UIEdgeInsets

{

guard let font = self . getDisplayFont ( ) else {

return UIEdgeInsets. zero

}

return UIEdgeInsets ( top : ( font. ascender - font. capHeight ) , left : 0 , bottom : abs ( font. descender ) , right : 0 )

}

private func getOffsetModifier ( to : Constrainable , edge : ViewEdge , toEdge : ViewEdge , textOptions : TextLayoutOptions = [ ] ) -> CGFloat

{

// Tired of ascenders/descenders being ignored.

// Let's make it easier to space these things out.

var typographyOffset : CGFloat = 0

let myTI = self . typographyInsets ( )

let toTI = to. typographyInsets ( )

if edge == toEdge {

// top to top, bottom to bottom

if edge == . top {

typographyOffset = toTI. top - myTI. top

}

if edge == . bottom {

typographyOffset = myTI. bottom - toTI. bottom

}

}

if edge == . top && toEdge == . bottom {

typographyOffset = - ( myTI. top + toTI. bottom )

}

if edge == . bottom && toEdge == . top {

typographyOffset = myTI. bottom + toTI. top

}

return typographyOffset ;

}

@discardableResult

public func topToBottom ( of view : Constrainable , offset : CGFloat = 0 , relation : ConstraintRelation = . equal , priority : ConstraintPriority = . required , textOptions : TextLayoutOptions = [ ] , isActive : Bool = true ) -> Constraint {

return top ( to : view , view. bottomAnchor , offset : offset , relation : relation , priority : priority , textOptions : textOptions , isActive : isActive )

}

@discardableResult

public func top ( to view : Constrainable , _ anchor : NSLayoutYAxisAnchor? = nil , offset : CGFloat = 0 , relation : ConstraintRelation = . equal , priority : ConstraintPriority = . required , textOptions : TextLayoutOptions = [ ] , isActive : Bool = true ) -> Constraint {

let toEdge = ( anchor != nil ) ? ViewEdge. bottom : ViewEdge. top

let offsetMod = self . getOffsetModifier ( to : view , edge : ViewEdge. top , toEdge : toEdge , textOptions : textOptions )

switch relation {

case . equal : return topAnchor. constraint ( equalTo : anchor ?? view. topAnchor , constant : offset + offsetMod ) . with ( priority ) . set ( active : isActive )

case . equalOrLess : return topAnchor. constraint ( lessThanOrEqualTo : anchor ?? view. topAnchor , constant : offset + offsetMod ) . with ( priority ) . set ( active : isActive )

case . equalOrGreater : return topAnchor. constraint ( greaterThanOrEqualTo : anchor ?? view. topAnchor , constant : offset + offsetMod ) . with ( priority ) . set ( active : isActive )

}

}

@discardableResult

public func bottomToTop ( of view : Constrainable , offset : CGFloat = 0 , relation : ConstraintRelation = . equal , priority : ConstraintPriority = . required , textOptions : TextLayoutOptions = [ ] , isActive : Bool = true ) -> Constraint {

return bottom ( to : view , view. topAnchor , offset : offset , relation : relation , priority : priority , textOptions : textOptions , isActive : isActive )

}

@discardableResult

public func bottom ( to view : Constrainable , _ anchor : NSLayoutYAxisAnchor? = nil , offset : CGFloat = 0 , relation : ConstraintRelation = . equal , priority : ConstraintPriority = . required , textOptions : TextLayoutOptions = [ ] , isActive : Bool = true ) -> Constraint {

let toEdge = ( anchor != nil ) ? ViewEdge. top : ViewEdge. bottom

let offsetMod = self . getOffsetModifier ( to : view , edge : ViewEdge. top , toEdge : toEdge , textOptions : textOptions )

switch relation {

case . equal : return bottomAnchor. constraint ( equalTo : anchor ?? view. bottomAnchor , constant : offset + offsetMod ) . with ( priority ) . set ( active : isActive )

case . equalOrLess : return bottomAnchor. constraint ( lessThanOrEqualTo : anchor ?? view. bottomAnchor , constant : offset + offsetMod ) . with ( priority ) . set ( active : isActive )

case . equalOrGreater : return bottomAnchor. constraint ( greaterThanOrEqualTo : anchor ?? view. bottomAnchor , constant : offset + offsetMod ) . with ( priority ) . set ( active : isActive )

}

}

@discardableResult

public func centerY ( to view : Constrainable , _ anchor : NSLayoutYAxisAnchor? = nil , offset : CGFloat = 0 , priority : ConstraintPriority = . required , textOptions : TextLayoutOptions = [ ] , isActive : Bool = true ) -> Constraint {

// Calculate a special offset mod

let myTI = self . typographyInsets ( )

let toTI = view. typographyInsets ( )

let offsetMod = ( ( toTI. top + toTI. bottom ) - ( myTI. top + myTI. bottom ) ) / 2

let constraint = centerYAnchor. constraint ( equalTo : anchor ?? view. centerYAnchor , constant : offset + offsetMod ) . with ( priority )

constraint. isActive = isActive

return constraint

}