Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
folium-app
GitHub Repository: folium-app/Folium
Path: blob/a-new-beginning/Folium-iOS/Controllers/EmulationControllers/CytrusController.swift
2 views
//
//  CytrusController.swift
//  Folium
//
//  Created by Jarrod Norwell on 28/11/2025.
//

import Cytrus
import GameController
import StoreKit
import UIKit

class CytrusController : UIViewController {
    var imageView: MTKView? = nil,
        secondaryImageView: MTKView? = nil
    
    var visualEffectView: UIVisualEffectView? = nil
    
    var settingsButton: UIButton? = nil,
        selectButton: UIButton? = nil,
        startButton: UIButton? = nil
    
    var upButton: UIButton? = nil,
        downButton: UIButton? = nil,
        leftButton: UIButton? = nil,
        rightButton: UIButton? = nil
    
    var bButton: UIButton? = nil,
        aButton: UIButton? = nil,
        xButton: UIButton? = nil,
        yButton: UIButton? = nil
    
    var lButton: UIButton? = nil,
        rButton: UIButton? = nil,
        zlButton: UIButton? = nil,
        zrButton: UIButton? = nil
    
    var leftThumbstick: LatestControllerThumbstick? = nil,
        rightThumbstick: LatestControllerThumbstick? = nil
    
    var constraints: (portrait: [NSLayoutConstraint], landscape: [NSLayoutConstraint]) = ([], [])
    
    var applicationModel: ApplicationModel
    var game: NewCytrusGame? = nil
    init(_ applicationModel: ApplicationModel, _ game: NewCytrusGame? = nil) {
        self.applicationModel = applicationModel
        self.game = game
        super.init(nibName: nil, bundle: nil)
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .systemBackground
    }
    
    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
    
        let device: MTLDevice? = MTLCreateSystemDefaultDevice()
        
        if #available(iOS 26, *) {
            let effect: UIGlassContainerEffect = .init()
            effect.spacing = 20
            
            visualEffectView = .init(effect: effect)
            guard let visualEffectView else {
                return
            }
            visualEffectView.translatesAutoresizingMaskIntoConstraints = false
            view.addSubview(visualEffectView)
            
            visualEffectView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
            visualEffectView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
            visualEffectView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
            visualEffectView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
        }
        
        let viewToAddSubviews: UIView = visualEffectView?.contentView ?? view
        
        imageView = MTKView(frame: .zero, device: device)
        guard let imageView else {
            return
        }
        imageView.translatesAutoresizingMaskIntoConstraints = false
        imageView.backgroundColor = .secondaryLabel
        if #available(iOS 26, *) {
            imageView.clipsToBounds = true
            imageView.cornerConfiguration = .corners(radius: .fixed(15.0))
        } else {
            imageView.clipsToBounds = true
            imageView.layer.cornerCurve = .continuous
            imageView.layer.cornerRadius = 15.0
        }
        viewToAddSubviews.addSubview(imageView)
        
        secondaryImageView = MTKView(frame: .zero, device: device)
        guard let secondaryImageView else {
            return
        }
        secondaryImageView.translatesAutoresizingMaskIntoConstraints = false
        secondaryImageView.backgroundColor = .secondaryLabel
        if #available(iOS 26, *) {
            secondaryImageView.clipsToBounds = true
            secondaryImageView.cornerConfiguration = .corners(radius: .fixed(15.0))
        } else {
            secondaryImageView.clipsToBounds = true
            secondaryImageView.layer.cornerCurve = .continuous
            secondaryImageView.layer.cornerRadius = 15.0
        }
        secondaryImageView.isUserInteractionEnabled = true
        viewToAddSubviews.addSubview(secondaryImageView)
        
        var menuChildren: [UIMenuElement] = [
            UIAction(title: "Multiplayer", image: .init(systemName: "network"), handler: { _ in
                guard let game = self.game else {
                    return
                }
                
                self.applicationModel.cytrus.pause(true)
                
                let navigationController: UINavigationController = UINavigationController(rootViewController: CytrusMultiplayerRoomsController(self.applicationModel, game))
                navigationController.modalPresentationStyle = .fullScreen
                self.present(navigationController, animated: true)
                
                /*
                Task {
                    switch await Transaction.latest(for: "premium_month") {
                    case .unverified(let transaction, let error):
                        print(#line, transaction.expirationDate?.formatted() ?? "", error.localizedDescription)
                    case .verified(let transaction):
                        if let expirationDate = transaction.expirationDate, expirationDate.compare(Date()) == .orderedDescending {
                            
                        } else {
                            await StoreModel(applicationModel: self.applicationModel).premium(controller: self)
                        }
                    case .none:
                        await StoreModel(applicationModel: self.applicationModel).premium(controller: self)
                    }
                }*/
            }),
            UIMenu(title: "Amiibo", image: .init(systemName: "person.crop.circle"), children: [
                UIAction(title: "Remove", image: .init(systemName: "minus.circle")) { _ in
                    self.applicationModel.cytrus.removeAmiibo()
                },
                UIAction(title: "Insert", image: .init(systemName: "plus.circle")) { _ in
                    let documentPicker: UIDocumentPickerViewController = .init(forOpeningContentTypes: [.item], asCopy: true)
                    documentPicker.delegate = self
                    self.present(documentPicker, animated: true)
                }
            ])
        ]
        
        if let connectedRoom = applicationModel.cytrus.multiplayer.connectedRoom {
            menuChildren.prepend(UIAction(title: "Chat", image: .init(systemName: "bubble.left.and.bubble.right")) { _ in
                let chatController: CytrusMultiplayerChatController = .init(applicationModel: self.applicationModel,
                                                                            room: connectedRoom,
                                                                            collectionViewLayout: self.applicationModel.layoutManager.list)
                
                let navigationController: UINavigationController = .init(rootViewController: chatController)
                
                if let sheetPresentationController = navigationController.sheetPresentationController {
                    let custom: UISheetPresentationController.Detent = .custom { context in self.view.safeAreaInsets.top + self.view.safeAreaInsets.bottom }
                    let medium: UISheetPresentationController.Detent = .medium()
                    let large: UISheetPresentationController.Detent = .large()
                    
                    sheetPresentationController.detents = [custom, medium, large]
                    sheetPresentationController.largestUndimmedDetentIdentifier = custom.identifier
                    sheetPresentationController.prefersGrabberVisible = true
                    if #available(iOS 26.1, *) {
                        sheetPresentationController.backgroundEffect = UIGlassEffect(style: .regular)
                    }
                    
                    self.present(navigationController, animated: true)
                }
            })
            
            menuChildren.prepend(UIAction(title: "Disconnect", image: .init(systemName: "minus.circle"), attributes: .destructive) { _ in
                self.applicationModel.cytrus.multiplayer.disconnect()
            })
        }
        
        let settingsConfiguration: UIButton.Configuration = .configuration(.medium, .capsule, UIImage(systemName: "ellipsis"), .medium)
        settingsButton = .button(with: settingsConfiguration, actions: ({ _ in }, { _ in }), UIMenu(children: [
            UIMenu(options: .displayInline, children: [
                UIDeferredMenuElement.uncached { completion in
                    Task {
                        completion([
                            UIAction(title: "Stop & Exit", image: UIImage(systemName: "stop"), attributes: .destructive) { _ in
                                let alertController: UIAlertController = UIAlertController(title: "Stop & Exit",
                                                                                           message: "Are you sure you want to stop & exit? Unsaved progress will be lost",
                                                                                           preferredStyle: .alert)
                                alertController.addAction(UIAlertAction(title: .localized(for: .dismiss), style: .cancel))
                                alertController.addAction(UIAlertAction(title: "Stop & Exit", style: .destructive, handler: { _ in
                                    self.applicationModel.cytrus.stop()
                                    self.dismiss(animated: true)
                                }))
                                self.present(alertController, animated: true)
                            },
                            UIAction(title: self.applicationModel.cytrus.isPaused ? "Resume" : "Pause",
                                     image: self.applicationModel.cytrus.isPaused ? UIImage(systemName: "play") : UIImage(systemName: "pause")) { _ in
                                         self.applicationModel.cytrus.pause(!self.applicationModel.cytrus.isPaused)
                                     }
                        ])
                    }
                }
            ]),
            UIMenu(options: .displayInline, children: [
                UIMenu(title: "Save State", image: .init(systemName: "arrow.down.circle"), preferredElementSize: .small, children: [
                    UIDeferredMenuElement.uncached { completion in
                        guard let game = self.game, let identifier: UInt64 = game.information.identifier else {
                            completion([])
                            return
                        }
                        
                        let stateNumbers: [Int] = [0, 1, 2]
                        
                        let elements: [UIAction] = stateNumbers.map { state in
                            return .init(image: UIImage(systemName: "\(state + 1).circle")) { _ in
                                if self.applicationModel.cytrus.stateExists(identifier, state) {
                                    let alertController: UIAlertController = UIAlertController(title: "Overwrite?",
                                                                                               message: "Are you sure you want to overwrite? This action is irreversible",
                                                                                               preferredStyle: .alert)
                                    alertController.addAction(UIAlertAction(title: .localized(for: .dismiss), style: .cancel))
                                    alertController.addAction(UIAlertAction(title: "Overwrite", style: .destructive, handler: { _ in
                                        self.applicationModel.cytrus.save(state)
                                    }))
                                    self.present(alertController, animated: true)
                                } else {
                                    self.applicationModel.cytrus.save(state)
                                }
                            }
                        }
                        
                        completion(elements)
                    }
                ]),
                UIMenu(title: "Load State", image: .init(systemName: "arrow.up.circle"), preferredElementSize: .small, children: [
                    UIDeferredMenuElement.uncached { completion in
                        guard let game = self.game, let identifier: UInt64 = game.information.identifier else {
                            completion([])
                            return
                        }
                        
                        let stateNumbers: [Int] = [0, 1, 2]
                        
                        let elements: [UIAction] = stateNumbers.map { state in
                            let fileExists: Bool = self.applicationModel.cytrus.stateExists(identifier, state)
                            return .init(image: UIImage(systemName: "\(state + 1).circle"), attributes: fileExists ? [] : .disabled) { _ in
                                self.applicationModel.cytrus.load(state)
                            }
                        }
                        
                        completion(elements)
                    }
                ]),
                UIMenu(options: .displayInline, children: [
                    UIAction(title: "Capture Still Image", image: UIImage(systemName: "photo.badge.arrow.down")) { _ in
                        self.applicationModel.cytrus.pause(true)
                        
                        // guard let imageView: UIImageView = self.imageView, let image: UIImage = imageView.image else {
                        //     return
                        // }
                        //
                        // UIImageWriteToSavedPhotosAlbum(image, self, #selector(self.image(_:didFinishSavingWithError:contextInfo:)), nil)
                        
                        self.applicationModel.cytrus.pause(false)
                    }
                ]),
                UIMenu(options: .displayInline, children: menuChildren)
            ])
        ]))
        guard let settingsButton else {
            return
        }
        viewToAddSubviews.addSubview(settingsButton)
        
        let selectConfiguration: UIButton.Configuration = .configuration(.medium, .capsule, UIImage(systemName: "minus"), .medium)
        selectButton = .button(with: selectConfiguration,
                               actions: ({ _ in
            self.applicationModel.cytrus.button(button: .select, player: 0, pressed: true)
        }, { _ in
            self.applicationModel.cytrus.button(button: .select, player: 0, pressed: false)
        }))
        guard let selectButton else {
            return
        }
        viewToAddSubviews.addSubview(selectButton)
        
        let startConfiguration: UIButton.Configuration = .configuration(.medium, .capsule, UIImage(systemName: "plus"), .medium)
        startButton = .button(with: startConfiguration,
                              actions: ({ _ in
            self.applicationModel.cytrus.button(button: .start, player: 0, pressed: true)
        }, { _ in
            self.applicationModel.cytrus.button(button: .start, player: 0, pressed: false)
        }))
        guard let startButton else {
            return
        }
        viewToAddSubviews.addSubview(startButton)
        
        let upConfiguration: UIButton.Configuration = .configuration(.large, .capsule, UIImage(systemName: "arrow.up"))
        upButton = .button(with: upConfiguration,
                           actions: ({ _ in
            self.applicationModel.cytrus.button(button: .up, player: 0, pressed: true)
        }, { _ in
            self.applicationModel.cytrus.button(button: .up, player: 0, pressed: false)
        }))
        guard let upButton else {
            return
        }
        viewToAddSubviews.addSubview(upButton)
        
        let downConfiguration: UIButton.Configuration = .configuration(.large, .capsule, UIImage(systemName: "arrow.down"))
        downButton = .button(with: downConfiguration,
                             actions: ({ _ in
            self.applicationModel.cytrus.button(button: .down, player: 0, pressed: true)
        }, { _ in
            self.applicationModel.cytrus.button(button: .down, player: 0, pressed: false)
        }))
        guard let downButton else {
            return
        }
        viewToAddSubviews.addSubview(downButton)
        
        let leftConfiguration: UIButton.Configuration = .configuration(.large, .capsule, UIImage(systemName: "arrow.left"))
        leftButton = .button(with: leftConfiguration,
                             actions: ({ _ in
            self.applicationModel.cytrus.button(button: .left, player: 0, pressed: true)
        }, { _ in
            self.applicationModel.cytrus.button(button: .left, player: 0, pressed: false)
        }))
        guard let leftButton else {
            return
        }
        viewToAddSubviews.addSubview(leftButton)
        
        let rightConfiguration: UIButton.Configuration = .configuration(.large, .capsule, UIImage(systemName: "arrow.right"))
        rightButton = .button(with: rightConfiguration,
                              actions: ({ _ in
            self.applicationModel.cytrus.button(button: .right, player: 0, pressed: true)
        }, { _ in
            self.applicationModel.cytrus.button(button: .right, player: 0, pressed: false)
        }))
        guard let rightButton else {
            return
        }
        viewToAddSubviews.addSubview(rightButton)
        
        let aConfiguration: UIButton.Configuration = .configuration(.large, .capsule, UIImage(systemName: "a.circle"))
        aButton = .button(with: aConfiguration,
                          actions: ({ _ in
            self.applicationModel.cytrus.button(button: .a, player: 0, pressed: true)
        }, { _ in
            self.applicationModel.cytrus.button(button: .a, player: 0, pressed: false)
        }))
        guard let aButton else {
            return
        }
        viewToAddSubviews.addSubview(aButton)
        
        let bConfiguration: UIButton.Configuration = .configuration(.large, .capsule, UIImage(systemName: "b.circle"))
        bButton = .button(with: bConfiguration,
                               actions: ({ _ in
            self.applicationModel.cytrus.button(button: .b, player: 0, pressed: true)
        }, { _ in
            self.applicationModel.cytrus.button(button: .b, player: 0, pressed: false)
        }))
        guard let bButton else {
            return
        }
        viewToAddSubviews.addSubview(bButton)
        
        let yConfiguration: UIButton.Configuration = .configuration(.large, .capsule, UIImage(systemName: "y.circle"))
        yButton = .button(with: yConfiguration,
                                 actions: ({ _ in
            self.applicationModel.cytrus.button(button: .y, player: 0, pressed: true)
        }, { _ in
            self.applicationModel.cytrus.button(button: .y, player: 0, pressed: false)
        }))
        guard let yButton else {
            return
        }
        viewToAddSubviews.addSubview(yButton)
        
        let xConfiguration: UIButton.Configuration = .configuration(.large, .capsule, UIImage(systemName: "x.circle"))
        xButton = .button(with: xConfiguration,
                               actions: ({ _ in
            self.applicationModel.cytrus.button(button: .x, player: 0, pressed: true)
        }, { _ in
            self.applicationModel.cytrus.button(button: .x, player: 0, pressed: false)
        }))
        guard let xButton else {
            return
        }
        viewToAddSubviews.addSubview(xButton)
        
        let lConfiguration: UIButton.Configuration = .configuration(.large, .capsule, UIImage(systemName: "left"))
        lButton = .button(with: lConfiguration,
                           actions: ({ _ in
            self.applicationModel.cytrus.button(button: .l, player: 0, pressed: true)
        }, { _ in
            self.applicationModel.cytrus.button(button: .l, player: 0, pressed: false)
        }))
        guard let lButton else {
            return
        }
        viewToAddSubviews.addSubview(lButton)
        
        let zlConfiguration: UIButton.Configuration = .configuration(.medium, .capsule, UIImage(systemName: "circle"), .medium)
        zlButton = .button(with: zlConfiguration,
                           actions: ({ _ in
            self.applicationModel.cytrus.button(button: .zl, player: 0, pressed: true)
        }, { _ in
            self.applicationModel.cytrus.button(button: .zl, player: 0, pressed: false)
        }))
        guard let zlButton else {
            return
        }
        viewToAddSubviews.addSubview(zlButton)
        
        let rConfiguration: UIButton.Configuration = .configuration(.large, .capsule, UIImage(systemName: "right"))
        rButton = .button(with: rConfiguration,
                           actions: ({ _ in
            self.applicationModel.cytrus.button(button: .r, player: 0, pressed: true)
        }, { _ in
            self.applicationModel.cytrus.button(button: .r, player: 0, pressed: false)
        }))
        guard let rButton else {
            return
        }
        viewToAddSubviews.addSubview(rButton)
        
        let zrConfiguration: UIButton.Configuration = .configuration(.medium, .capsule, UIImage(systemName: "circle"), .medium)
        zrButton = .button(with: zrConfiguration,
                           actions: ({ _ in
            self.applicationModel.cytrus.button(button: .zr, player: 0, pressed: true)
        }, { _ in
            self.applicationModel.cytrus.button(button: .zr, player: 0, pressed: false)
        }))
        guard let zrButton else {
            return
        }
        viewToAddSubviews.addSubview(zrButton)
        
        leftThumbstick = .init(.left, self)
        guard let leftThumbstick else {
            return
        }
        leftThumbstick.translatesAutoresizingMaskIntoConstraints = false
        viewToAddSubviews.insertSubview(leftThumbstick, belowSubview: upButton)
        
        rightThumbstick = .init(.right, self)
        guard let rightThumbstick else {
            return
        }
        rightThumbstick.translatesAutoresizingMaskIntoConstraints = false
        viewToAddSubviews.insertSubview(rightThumbstick, belowSubview: aButton)
        
        if iPhone {
            constraints.portrait.append(contentsOf: [
                imageView.topAnchor.constraint(equalTo: viewToAddSubviews.safeAreaLayoutGuide.topAnchor,
                                               constant: 20.0),
                imageView.leadingAnchor.constraint(equalTo: viewToAddSubviews.safeAreaLayoutGuide.leadingAnchor,
                                                   constant: 20.0),
                imageView.trailingAnchor.constraint(equalTo: viewToAddSubviews.safeAreaLayoutGuide.trailingAnchor,
                                                    constant: -20.0),
                imageView.heightAnchor.constraint(equalTo: imageView.safeAreaLayoutGuide.widthAnchor,
                                                  multiplier: 3.0 / 5.0),
                
                secondaryImageView.topAnchor.constraint(equalTo: imageView.safeAreaLayoutGuide.bottomAnchor,
                                                        constant: 20.0),
                secondaryImageView.leadingAnchor.constraint(equalTo: imageView.safeAreaLayoutGuide.leadingAnchor,
                                                            constant: 20.0),
                secondaryImageView.trailingAnchor.constraint(equalTo: imageView.safeAreaLayoutGuide.trailingAnchor,
                                                             constant: -20.0),
                secondaryImageView.heightAnchor.constraint(equalTo: secondaryImageView.safeAreaLayoutGuide.widthAnchor,
                                                           multiplier: 3.0 / 4.0),
                
                settingsButton.bottomAnchor.constraint(equalTo: viewToAddSubviews.bottomAnchor,
                                                       constant: -20.0),
                settingsButton.centerXAnchor.constraint(equalTo: viewToAddSubviews.safeAreaLayoutGuide.centerXAnchor),
                
                selectButton.bottomAnchor.constraint(equalTo: viewToAddSubviews.bottomAnchor,
                                                     constant: -20.0),
                selectButton.trailingAnchor.constraint(equalTo: settingsButton.safeAreaLayoutGuide.leadingAnchor,
                                                       constant: -20.0),
                
                startButton.bottomAnchor.constraint(equalTo: viewToAddSubviews.bottomAnchor,
                                                    constant: -20.0),
                startButton.leadingAnchor.constraint(equalTo: settingsButton.safeAreaLayoutGuide.trailingAnchor,
                                                     constant: 20.0),
                
                bButton.bottomAnchor.constraint(equalTo: startButton.safeAreaLayoutGuide.topAnchor,
                                                    constant: -20.0),
                bButton.trailingAnchor.constraint(equalTo: aButton.safeAreaLayoutGuide.leadingAnchor),
                
                aButton.bottomAnchor.constraint(equalTo: bButton.safeAreaLayoutGuide.topAnchor),
                aButton.trailingAnchor.constraint(equalTo: viewToAddSubviews.safeAreaLayoutGuide.trailingAnchor,
                                                       constant: -20.0),
                
                xButton.bottomAnchor.constraint(equalTo: aButton.safeAreaLayoutGuide.topAnchor),
                xButton.trailingAnchor.constraint(equalTo: aButton.safeAreaLayoutGuide.leadingAnchor),
                
                yButton.bottomAnchor.constraint(equalTo: bButton.safeAreaLayoutGuide.topAnchor),
                yButton.trailingAnchor.constraint(equalTo: bButton.safeAreaLayoutGuide.leadingAnchor),
                
                upButton.leadingAnchor.constraint(equalTo: leftButton.safeAreaLayoutGuide.trailingAnchor),
                upButton.bottomAnchor.constraint(equalTo: leftButton.safeAreaLayoutGuide.topAnchor),
                
                leftButton.leadingAnchor.constraint(equalTo: viewToAddSubviews.safeAreaLayoutGuide.leadingAnchor,
                                                    constant: 20.0),
                leftButton.bottomAnchor.constraint(equalTo: downButton.safeAreaLayoutGuide.topAnchor),
                
                downButton.leadingAnchor.constraint(equalTo: leftButton.safeAreaLayoutGuide.trailingAnchor),
                downButton.bottomAnchor.constraint(equalTo: selectButton.safeAreaLayoutGuide.topAnchor,
                                                   constant: -20.0),
                
                rightButton.leadingAnchor.constraint(equalTo: downButton.safeAreaLayoutGuide.trailingAnchor),
                rightButton.bottomAnchor.constraint(equalTo: downButton.safeAreaLayoutGuide.topAnchor),
                
                lButton.leadingAnchor.constraint(equalTo: viewToAddSubviews.safeAreaLayoutGuide.leadingAnchor,
                                                  constant: 20.0),
                lButton.bottomAnchor.constraint(equalTo: upButton.safeAreaLayoutGuide.topAnchor,
                                                 constant: -20.0),
                
                zlButton.leadingAnchor.constraint(equalTo: lButton.safeAreaLayoutGuide.trailingAnchor,
                                                  constant: 20.0),
                zlButton.centerYAnchor.constraint(equalTo: lButton.safeAreaLayoutGuide.centerYAnchor),
                
                rButton.trailingAnchor.constraint(equalTo: viewToAddSubviews.safeAreaLayoutGuide.trailingAnchor,
                                                   constant: -20.0),
                rButton.bottomAnchor.constraint(equalTo: upButton.safeAreaLayoutGuide.topAnchor,
                                                 constant: -20.0),
                
                zrButton.trailingAnchor.constraint(equalTo: rButton.safeAreaLayoutGuide.leadingAnchor,
                                                   constant: -20.0),
                zrButton.centerYAnchor.constraint(equalTo: rButton.safeAreaLayoutGuide.centerYAnchor),
                
                leftThumbstick.topAnchor.constraint(equalTo: upButton.safeAreaLayoutGuide.topAnchor),
                leftThumbstick.leadingAnchor.constraint(equalTo: leftButton.safeAreaLayoutGuide.leadingAnchor),
                leftThumbstick.bottomAnchor.constraint(equalTo: downButton.safeAreaLayoutGuide.bottomAnchor),
                leftThumbstick.trailingAnchor.constraint(equalTo: rightButton.safeAreaLayoutGuide.trailingAnchor),
                
                rightThumbstick.topAnchor.constraint(equalTo: xButton.safeAreaLayoutGuide.topAnchor),
                rightThumbstick.leadingAnchor.constraint(equalTo: yButton.safeAreaLayoutGuide.leadingAnchor),
                rightThumbstick.bottomAnchor.constraint(equalTo: bButton.safeAreaLayoutGuide.bottomAnchor),
                rightThumbstick.trailingAnchor.constraint(equalTo: aButton.safeAreaLayoutGuide.trailingAnchor)
            ])
            
            constraints.landscape.append(contentsOf: [
                imageView.topAnchor.constraint(equalTo: viewToAddSubviews.topAnchor,
                                               constant: 20.0),
                imageView.centerXAnchor.constraint(equalTo: viewToAddSubviews.safeAreaLayoutGuide.centerXAnchor),
                imageView.bottomAnchor.constraint(equalTo: viewToAddSubviews.centerYAnchor,
                                                  constant: -10.0),
                imageView.widthAnchor.constraint(equalTo: imageView.heightAnchor,
                                                 multiplier: 5.0 / 3.0),
                
                secondaryImageView.topAnchor.constraint(equalTo: viewToAddSubviews.centerYAnchor,
                                                        constant: 10.0),
                secondaryImageView.centerXAnchor.constraint(equalTo: viewToAddSubviews.safeAreaLayoutGuide.centerXAnchor),
                secondaryImageView.bottomAnchor.constraint(equalTo: viewToAddSubviews.bottomAnchor,
                                                           constant: -20.0),
                secondaryImageView.widthAnchor.constraint(equalTo: secondaryImageView.heightAnchor,
                                                          multiplier: 4.0 / 3.0),
                
                startButton.bottomAnchor.constraint(equalTo: viewToAddSubviews.bottomAnchor,
                                                    constant: -20.0),
                startButton.leadingAnchor.constraint(equalTo: imageView.safeAreaLayoutGuide.trailingAnchor,
                                                     constant: 20.0),
                
                selectButton.bottomAnchor.constraint(equalTo: viewToAddSubviews.bottomAnchor,
                                                     constant: -20.0),
                selectButton.trailingAnchor.constraint(equalTo: imageView.safeAreaLayoutGuide.leadingAnchor,
                                                       constant: -20.0),
                
                settingsButton.bottomAnchor.constraint(equalTo: viewToAddSubviews.bottomAnchor,
                                                       constant: -20.0),
                settingsButton.leadingAnchor.constraint(equalTo: startButton.safeAreaLayoutGuide.trailingAnchor,
                                                        constant: 20.0),
                
                bButton.bottomAnchor.constraint(equalTo: startButton.safeAreaLayoutGuide.topAnchor,
                                                    constant: -20.0),
                bButton.trailingAnchor.constraint(equalTo: aButton.safeAreaLayoutGuide.leadingAnchor),
                
                aButton.bottomAnchor.constraint(equalTo: bButton.safeAreaLayoutGuide.topAnchor),
                aButton.trailingAnchor.constraint(equalTo: viewToAddSubviews.safeAreaLayoutGuide.trailingAnchor,
                                                       constant: -20.0),
                
                xButton.bottomAnchor.constraint(equalTo: aButton.safeAreaLayoutGuide.topAnchor),
                xButton.trailingAnchor.constraint(equalTo: aButton.safeAreaLayoutGuide.leadingAnchor),
                
                yButton.bottomAnchor.constraint(equalTo: bButton.safeAreaLayoutGuide.topAnchor),
                yButton.trailingAnchor.constraint(equalTo: bButton.safeAreaLayoutGuide.leadingAnchor),
                
                upButton.leadingAnchor.constraint(equalTo: leftButton.safeAreaLayoutGuide.trailingAnchor),
                upButton.bottomAnchor.constraint(equalTo: leftButton.safeAreaLayoutGuide.topAnchor),
                
                leftButton.leadingAnchor.constraint(equalTo: viewToAddSubviews.safeAreaLayoutGuide.leadingAnchor,
                                                    constant: 20.0),
                leftButton.bottomAnchor.constraint(equalTo: downButton.safeAreaLayoutGuide.topAnchor),
                
                downButton.leadingAnchor.constraint(equalTo: leftButton.safeAreaLayoutGuide.trailingAnchor),
                downButton.bottomAnchor.constraint(equalTo: selectButton.safeAreaLayoutGuide.topAnchor,
                                                   constant: -20.0),
                
                rightButton.leadingAnchor.constraint(equalTo: downButton.safeAreaLayoutGuide.trailingAnchor),
                rightButton.bottomAnchor.constraint(equalTo: downButton.safeAreaLayoutGuide.topAnchor),
                
                lButton.leadingAnchor.constraint(equalTo: viewToAddSubviews.safeAreaLayoutGuide.leadingAnchor,
                                                  constant: 20.0),
                lButton.bottomAnchor.constraint(equalTo: upButton.safeAreaLayoutGuide.topAnchor,
                                                 constant: -20.0),
                
                zlButton.leadingAnchor.constraint(equalTo: lButton.safeAreaLayoutGuide.trailingAnchor,
                                                  constant: 20.0),
                zlButton.centerYAnchor.constraint(equalTo: lButton.safeAreaLayoutGuide.centerYAnchor),
                
                rButton.trailingAnchor.constraint(equalTo: viewToAddSubviews.safeAreaLayoutGuide.trailingAnchor,
                                                   constant: -20.0),
                rButton.bottomAnchor.constraint(equalTo: upButton.safeAreaLayoutGuide.topAnchor,
                                                 constant: -20.0),
                
                zrButton.trailingAnchor.constraint(equalTo: rButton.safeAreaLayoutGuide.leadingAnchor,
                                                   constant: -20.0),
                zrButton.centerYAnchor.constraint(equalTo: rButton.safeAreaLayoutGuide.centerYAnchor),
                
                leftThumbstick.topAnchor.constraint(equalTo: upButton.safeAreaLayoutGuide.topAnchor),
                leftThumbstick.leadingAnchor.constraint(equalTo: leftButton.safeAreaLayoutGuide.leadingAnchor),
                leftThumbstick.bottomAnchor.constraint(equalTo: downButton.safeAreaLayoutGuide.bottomAnchor),
                leftThumbstick.trailingAnchor.constraint(equalTo: rightButton.safeAreaLayoutGuide.trailingAnchor),
                
                rightThumbstick.topAnchor.constraint(equalTo: xButton.safeAreaLayoutGuide.topAnchor),
                rightThumbstick.leadingAnchor.constraint(equalTo: yButton.safeAreaLayoutGuide.leadingAnchor),
                rightThumbstick.bottomAnchor.constraint(equalTo: bButton.safeAreaLayoutGuide.bottomAnchor),
                rightThumbstick.trailingAnchor.constraint(equalTo: aButton.safeAreaLayoutGuide.trailingAnchor)
            ])
        } else {
            constraints.portrait.append(contentsOf: [
                imageView.topAnchor.constraint(equalTo: viewToAddSubviews.safeAreaLayoutGuide.topAnchor,
                                               constant: 20.0),
                imageView.centerXAnchor.constraint(equalTo: viewToAddSubviews.safeAreaLayoutGuide.centerXAnchor),
                imageView.widthAnchor.constraint(equalTo: viewToAddSubviews.safeAreaLayoutGuide.widthAnchor,
                                                 multiplier: 2.0 / 3.0),
                imageView.heightAnchor.constraint(equalTo: imageView.safeAreaLayoutGuide.widthAnchor,
                                                  multiplier: 3.0 / 5.0),
                
                secondaryImageView.topAnchor.constraint(equalTo: imageView.safeAreaLayoutGuide.bottomAnchor,
                                                        constant: 20.0),
                secondaryImageView.leadingAnchor.constraint(equalTo: imageView.safeAreaLayoutGuide.leadingAnchor,
                                                            constant: 20.0),
                secondaryImageView.trailingAnchor.constraint(equalTo: imageView.safeAreaLayoutGuide.trailingAnchor,
                                                             constant: -20.0),
                secondaryImageView.heightAnchor.constraint(equalTo: secondaryImageView.safeAreaLayoutGuide.widthAnchor,
                                                           multiplier: 3.0 / 4.0),
                
                settingsButton.bottomAnchor.constraint(equalTo: viewToAddSubviews.bottomAnchor,
                                                       constant: -20.0),
                settingsButton.centerXAnchor.constraint(equalTo: viewToAddSubviews.safeAreaLayoutGuide.centerXAnchor),
                
                selectButton.bottomAnchor.constraint(equalTo: viewToAddSubviews.bottomAnchor,
                                                     constant: -20.0),
                selectButton.trailingAnchor.constraint(equalTo: settingsButton.safeAreaLayoutGuide.leadingAnchor,
                                                       constant: -20.0),
                
                startButton.bottomAnchor.constraint(equalTo: viewToAddSubviews.bottomAnchor,
                                                    constant: -20.0),
                startButton.leadingAnchor.constraint(equalTo: settingsButton.safeAreaLayoutGuide.trailingAnchor,
                                                     constant: 20.0),
                
                bButton.bottomAnchor.constraint(equalTo: startButton.safeAreaLayoutGuide.topAnchor,
                                                    constant: -20.0),
                bButton.trailingAnchor.constraint(equalTo: aButton.safeAreaLayoutGuide.leadingAnchor),
                
                aButton.bottomAnchor.constraint(equalTo: bButton.safeAreaLayoutGuide.topAnchor),
                aButton.trailingAnchor.constraint(equalTo: viewToAddSubviews.safeAreaLayoutGuide.trailingAnchor,
                                                       constant: -20.0),
                
                xButton.bottomAnchor.constraint(equalTo: aButton.safeAreaLayoutGuide.topAnchor),
                xButton.trailingAnchor.constraint(equalTo: aButton.safeAreaLayoutGuide.leadingAnchor),
                
                yButton.bottomAnchor.constraint(equalTo: bButton.safeAreaLayoutGuide.topAnchor),
                yButton.trailingAnchor.constraint(equalTo: bButton.safeAreaLayoutGuide.leadingAnchor),
                
                upButton.leadingAnchor.constraint(equalTo: leftButton.safeAreaLayoutGuide.trailingAnchor),
                upButton.bottomAnchor.constraint(equalTo: leftButton.safeAreaLayoutGuide.topAnchor),
                
                leftButton.leadingAnchor.constraint(equalTo: viewToAddSubviews.safeAreaLayoutGuide.leadingAnchor,
                                                    constant: 20.0),
                leftButton.bottomAnchor.constraint(equalTo: downButton.safeAreaLayoutGuide.topAnchor),
                
                downButton.leadingAnchor.constraint(equalTo: leftButton.safeAreaLayoutGuide.trailingAnchor),
                downButton.bottomAnchor.constraint(equalTo: selectButton.safeAreaLayoutGuide.topAnchor,
                                                   constant: -20.0),
                
                rightButton.leadingAnchor.constraint(equalTo: downButton.safeAreaLayoutGuide.trailingAnchor),
                rightButton.bottomAnchor.constraint(equalTo: downButton.safeAreaLayoutGuide.topAnchor),
                
                lButton.leadingAnchor.constraint(equalTo: viewToAddSubviews.safeAreaLayoutGuide.leadingAnchor,
                                                  constant: 20.0),
                lButton.bottomAnchor.constraint(equalTo: upButton.safeAreaLayoutGuide.topAnchor,
                                                 constant: -20.0),
                
                zlButton.leadingAnchor.constraint(equalTo: lButton.safeAreaLayoutGuide.trailingAnchor,
                                                  constant: 20.0),
                zlButton.centerYAnchor.constraint(equalTo: lButton.safeAreaLayoutGuide.centerYAnchor),
                
                rButton.trailingAnchor.constraint(equalTo: viewToAddSubviews.safeAreaLayoutGuide.trailingAnchor,
                                                   constant: -20.0),
                rButton.bottomAnchor.constraint(equalTo: upButton.safeAreaLayoutGuide.topAnchor,
                                                 constant: -20.0),
                
                zrButton.trailingAnchor.constraint(equalTo: rButton.safeAreaLayoutGuide.leadingAnchor,
                                                   constant: -20.0),
                zrButton.centerYAnchor.constraint(equalTo: rButton.safeAreaLayoutGuide.centerYAnchor),
                
                leftThumbstick.topAnchor.constraint(equalTo: upButton.safeAreaLayoutGuide.topAnchor),
                leftThumbstick.leadingAnchor.constraint(equalTo: leftButton.safeAreaLayoutGuide.leadingAnchor),
                leftThumbstick.bottomAnchor.constraint(equalTo: downButton.safeAreaLayoutGuide.bottomAnchor),
                leftThumbstick.trailingAnchor.constraint(equalTo: rightButton.safeAreaLayoutGuide.trailingAnchor),
                
                rightThumbstick.topAnchor.constraint(equalTo: xButton.safeAreaLayoutGuide.topAnchor),
                rightThumbstick.leadingAnchor.constraint(equalTo: yButton.safeAreaLayoutGuide.leadingAnchor),
                rightThumbstick.bottomAnchor.constraint(equalTo: bButton.safeAreaLayoutGuide.bottomAnchor),
                rightThumbstick.trailingAnchor.constraint(equalTo: aButton.safeAreaLayoutGuide.trailingAnchor)
            ])
            
            constraints.landscape.append(contentsOf: [
                imageView.topAnchor.constraint(equalTo: viewToAddSubviews.topAnchor,
                                               constant: 20.0),
                imageView.centerXAnchor.constraint(equalTo: viewToAddSubviews.safeAreaLayoutGuide.centerXAnchor),
                imageView.bottomAnchor.constraint(equalTo: viewToAddSubviews.centerYAnchor,
                                                  constant: -10.0),
                imageView.widthAnchor.constraint(equalTo: imageView.heightAnchor,
                                                 multiplier: 4.0 / 3.0),
                
                secondaryImageView.topAnchor.constraint(equalTo: viewToAddSubviews.centerYAnchor,
                                                        constant: 10.0),
                secondaryImageView.centerXAnchor.constraint(equalTo: viewToAddSubviews.safeAreaLayoutGuide.centerXAnchor),
                secondaryImageView.bottomAnchor.constraint(equalTo: viewToAddSubviews.bottomAnchor,
                                                           constant: -20.0),
                secondaryImageView.widthAnchor.constraint(equalTo: secondaryImageView.heightAnchor,
                                                          multiplier: 4.0 / 3.0),
                
                startButton.bottomAnchor.constraint(equalTo: viewToAddSubviews.bottomAnchor,
                                                    constant: -20.0),
                startButton.leadingAnchor.constraint(equalTo: imageView.safeAreaLayoutGuide.trailingAnchor,
                                                     constant: 20.0),
                
                selectButton.bottomAnchor.constraint(equalTo: viewToAddSubviews.bottomAnchor,
                                                     constant: -20.0),
                selectButton.trailingAnchor.constraint(equalTo: imageView.safeAreaLayoutGuide.leadingAnchor,
                                                       constant: -20.0),
                
                settingsButton.bottomAnchor.constraint(equalTo: viewToAddSubviews.bottomAnchor,
                                                       constant: -20.0),
                settingsButton.leadingAnchor.constraint(equalTo: startButton.safeAreaLayoutGuide.trailingAnchor,
                                                        constant: 20.0),
                
                bButton.bottomAnchor.constraint(equalTo: startButton.safeAreaLayoutGuide.topAnchor,
                                                    constant: -20.0),
                bButton.trailingAnchor.constraint(equalTo: aButton.safeAreaLayoutGuide.leadingAnchor),
                
                aButton.bottomAnchor.constraint(equalTo: bButton.safeAreaLayoutGuide.topAnchor),
                aButton.trailingAnchor.constraint(equalTo: viewToAddSubviews.safeAreaLayoutGuide.trailingAnchor,
                                                       constant: -20.0),
                
                xButton.bottomAnchor.constraint(equalTo: aButton.safeAreaLayoutGuide.topAnchor),
                xButton.trailingAnchor.constraint(equalTo: aButton.safeAreaLayoutGuide.leadingAnchor),
                
                yButton.bottomAnchor.constraint(equalTo: bButton.safeAreaLayoutGuide.topAnchor),
                yButton.trailingAnchor.constraint(equalTo: bButton.safeAreaLayoutGuide.leadingAnchor),
                
                upButton.leadingAnchor.constraint(equalTo: leftButton.safeAreaLayoutGuide.trailingAnchor),
                upButton.bottomAnchor.constraint(equalTo: leftButton.safeAreaLayoutGuide.topAnchor),
                
                leftButton.leadingAnchor.constraint(equalTo: viewToAddSubviews.safeAreaLayoutGuide.leadingAnchor,
                                                    constant: 20.0),
                leftButton.bottomAnchor.constraint(equalTo: downButton.safeAreaLayoutGuide.topAnchor),
                
                downButton.leadingAnchor.constraint(equalTo: leftButton.safeAreaLayoutGuide.trailingAnchor),
                downButton.bottomAnchor.constraint(equalTo: selectButton.safeAreaLayoutGuide.topAnchor,
                                                   constant: -20.0),
                
                rightButton.leadingAnchor.constraint(equalTo: downButton.safeAreaLayoutGuide.trailingAnchor),
                rightButton.bottomAnchor.constraint(equalTo: downButton.safeAreaLayoutGuide.topAnchor),
                
                lButton.leadingAnchor.constraint(equalTo: viewToAddSubviews.safeAreaLayoutGuide.leadingAnchor,
                                                  constant: 20.0),
                lButton.bottomAnchor.constraint(equalTo: upButton.safeAreaLayoutGuide.topAnchor,
                                                 constant: -20.0),
                
                zlButton.leadingAnchor.constraint(equalTo: lButton.safeAreaLayoutGuide.trailingAnchor,
                                                  constant: 20.0),
                zlButton.centerYAnchor.constraint(equalTo: lButton.safeAreaLayoutGuide.centerYAnchor),
                
                rButton.trailingAnchor.constraint(equalTo: viewToAddSubviews.safeAreaLayoutGuide.trailingAnchor,
                                                   constant: -20.0),
                rButton.bottomAnchor.constraint(equalTo: upButton.safeAreaLayoutGuide.topAnchor,
                                                 constant: -20.0),
                
                zrButton.trailingAnchor.constraint(equalTo: rButton.safeAreaLayoutGuide.leadingAnchor,
                                                   constant: -20.0),
                zrButton.centerYAnchor.constraint(equalTo: rButton.safeAreaLayoutGuide.centerYAnchor),
                
                leftThumbstick.topAnchor.constraint(equalTo: upButton.safeAreaLayoutGuide.topAnchor),
                leftThumbstick.leadingAnchor.constraint(equalTo: leftButton.safeAreaLayoutGuide.leadingAnchor),
                leftThumbstick.bottomAnchor.constraint(equalTo: downButton.safeAreaLayoutGuide.bottomAnchor),
                leftThumbstick.trailingAnchor.constraint(equalTo: rightButton.safeAreaLayoutGuide.trailingAnchor),
                
                rightThumbstick.topAnchor.constraint(equalTo: xButton.safeAreaLayoutGuide.topAnchor),
                rightThumbstick.leadingAnchor.constraint(equalTo: yButton.safeAreaLayoutGuide.leadingAnchor),
                rightThumbstick.bottomAnchor.constraint(equalTo: bButton.safeAreaLayoutGuide.bottomAnchor),
                rightThumbstick.trailingAnchor.constraint(equalTo: aButton.safeAreaLayoutGuide.trailingAnchor)
            ])
        }
        
        switch interfaceOrientation() {
        case .portrait:
            view.addConstraints(constraints.portrait)
        case .landscapeLeft, .landscapeRight:
            view.addConstraints(constraints.landscape)
        default:
            break
        }
        
        applicationModel.cytrus.allocate()
        applicationModel.cytrus.diskCacheCallback { stage, current, total in
            print(stage, current, total)
        }
        
        
        Task {
            await GCController.startWirelessControllerDiscovery()
        }
        
        NotificationCenter.default.addObserver(forName: .applicationStateDidChange, object: nil, queue: .current) { notification in
            guard let applicationState: ApplicationState = notification.object as? ApplicationState else {
                return
            }
            
            self.applicationModel.cytrus.pause(applicationState.shouldPause)
        }
        
        NotificationCenter.default.addObserver(forName: .init("openKeyboard"), object: nil, queue: .main) { notification in
            guard let config = notification.object as? KeyboardConfig else {
                return
            }
            
            let alertController = UIAlertController(title: "", message: nil, preferredStyle: .alert)
            
            let cancelAction: UIAlertAction = .init(title: .localized(for: .dismiss), style: .cancel) { _ in
                NotificationCenter.default.post(name: .init("closeKeyboard"), object: nil, userInfo: [
                    "buttonPressed" : 0,
                    "keyboardText" : ""
                ])
            }
            
            let okayButton: UIAlertAction = .init(title: "Okay", style: .default) { _ in
                guard let textFields = alertController.textFields, let textField = textFields.first else {
                    return
                }
                
                NotificationCenter.default.post(name: .init("closeKeyboard"), object: nil, userInfo: [
                    "buttonPressed" : 0,
                    "keyboardText" : textField.text ?? ""
                ])
            }
            
            switch config.buttonConfig {
            case .single:
                alertController.addAction(okayButton)
            case .dual:
                alertController.addAction(cancelAction)
                alertController.addAction(okayButton)
            case .triple:
                break
            case .none:
                break
            @unknown default:
                break
            }
            
            
            alertController.addTextField()
            self.present(alertController, animated: true)
        }
        
        NotificationCenter.default.addObserver(forName: .GCControllerDidConnect, object: nil, queue: .current) { notification in
            guard let controller: GCController = notification.object as? GCController,
                let extendedGamepad: GCExtendedGamepad = controller.extendedGamepad else {
                return
            }
            
            self.view.subviews.filter { subview in
                subview.isKind(of: UIButton.classForCoder()) && subview != settingsButton
            }.forEach { button in
                UIView.animate(withDuration: 1 / 3) {
                    button.alpha = 1 / 3
                }
            }
            
            extendedGamepad.buttonA.pressedChangedHandler = { element, value, pressed in
                self.applicationModel.cytrus.button(button: .a, player: controller.playerIndex.rawValue, pressed: pressed)
            }
            
            extendedGamepad.buttonB.pressedChangedHandler = { element, value, pressed in
                self.applicationModel.cytrus.button(button: .b, player: controller.playerIndex.rawValue, pressed: pressed)
            }
            
            extendedGamepad.buttonX.pressedChangedHandler = { element, value, pressed in
                self.applicationModel.cytrus.button(button: .x, player: controller.playerIndex.rawValue, pressed: pressed)
            }
            
            extendedGamepad.buttonY.pressedChangedHandler = { element, value, pressed in
                self.applicationModel.cytrus.button(button: .y, player: controller.playerIndex.rawValue, pressed: pressed)
            }
            
            extendedGamepad.dpad.up.pressedChangedHandler = { element, value, pressed in
                self.applicationModel.cytrus.button(button: .up, player: controller.playerIndex.rawValue, pressed: pressed)
            }
            
            extendedGamepad.dpad.down.pressedChangedHandler = { element, value, pressed in
                self.applicationModel.cytrus.button(button: .down, player: controller.playerIndex.rawValue, pressed: pressed)
            }
            
            extendedGamepad.dpad.left.pressedChangedHandler = { element, value, pressed in
                self.applicationModel.cytrus.button(button: .left, player: controller.playerIndex.rawValue, pressed: pressed)
            }
            
            extendedGamepad.dpad.right.pressedChangedHandler = { element, value, pressed in
                self.applicationModel.cytrus.button(button: .right, player: controller.playerIndex.rawValue, pressed: pressed)
            }
            
            extendedGamepad.leftShoulder.pressedChangedHandler = { element, value, pressed in
                self.applicationModel.cytrus.button(button: .l, player: controller.playerIndex.rawValue, pressed: pressed)
            }
            
            extendedGamepad.leftTrigger.pressedChangedHandler = { element, value, pressed in
                self.applicationModel.cytrus.button(button: .zl, player: controller.playerIndex.rawValue, pressed: pressed)
            }
            
            extendedGamepad.rightShoulder.pressedChangedHandler = { element, value, pressed in
                self.applicationModel.cytrus.button(button: .r, player: controller.playerIndex.rawValue, pressed: pressed)
            }
            
            extendedGamepad.rightTrigger.pressedChangedHandler = { element, value, pressed in
                self.applicationModel.cytrus.button(button: .zr, player: controller.playerIndex.rawValue, pressed: pressed)
            }
            
            if let buttonOptions = extendedGamepad.buttonOptions {
                buttonOptions.pressedChangedHandler = { element, value, pressed in
                    self.applicationModel.cytrus.button(button: .select, player: controller.playerIndex.rawValue, pressed: pressed)
                }
            }
            
            extendedGamepad.buttonMenu.pressedChangedHandler = { element, value, pressed in
                self.applicationModel.cytrus.button(button: .start, player: controller.playerIndex.rawValue, pressed: pressed)
            }
        }
        
        NotificationCenter.default.addObserver(forName: .GCControllerDidDisconnect, object: nil, queue: .current) { notification in
            self.view.subviews.filter { subview in
                subview.isKind(of: UIButton.classForCoder()) && subview != settingsButton
            }.forEach { button in
                UIView.animate(withDuration: 1 / 3) {
                    button.alpha = 1
                }
            }
        }
    }
    
    override var prefersHomeIndicatorAutoHidden: Bool { true }
    override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
        .portrait
    }

    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
        guard let imageView, let secondaryImageView else {
            return
        }
        
        if !applicationModel.cytrus.running() || !applicationModel.cytrus.stopped() {
            if let layer = imageView.layer as? CAMetalLayer {
                applicationModel.cytrus.initialize(layer, layer.frame.size)
            }
            
            if let layer = secondaryImageView.layer as? CAMetalLayer {
                applicationModel.cytrus.initialize(layer, layer.frame.size, true)
            }
            
#if !targetEnvironment(simulator)
            if let game {
                DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
                    Thread.setThreadPriority(1)
                    Thread.detachNewThread {
                        self.applicationModel.cytrus.insert(game.details.url) {
                            self.applicationModel.cytrus.orientationChange(with: self.interfaceOrientation(), using: imageView)
                            self.applicationModel.cytrus.orientationChange(with: self.interfaceOrientation(), using: secondaryImageView, true)
                        }
                    }
                }
            } else {
                DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
                    Thread.setThreadPriority(1)
                    Thread.detachNewThread {
                        let url: URL = self.applicationModel.cytrus.bootHome(3)
                        print(url.path)
                        self.applicationModel.cytrus.insert(url) {
                            self.applicationModel.cytrus.orientationChange(with: self.interfaceOrientation(), using: imageView)
                            self.applicationModel.cytrus.orientationChange(with: self.interfaceOrientation(), using: secondaryImageView, true)
                        }
                    }
                }
            }
#endif
        }
    }

    override func viewDidDisappear(_ animated: Bool) {
        super.viewDidDisappear(animated)
        applicationModel.cytrus.deallocate()
        applicationModel.cytrus.deinitialize()
    }
    
    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        if let game {
            game.extras.update()
        }
        
        GCController.stopWirelessControllerDiscovery()
        NotificationCenter.default.removeObserver(self)
    }
    
    override func viewWillTransition(to size: CGSize, with coordinator: any UIViewControllerTransitionCoordinator) {
        super.viewWillTransition(to: size, with: coordinator)
        coordinator.animate { context in } completion: { context in
            switch self.interfaceOrientation() {
            case .portrait:
                self.view.removeConstraints(self.constraints.landscape)
                self.view.addConstraints(self.constraints.portrait)
            case .landscapeLeft, .landscapeRight:
                self.view.removeConstraints(self.constraints.portrait)
                self.view.addConstraints(self.constraints.landscape)
            default:
                break
            }
            
            self.view.setNeedsUpdateConstraints()
        }
    }
}

extension CytrusController {
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        super.touchesBegan(touches, with: event)
        guard let touch = touches.first else {
            return
        }
        
        if let secondaryImageView {
            applicationModel.cytrus.touchBegan(at: touch.location(in: secondaryImageView))
        }
    }
    
    override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
        super.touchesEnded(touches, with: event)
        applicationModel.cytrus.touchEnded()
    }
    
    override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
        super.touchesMoved(touches, with: event)
        guard let touch = touches.first else {
            return
        }
        
        if let secondaryImageView {
            applicationModel.cytrus.touchMoved(at: touch.location(in: secondaryImageView))
        }
    }
}

extension CytrusController : LatestControllerThumbstickDelegate {
    func touchBegan(with thumbstick: LatestThumbstick, position: (x: Float, y: Float), playerIndex: GCControllerPlayerIndex) {
        
    }
    
    func touchEnded(with thumbstick: LatestThumbstick, position: (x: Float, y: Float), playerIndex: GCControllerPlayerIndex) {
        
    }
    
    func touchMoved(with thumbstick: LatestThumbstick, position: (x: Float, y: Float), playerIndex: GCControllerPlayerIndex) {
        
    }
    
    func touchBegan(with thumbstick: LatestThumbstick, position: (x: Float, y: Float), playerIndex: GCControllerPlayerIndex) async {
        applicationModel.cytrus.thumbstickMoved(thumbstick == .left ? .circlePad : .cStick, position.x, position.y)
    }
    
    func touchEnded(with thumbstick: LatestThumbstick, position: (x: Float, y: Float), playerIndex: GCControllerPlayerIndex) async {
        applicationModel.cytrus.thumbstickMoved(thumbstick == .left ? .circlePad : .cStick, position.x, position.y)
    }
    
    func touchMoved(with thumbstick: LatestThumbstick, position: (x: Float, y: Float), playerIndex: GCControllerPlayerIndex) async {
        applicationModel.cytrus.thumbstickMoved(thumbstick == .left ? .circlePad : .cStick, position.x, position.y)
    }
}

extension CytrusController {
    @objc func image(_ image: UIImage, didFinishSavingWithError error: NSError?, contextInfo: UnsafeRawPointer) {
        if #available(iOS 17.5, *), let settingsButton {
            UINotificationFeedbackGenerator(view: settingsButton).notificationOccurred(error == nil ? .success : .error)
        } else {
            UINotificationFeedbackGenerator().notificationOccurred(error == nil ? .success : .error)
        }
    }
}

extension CytrusController : UIDocumentPickerDelegate {
    func documentPickerWasCancelled(_ controller: UIDocumentPickerViewController) {
        controller.dismiss(animated: true)
    }
    
    func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
        guard let url = urls.first else {
            return
        }
        
        let result: Bool = self.applicationModel.cytrus.insertAmiibo(url)
        UINotificationFeedbackGenerator().notificationOccurred(result ? .success : .error)
    }
}