//
//  QLView+QuickLayout.swift
//  QuickLayout
//
//  Created by Daniel Huri on 11/19/17.
//

import Foundation
#if os(OSX)
import AppKit
#else
import UIKit
#endif

public extension QLView {
    
    /**
     Set constant value of an edge.
     Should be used with *width* or *height*
     - parameter edge: Edge type.
     - parameter value: Edge size.
     - parameter relation: Relation to the given constant value (default is *.equal*).
     - parameter ratio: Ratio of the cconstant constraint to actual given value (default is *1*)
     - parameter priority: Constraint's priority (default is *.required*).
     - returns: The applied constraint (discardable).
     */
    @discardableResult
    public func set(_ edge: QLAttribute, of value: CGFloat, relation: QLRelation = .equal,
                    ratio: CGFloat = 1.0, priority: QLPriority = .required) -> NSLayoutConstraint {
        if translatesAutoresizingMaskIntoConstraints {
            translatesAutoresizingMaskIntoConstraints = false
        }
        let constraint = NSLayoutConstraint(item: self, attribute: edge, relatedBy: relation, toItem: nil, attribute: .notAnAttribute, multiplier: ratio, constant: value)
        constraint.priority = priority
        addConstraint(constraint)
        return constraint
    }
    
    /**
     Set constant value for multiple edges simultaniously, using variadic parameter.
     Should be used with *width* or *height*
     - parameter edges: Edge types.
     - parameter value: Edges size.
     - parameter priority: Constraint's priority (default is *.required*).
     - returns: The applied constraints in QLMultipleConstraints - see definition (discardable).
     */
    @discardableResult
    public func set(_ edges: QLAttribute..., of value: CGFloat, relation: QLRelation = .equal,
                    ratio: CGFloat = 1.0, priority: QLPriority = .required) -> QLMultipleConstraints {
        return set(edges, to: value, relation: relation, ratio: ratio, priority: priority)
    }
    
    /** **PRIVATELY USED** AS A REPLACEMENT for the variadic version for the method*/
    @discardableResult
    func set(_ edges: [QLAttribute], to value: CGFloat, relation: QLRelation = .equal,
             ratio: CGFloat = 1.0, priority: QLPriority = .required) -> QLMultipleConstraints {
        var constraints: QLMultipleConstraints = [:]
        let uniqueEdges = Set(edges)
        for edge in uniqueEdges {
            let constraint = set(edge, of: value, priority: priority)
            constraints[edge] = constraint
        }
        return constraints
    }
    
    /**
     Layout edge to another view's edge.
     - You can optionally define relation, ratio, constant and priority (each gets a default value)
     - For example - Can be used to align self *left* edge to the *right* of another view.
     - *self* and *view* must be directly connected (siblings / child-parent) in the view hierarchy.
     - *superview* must not be *nil*.
     - parameter edge: The edge of the first view. If not sent or *nil* - The function automatically assumes *edge* to be *otherEdge*
     - parameter otherEdge: The edge of the second view.
     - parameter view: The second view that self must be aligned with.
     - parameter relation: The relation of the first edge to the second edge (default is .equal)
     - parameter ratio: The ratio of the edge in relative to the superview edge (default is 1).
     - parameter offset: Additional offset which is applied to the constraint (default is 0).
     - parameter priority: Constraint's priority (default is *.required*).
     - returns: The instance of the constraint that was applied (discardable). nil if method failed to apply the constraint.
     */
    @discardableResult
    public func layout(_ edge: QLAttribute? = nil, to otherEdge: QLAttribute, of view: QLView,
                       relation: QLRelation = .equal, ratio: CGFloat = 1.0, offset: CGFloat = 0,
                       priority: QLPriority = .required) -> NSLayoutConstraint? {
        guard isValidForQuickLayout else {
            print("\(String(describing: self)) Error in func: \(#function)")
            return nil
        }
        let constraint = NSLayoutConstraint(item: self, attribute: edge ?? otherEdge, relatedBy: relation, toItem: view, attribute: otherEdge, multiplier: ratio, constant: offset)
        constraint.priority = priority
        superview!.addConstraint(constraint)
        return constraint
    }
    
    /**
     Layout multiple edges of the view to the corresonding edges of another given view.
     - You can optionally define relation, ratio, constant and priority (each gets a default value)
     - For example - Can be used to align self *left* and *right* edges the same edge of another given view.
     - *self* and *view* must be directly connected (siblings / child-parent) in the view hierarchy.
     - *superview* must not be *nil*.
     - parameter edges: The view edges
     - parameter view: Another view that self must be aligned with.
     - parameter relation: The relation of the edges. Can be applied to *.width* or *height* for example. (default is *.equal*).
     - parameter ratio: The ratio of the edges to the other view edges (default is 1).
     - parameter offset: Additional offset which is applied to each of the constraints (default is 0).
     - parameter priority: Constraints' priority (default is *.required*).
     - returns: The instance of the constraint that was applied (discardable). *nil* if the method failed to apply the constraint.
     */
    @discardableResult
    public func layout(_ edges: QLAttribute..., to view: QLView, relation: QLRelation = .equal,
                       ratio: CGFloat = 1.0, offset: CGFloat = 0,
                       priority: QLPriority = .required) -> QLMultipleConstraints {
        var constraints: QLMultipleConstraints = [:]
        guard isValidForQuickLayout else {
            print("\(String(describing: self)) Error in func: \(#function)")
            return constraints
        }
        let uniqueEdges = Set(edges)
        for edge in uniqueEdges {
            let constraint = NSLayoutConstraint(item: self, attribute: edge, relatedBy: relation, toItem: view, attribute: edge, multiplier: ratio, constant: offset)
            constraint.priority = priority
            superview!.addConstraint(constraint)
            constraints[edge] = constraint
        }
        return constraints
    }
    
    /**
     Layout edge to the same edge of superview.
     - Example of usage: *view.layoutToSuperview(.top)* makes *view* cling to the *top* of it's *superview*.
     - You can optionally define ratio, constant and priority (each gets a default value)
     - *superview* must not be *nil*.
     - parameter edge: The edge (.width, .height, .left, .right, .leading, .trailing, etc...)
     - parameter relation: The relation of the edge to the superview's corresponding edge (default is *.equal*)
     - parameter ratio: The ratio of the edge in relative to the superview edge (default is 1).
     - parameter offset: Additional offset from that can be applied to the constraint (default is 0).
     - parameter priority: Constraint's priority (default is *.required*).
     - returns: The instance of the constraint that was applied (discardable). Nil if method failed to apply constraint.
     */
    @discardableResult
    public func layoutToSuperview(_ edge: QLAttribute, relation: QLRelation = .equal,
                                  ratio: CGFloat = 1, offset: CGFloat = 0,
                                  priority: QLPriority = .required) -> NSLayoutConstraint? {
        guard isValidForQuickLayout else {
            print("\(String(describing: self)) Error in func: \(#function)")
            return nil
        }
        let constraint = NSLayoutConstraint(item: self, attribute: edge, relatedBy: relation, toItem: superview, attribute: edge, multiplier: ratio, constant: offset)
        constraint.priority = priority
        superview!.addConstraint(constraint)
        return constraint
    }
    
    /**
     Layout multiple edges to the same edges as superview, using variadic parameter.
     Example for edges value:
     - You can optionally define ratio, constant and priority (each gets a default value)
     - *superview* must not be *nil*.
     - parameter edges: The edges (.width, .height, .left, .right, .leading, .trailing, etc...)
     - parameter relation: The relation of the edges to the superview's corresponding edges (default is *.equal*)
     - parameter ratio: The ratio of the edges in relative to the superview edge (default is 1).
     - parameter offset: Additional offset from that can be applied to the constraints (default is 0).
     - parameter priority: Constraints' priority (default is *.required*).
     - returns: The instance of QLMultipleConstraints - see type definition (discardable).
     */
    @discardableResult
    public func layoutToSuperview(_ edges: QLAttribute..., relation: QLRelation = .equal,
                                  ratio: CGFloat = 1, offset: CGFloat = 0,
                                  priority: QLPriority = .required) -> QLMultipleConstraints {
        var constraints: QLMultipleConstraints = [:]
        guard !edges.isEmpty && isValidForQuickLayout else {
            return constraints
        }
        let uniqueEdges = Set(edges)
        for edge in uniqueEdges {
            let constraint = NSLayoutConstraint(item: self, attribute: edge, relatedBy: relation, toItem: superview, attribute: edge, multiplier: ratio, constant: offset)
            constraint.priority = priority
            superview!.addConstraint(constraint)
            constraints[edge] = constraint
        }
        return constraints
    }
    
    /**
     Layout to one of the superview's axes.
     - You can optionally define ratio, constant and priority (each gets a default value)
     - *superview* must not be *nil*.
     - parameter axis: The axis to which the view must be stretched (horizontally or vertically)
     - parameter offset: Represents an additional edge offset from that can be applied to the constraints (default is 0)
     - parameter priority: Represents constraint's priority (default is *.required*)
     - returns: The instance of the constraint that was applied (discardable).
     */
    @discardableResult
    public func layoutToSuperview(axis: QLAxis, offset: CGFloat = 0,
                                  priority: QLPriority = .required) -> QLAxisConstraints? {
        let attributes = axis.attributes
        guard let first = layoutToSuperview(attributes.first, offset: offset, priority: priority) else {
            return nil
        }
        guard let second = layoutToSuperview(attributes.second, offset: -offset, priority: priority) else {
            return nil
        }
        return QLAxisConstraints(first: first, second: second)
    }
    
    /**
     Size to superview with a given ratio and constant
     - *superview* must not be *nil*.
     - parameter ratio: The ratio of view to the size of superview.
     - parameter offset: Represents an additional edge offset from that can be applied to the size (default is 0)
     - parameter priority: Represents constraint's priority (default is *.required*)
     - returns: The instance of QLSizeConstraints - see definition (discardable).
     */
    @discardableResult
    public func sizeToSuperview(withRatio ratio: CGFloat = 1, offset: CGFloat = 0,
                                priority: QLPriority = .required) -> QLSizeConstraints? {
        let size = layoutToSuperview(.width, .height, ratio: ratio, offset: offset, priority: priority)
        guard !size.isEmpty else {
            return nil
        }
        return QLSizeConstraints(width: size[.width]!, height: size[.height]!)
    }
    
    /**
     Center in superview with an optional offset
     - *superview* must not be *nil*.
     - parameter offset: Represents an additional offset from the center (default is 0)
     - parameter priority: Represents constraint's priority (default is *.required*)
     - returns: The instance of QLCenterConstraints - see definition (discardable).
     */
    @discardableResult
    public func centerInSuperview(offset: CGFloat = 0, priority: QLPriority = .required) -> QLCenterConstraints? {
        let center = layoutToSuperview(.centerX, .centerY, offset: offset)
        guard !center.isEmpty else {
            return nil
        }
        return QLCenterConstraints(x: center[.centerX]!, y: center[.centerY]!)
    }
    
    /**
     Fill superview totally (center and size to superview)
     - *superview* must not be *nil*.
     - parameter ratio: Ratio to the superview's size (default is 1)
     - parameter offset: Offset from center (default is 0)
     - parameter priority: Represents constraint's priority (default is *.required*)
     - returns: The instance of QLFillConstraints - see definition (discardable).
     */
    @discardableResult
    public func fillSuperview(withSizeRatio ratio: CGFloat = 1, offset: CGFloat = 0,
                              priority: QLPriority = .required) -> QLFillConstraints? {
        guard let center = centerInSuperview(priority: priority) else {
            return nil
        }
        guard let size = sizeToSuperview(withRatio: ratio, offset: offset, priority: priority) else {
            return nil
        }
        return QLFillConstraints(center: center, size: size)
    }
    
    /** **PRIVATELY USED** to test for validation*/
    var isValidForQuickLayout: Bool {
        guard superview != nil else {
            print("\(String(describing: self)):\(#function) - superview is unexpectedly nullified")
            return false
        }
        if translatesAutoresizingMaskIntoConstraints {
            translatesAutoresizingMaskIntoConstraints = false
        }
        return true
    }
}
