//
//  StackView.swift
//  App
//
//  Created by James Magahern on 9/22/20.
//

import UIKit

class StackView<T: UIView>: UIView
{
    public enum LayoutType {
        case intrinsicSize
        case equalSize
    }
    
    var arrangedSubviews: [T] = []
        { didSet { setNeedsLayout() } }
    
    var layoutDimension: UIAxis = .vertical
    var layoutType: LayoutType = .intrinsicSize
    
    convenience init(dimension: UIAxis = .vertical, layoutType: LayoutType = .intrinsicSize) {
        self.init(frame: .zero)
        self.layoutDimension = dimension
        self.layoutType = layoutType
    }
    
    // Convenience
    public func addArrangedSubview(_ view: T) {
        addSubview(view)
        arrangedSubviews.append(view)
        setNeedsLayout()
    }
    
    public func removeArrangedSubview(_ view: T) {
        if view.superview == self {
            view.removeFromSuperview()
        }
        
        arrangedSubviews.removeAll { $0 == view }
        setNeedsLayout()
    }
    
    public func removeAllArrangedSubviews() {
        arrangedSubviews.forEach { $0.removeFromSuperview() }
        arrangedSubviews.removeAll()
        setNeedsLayout()
    }
    
    override func sizeThatFits(_ containerSize: CGSize) -> CGSize {
        var size: CGSize = .zero
        
        if layoutDimension == .horizontal {
            size.height = containerSize.height
        } else {
            size.width = containerSize.width
        }
        
        var spaceRemaining = containerSize
        for view in arrangedSubviews {
            let viewSize = view.sizeThatFits(spaceRemaining)
            if layoutDimension == .horizontal {
                size.width += viewSize.width
                spaceRemaining.width -= viewSize.width
            } else {
                size.height += viewSize.height
                spaceRemaining.height -= viewSize.height
            }
        }
        
        return size
    }
    
    override func layoutSubviews() {
        super.layoutSubviews()
        
        if layoutType == .intrinsicSize {
            layoutWithIntrinsicSize()
        } else if layoutType == .equalSize {
            layoutWithEqualSize()
        } else {
            fatalError("StackView: I don't know how to lay out this type")
        }
    }
    
    fileprivate func layoutWithIntrinsicSize() {
        var origin: CGPoint = CGPoint(x: safeAreaInsets.left, y: safeAreaInsets.top)
        var spaceRemaining = bounds.size
        for view in arrangedSubviews {
            var viewSize = view.sizeThatFits(spaceRemaining)
            
            var offset: CGPoint = .zero
            if layoutDimension == .horizontal {
                offset.x = viewSize.width
                viewSize.height = bounds.height
                spaceRemaining.width -= viewSize.width
            } else {
                offset.y = viewSize.height
                viewSize.width = bounds.width
                spaceRemaining.height -= viewSize.height
            }
            
            view.frame = CGRect(origin: origin, size: viewSize)
            origin.x += offset.x
            origin.y += offset.y
        }

    }
    
    fileprivate func layoutWithEqualSize() {
        let numberOfItems = arrangedSubviews.count
        
        let structureDimension: CGFloat = (
            (layoutDimension == .horizontal) ? bounds.height : bounds.width
        )
        
        let itemDimension: CGFloat = (
            (layoutDimension == .horizontal) ? (bounds.width / CGFloat(numberOfItems)) : (bounds.height / CGFloat(numberOfItems))
        )
        
        var origin: CGPoint = CGPoint(x: safeAreaInsets.left, y: safeAreaInsets.top)
        for view in arrangedSubviews {
            let size: CGSize = (
                (layoutDimension == .horizontal)
                    ? CGSize(width: itemDimension, height: structureDimension)
                    : CGSize(width: structureDimension, height: itemDimension)
            )
            
            view.frame = CGRect(origin: origin, size: size)
            
            origin = (
                (layoutDimension == .horizontal)
                    ? CGPoint(x: origin.x + size.width, y: origin.y)
                    : CGPoint(x: origin.x, y: origin.y + size.height)
            )
        }
    }
}
