import Foundation
import UIKit
import Display
import ComponentFlow
import Camera
import CameraButtonComponent

private func generateCollageIcon(grid: Camera.CollageGrid, crossed: Bool) -> UIImage? {
    return generateImage(CGSize(width: 36.0, height: 36.0), rotatedContext: { size, context in
        let bounds = CGRect(origin: .zero, size: size)
        context.clear(bounds)
                      
        let lineWidth = 2.0 - UIScreenPixel
        context.setLineWidth(lineWidth)
        context.setStrokeColor(UIColor.white.cgColor)

        let iconBounds = bounds.insetBy(dx: 11.0, dy: 9.0)
        let path = UIBezierPath(roundedRect: iconBounds, cornerRadius: 3.0)
        context.addPath(path.cgPath)
        context.strokePath()
        
        let rowHeight = iconBounds.height / CGFloat(grid.rows.count)
        
        var yOffset: CGFloat = iconBounds.minY + lineWidth / 2.0
        for i in 0 ..< grid.rows.count {
            let row = grid.rows[i]
            var xOffset: CGFloat = iconBounds.minX
            let lineCount = max(0, row.columns - 1)
            let colWidth = iconBounds.width / CGFloat(max(row.columns, 1))
            for _ in 0 ..< lineCount {
                xOffset += colWidth
                context.move(to: CGPoint(x: xOffset, y: yOffset))
                context.addLine(to: CGPoint(x: xOffset, y: yOffset + rowHeight))
                context.strokePath()
            }
            yOffset += rowHeight
            
            if i != grid.rows.count - 1 {
                context.move(to: CGPoint(x: iconBounds.minX, y: yOffset - lineWidth / 2.0))
                context.addLine(to: CGPoint(x: iconBounds.maxX, y: yOffset - lineWidth / 2.0))
                context.strokePath()
            }
        }
        
        if crossed {
            context.setLineCap(.round)
            
            let startPoint = CGPoint(x: iconBounds.minX - 3.0, y: iconBounds.minY - 2.0)
            let endPoint = CGPoint(x: iconBounds.maxX + 4.0, y: iconBounds.maxY + 1.0)
            
            context.setBlendMode(.clear)
            context.move(to: startPoint.offsetBy(dx: 0.0, dy: lineWidth))
            context.addLine(to: endPoint.offsetBy(dx: 0.0, dy: lineWidth))
            context.strokePath()
            
            context.setBlendMode(.normal)
            
            context.move(to: startPoint)
            context.addLine(to: endPoint)
            context.strokePath()
        }
    })
}

final class CollageIconComponent: Component {
    typealias EnvironmentType = Empty
    
    let grid: Camera.CollageGrid
    let crossed: Bool
    let isSelected: Bool
    let tintColor: UIColor
    
    init(
        grid: Camera.CollageGrid,
        crossed: Bool,
        isSelected: Bool,
        tintColor: UIColor
    ) {
        self.grid = grid
        self.crossed = crossed
        self.isSelected = isSelected
        self.tintColor = tintColor
    }
    
    static func ==(lhs: CollageIconComponent, rhs: CollageIconComponent) -> Bool {
        if lhs.grid != rhs.grid {
            return false
        }
        if lhs.crossed != rhs.crossed {
            return false
        }
        if lhs.isSelected != rhs.isSelected {
            return false
        }
        if lhs.tintColor != rhs.tintColor {
            return false
        }
        return true
    }
    
    final class View: UIView {
        private let iconView = UIImageView()
                
        private var component: CollageIconComponent?
        private weak var state: EmptyComponentState?
        
        override init(frame: CGRect) {
            super.init(frame: frame)
                     
            self.addSubview(self.iconView)
        }
        
        required init?(coder: NSCoder) {
            fatalError("init(coder:) has not been implemented")
        }
                
        func update(component: CollageIconComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<EnvironmentType>, transition: ComponentTransition) -> CGSize {
            let previousComponent = self.component
            self.component = component
            self.state = state
                        
            if component.grid != previousComponent?.grid {
                let image = generateCollageIcon(grid: component.grid, crossed: component.crossed)
                let selectedImage = generateImage(CGSize(width: 36.0, height: 36.0), contextGenerator: { size, context in
                    context.clear(CGRect(origin: .zero, size: size))
                    context.setFillColor(UIColor.white.cgColor)
                    context.fillEllipse(in: CGRect(origin: .zero, size: size))
                    
                    if let image, let cgImage = image.cgImage {
                        context.setBlendMode(.clear)
                        context.clip(to: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - image.size.width) / 2.0), y: floorToScreenPixels((size.height - image.size.height) / 2.0) - 1.0), size: image.size), mask: cgImage)
                        context.fill(CGRect(origin: .zero, size: size))
                    }
                })?.withRenderingMode(.alwaysTemplate)
                
                self.iconView.image = image

                if self.iconView.isHighlighted {
                    self.iconView.isHighlighted = false
                    self.iconView.highlightedImage = selectedImage
                    self.iconView.isHighlighted = true
                } else {
                    self.iconView.highlightedImage = selectedImage
                }
            }
            
            let size = CGSize(width: 36.0, height: 36.0)
            self.iconView.frame = CGRect(origin: .zero, size: size)
            self.iconView.isHighlighted = component.isSelected
            
            self.iconView.tintColor = component.tintColor
            
            return size
        }
    }

    public func makeView() -> View {
        return View(frame: CGRect())
    }
    
    public func update(view: View, availableSize: CGSize, state: State, environment: Environment<EnvironmentType>, transition: ComponentTransition) -> CGSize {
        return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
    }
}

final class CollageIconCarouselComponent: Component {
    typealias EnvironmentType = Empty
    
    let grids: [Camera.CollageGrid]
    let selected: (Camera.CollageGrid) -> Void
    
    init(
        grids: [Camera.CollageGrid],
        selected: @escaping (Camera.CollageGrid) -> Void
    ) {
        self.grids = grids
        self.selected = selected
    }
    
    static func ==(lhs: CollageIconCarouselComponent, rhs: CollageIconCarouselComponent) -> Bool {
        if lhs.grids != rhs.grids {
            return false
        }
        return true
    }
    
    final class View: UIView {
        private let clippingView = UIView()
        private let scrollView = UIScrollView()
        
        private var itemViews: [AnyHashable: ComponentView<Empty>] = [:]
                
        private var component: CollageIconCarouselComponent?
        private weak var state: EmptyComponentState?
        
        override init(frame: CGRect) {
            super.init(frame: frame)
                                 
            self.scrollView.contentInsetAdjustmentBehavior = .never
            self.scrollView.showsVerticalScrollIndicator = false
            self.scrollView.showsHorizontalScrollIndicator = false
            
            self.addSubview(self.clippingView)
            self.clippingView.addSubview(self.scrollView)
        }
        
        required init?(coder: NSCoder) {
            fatalError("init(coder:) has not been implemented")
        }
                
        func update(component: CollageIconCarouselComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<EnvironmentType>, transition: ComponentTransition) -> CGSize {
            self.component = component
            self.state = state
            
            let inset: CGFloat = 27.0
            let spacing: CGFloat = availableSize.width > 290.0 ? 7.0 : 8.0
            var contentWidth: CGFloat = inset
            let buttonSize = CGSize(width: 40.0, height: 40.0)
            
            var validIds: [AnyHashable] = []
            for grid in component.grids {
                validIds.append(grid)
                
                let itemView: ComponentView<Empty>
                if let current = itemViews[grid] {
                    itemView = current
                } else {
                    itemView = ComponentView()
                    self.itemViews[grid] = itemView
                }
                let itemSize = itemView.update(
                    transition: .immediate,
                    component: AnyComponent(CameraButton(
                        content: AnyComponentWithIdentity(
                            id: "content",
                            component: AnyComponent(
                                CollageIconComponent(
                                    grid: grid,
                                    crossed: false,
                                    isSelected: false,
                                    tintColor: .white
                                )
                            )
                        ),
                        action: { [weak self] in
                            if let component = self?.component {
                                component.selected(grid)
                            }
                        }
                    )),
                    environment: {},
                    containerSize: buttonSize
                )
                if let view = itemView.view {
                    if view.superview == nil {
                        self.scrollView.addSubview(view)
                        
                        view.layer.shadowOffset = CGSize(width: 0.0, height: 0.0)
                        view.layer.shadowRadius = 3.0
                        view.layer.shadowColor = UIColor.black.cgColor
                        view.layer.shadowOpacity = 0.25
                        view.layer.rasterizationScale = UIScreenScale
                        view.layer.shouldRasterize = true
                    }
                    view.frame = CGRect(origin: CGPoint(x: contentWidth, y: 0.0), size: itemSize)
                }
                contentWidth += itemSize.width + spacing
            }
            
            let contentSize = CGSize(width: contentWidth, height: buttonSize.height)
            if self.scrollView.contentSize != contentSize {
                self.scrollView.contentSize = contentSize
            }
            self.scrollView.frame = CGRect(origin: .zero, size: availableSize)
            self.clippingView.frame = CGRect(origin: .zero, size: availableSize)
            
            if self.clippingView.mask == nil {
                if let maskImage = generateGradientImage(size: CGSize(width: 42.0, height: 10.0), colors: [UIColor.clear, UIColor.black, UIColor.black, UIColor.clear], locations: [0.0, 0.2, 0.8, 1.0], direction: .horizontal) {
                    let maskView = UIImageView(image: maskImage.stretchableImage(withLeftCapWidth: 13, topCapHeight: 0))
                    self.clippingView.mask = maskView
                }
            }
            self.clippingView.mask?.frame = CGRect(origin: .zero, size: availableSize)
            
            var removeIds: [AnyHashable] = []
            for (id, itemView) in self.itemViews {
                if !validIds.contains(id) {
                    removeIds.append(id)
                    itemView.view?.removeFromSuperview()
                }
            }
            for id in removeIds {
                self.itemViews.removeValue(forKey: id)
            }
            
            return availableSize
        }
        
        func animateIn() {
            guard self.frame.width > 0.0 else {
                return
            }
            self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25)
            for (_, itemView) in self.itemViews {
                itemView.view?.layer.animatePosition(from: CGPoint(x: self.frame.width, y: 0.0), to: .zero, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
            }
        }
        
        func animateOut(completion: @escaping () -> Void) {
            guard self.frame.width > 0.0 else {
                return
            }
            self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { _ in
                completion()
            })
            for (_, itemView) in self.itemViews {
                itemView.view?.layer.animatePosition(from: .zero, to: CGPoint(x: self.frame.width + self.scrollView.contentOffset.x, y: 0.0), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, additive: true)
            }
        }
    }

    public func makeView() -> View {
        return View(frame: CGRect())
    }
    
    public func update(view: View, availableSize: CGSize, state: State, environment: Environment<EnvironmentType>, transition: ComponentTransition) -> CGSize {
        return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
    }
}