//
//  ConfettiView.swift
//  Tusker
//
//  Created by Shadowfacts on 1/25/23.
//  Copyright © 2023 Shadowfacts. All rights reserved.
//

import SwiftUI

struct ConfettiView: View {
    private static let colors = [
        Color.red,
        .purple,
        .blue,
        .pink,
        .yellow,
        .green,
        .teal,
        .cyan,
        .mint,
        .indigo,
        .orange,
    ]
    
    @State private var size: CGSize?
    @State private var startDate: Date?
    
    var body: some View {
        allConfetti
            .frame(maxWidth: .infinity, maxHeight: .infinity)
            .background(GeometryReader { proxy in
                Color.clear
                    .preference(key: SizeKey.self, value: proxy.size)
                    .onPreferenceChange(SizeKey.self) { newValue in
                        self.size = newValue
                        self.startDate = Date()
                    }
            })
    }
    
    @ViewBuilder
    private var allConfetti: some View {
        if let size {
            TimelineView(.animation) { context in
                ZStack {
                    ForEach(0..<200) { idx in
                        let time = context.date.timeIntervalSince(startDate!)
                        ConfettiPiece(shape: Rectangle(), color: Self.colors[idx % Self.colors.count], canvasSize: size, time: time)
                        ConfettiPiece(shape: Circle(), color: Self.colors[idx % Self.colors.count], canvasSize: size, time: time)
                        ConfettiPiece(shape: Triangle(), color: Self.colors[idx % Self.colors.count], canvasSize: size, time: time)
                    }
                }
            }
        } else {
            Color.clear
        }
    }
}

private struct SizeKey: PreferenceKey {
    static var defaultValue: CGSize = .zero
    static func reduce(value: inout CGSize, nextValue: () -> CGSize) {
        value = nextValue()
    }
}

private struct ConfettiPiece<S: Shape>: View {
    let shape: S
    let color: Color
    let canvasSize: CGSize
    let time: TimeInterval
    @State private var start: CGPoint = .zero
    @State private var velocity: CGPoint = {
        let angle = CGFloat.random(in: 0..<2) * .pi
        let velocity = CGFloat.random(in: 25...250)
        return CGPoint(x: cos(angle) * velocity, y: sin(angle) * velocity)
    }()
    private let gravity: CGFloat = 100
    @State private var rotationAxis: (CGFloat, CGFloat, CGFloat) = (.random() ? 1 : 0, .random() ? 1 : 0, .random() ? 1 : 0)
    @State private var rotationsPerSecond = Double.random(in: 0.5..<2)
    
    private var x: CGFloat {
        (canvasSize.width / 2) + velocity.x * time
    }
    
    private var y: CGFloat {
        (canvasSize.height / 2) + velocity.y * time + 0.5 * time * time * gravity
    }
    
    private var angle: Angle {
        .degrees(time * rotationsPerSecond * 360)
    }
    
    var body: some View {
        shape
            .frame(width: 7, height: 7)
            .foregroundColor(color)
            .rotation3DEffect(angle, axis: rotationAxis)
            .position(x: x, y: y)
    }
}

private struct Triangle: Shape {
    func path(in rect: CGRect) -> Path {
        var p = Path()
        p.move(to: CGPoint(x: rect.midX, y: rect.minY))
        p.addLine(to: CGPoint(x: rect.minX, y: rect.maxY))
        p.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY))
        p.closeSubpath()
        return p
    }
}

struct ConfettiView_Previews: PreviewProvider {
    static var previews: some View {
        ConfettiView()
    }
}
