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/MandarineController.swift
2 views
//
//  MandarineController.swift
//  Folium
//
//  Created by Jarrod Norwell on 28/11/2025.
//

import CombinedCores
import GameController
import UIKit

class MandarineController : UIViewController {
    var imageView: UIImageView? = nil,
        secondaryImageView: UIImageView? = 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 crossButton: UIButton? = nil,
        circleButton: UIButton? = nil,
        triangleButton: UIButton? = nil,
        squareButton: UIButton? = nil
    
    var l1Button: UIButton? = nil,
        r1Button: UIButton? = nil,
        l2Button: UIButton? = nil,
        r2Button: UIButton? = nil
    
    var portraitBottomConstraint: NSLayoutConstraint? = nil
    var landscapeLeftConstraint: NSLayoutConstraint? = nil,
        landscapeRightConstraint: NSLayoutConstraint? = nil
    var constraints: (portrait: [NSLayoutConstraint], landscape: [NSLayoutConstraint]) = ([], [])
    
    var game: NewMandarineGame
    var mandarineCore: CombinedCores.MandarineCore
    init(_ game: NewMandarineGame, _ mandarineCore: CombinedCores.MandarineCore) {
        self.game = game
        self.mandarineCore = mandarineCore
        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)
        
        secondaryImageView = UIImageView()
        guard let secondaryImageView else {
            return
        }
        secondaryImageView.translatesAutoresizingMaskIntoConstraints = false
        secondaryImageView.backgroundColor = .label
        view.addSubview(secondaryImageView)
        
        visualEffectView = UIVisualEffectView(effect: UIBlurEffect(style: .systemThickMaterial))
        guard let visualEffectView else {
            return
        }
        visualEffectView.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(visualEffectView)
        
        imageView = UIImageView()
        guard let imageView else {
            return
        }
        imageView.translatesAutoresizingMaskIntoConstraints = false
        imageView.backgroundColor = .label
        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
        }
        visualEffectView.contentView.addSubview(imageView)
        
        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
                                                            Task {
                                                                await self.mandarineCore.stop()
                                                                self.dismiss(animated: true)
                                                            }
                                                        }))
                                                        self.present(alertController, animated: true)
                                                    },
                                                    UIAction(title: await self.mandarineCore.paused ? "Resume" : "Pause",
                                                             image: await self.mandarineCore.paused ? UIImage(systemName: "play") : UIImage(systemName: "pause")) { _ in
                                                                 Task {
                                                                     if await self.mandarineCore.paused {
                                                                         await self.mandarineCore.unpause()
                                                                     } else {
                                                                         await self.mandarineCore.pause()
                                                                     }
                                                                 }
                                                             }
                                                ])
                                            }
                                        }
                                    ]),
                                    UIMenu(options: .displayInline, children: [
                                        UIMenu(title: "Delete State", image: UIImage(systemName: "minus.circle"), preferredElementSize: .small, children: [
                                            UIDeferredMenuElement.uncached { completion in
                                                guard let documentDirectory: URL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else {
                                                    completion([])
                                                    return
                                                }
                                                
                                                let statesDirectory: URL = documentDirectory.appending(component: "Mandarine").appending(component: "states")
                                                
                                                let stateNumbers: [Int] = [0, 1, 2]
                                                
                                                let elements: [UIAction] = stateNumbers.map { state in
                                                    let url: URL = statesDirectory.appending(component: "\(self.game.details.name)_\(state).state")
                                                    let fileExists: Bool = FileManager.default.fileExists(atPath: url.path)
                                                    
                                                    return .init(image: UIImage(systemName: "\(state + 1).circle"), attributes: fileExists ? .destructive : .disabled) { _ in
                                                        Task {
                                                            try FileManager.default.removeItem(at: url)
                                                        }
                                                    }
                                                }
                                                
                                                completion(elements)
                                            }
                                        ]),
                                        UIMenu(title: "Save State", image: .init(systemName: "arrow.down.circle"), preferredElementSize: .small, children: [
                                            UIDeferredMenuElement.uncached { completion in
                                                guard let documentDirectory: URL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else {
                                                    completion([])
                                                    return
                                                }
                                                
                                                let statesDirectory: URL = documentDirectory.appending(component: "Mandarine").appending(component: "states")
                                                
                                                let stateNumbers: [Int] = [0, 1, 2]
                                                
                                                let elements: [UIAction] = stateNumbers.map { state in
                                                    let url: URL = statesDirectory.appending(component: "\(self.game.details.name)_\(state).state")
                                                    let fileExists: Bool = FileManager.default.fileExists(atPath: url.path)
                                                    
                                                    return .init(image: UIImage(systemName: "\(state + 1).circle")) { _ in
                                                        if fileExists {
                                                            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
                                                                Task {
                                                                    await self.mandarineCore.save(state: url)
                                                                }
                                                            }))
                                                            self.present(alertController, animated: true)
                                                        } else {
                                                            Task {
                                                                await self.mandarineCore.save(state: url)
                                                            }
                                                        }
                                                    }
                                                }
                                                
                                                completion(elements)
                                            }
                                        ]),
                                        UIMenu(title: "Load State", image: .init(systemName: "arrow.up.circle"), preferredElementSize: .small, children: [
                                            UIDeferredMenuElement.uncached { completion in
                                                guard let documentDirectory: URL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else {
                                                    completion([])
                                                    return
                                                }
                                                
                                                let statesDirectory: URL = documentDirectory.appending(component: "Mandarine").appending(component: "states")
                                                
                                                let stateNumbers: [Int] = [0, 1, 2]
                                                
                                                let elements: [UIAction] = stateNumbers.map { state in
                                                    let url: URL = statesDirectory.appending(component: "\(self.game.details.name)_\(state).state")
                                                    let fileExists: Bool = FileManager.default.fileExists(atPath: url.path)
                                                    
                                                    return .init(image: UIImage(systemName: "\(state + 1).circle"), attributes: fileExists ? [] : .disabled) { _ in
                                                        Task {
                                                            await self.mandarineCore.load(state: url)
                                                        }
                                                    }
                                                }
                                                
                                                completion(elements)
                                            }
                                        ]),
                                        UIMenu(options: .displayInline, children: [
                                            UIAction(title: "Capture Still Image", image: UIImage(systemName: "photo.badge.arrow.down")) { _ in
                                                Task {
                                                    await self.mandarineCore.pause()
                                                    
                                                    guard let imageView: UIImageView = self.imageView, let image: UIImage = imageView.image else {
                                                        return
                                                    }
                                                    
                                                    UIImageWriteToSavedPhotosAlbum(image, self, #selector(self.image(_:didFinishSavingWithError:contextInfo:)), nil)
                                                    
                                                    await self.mandarineCore.unpause()
                                                }
                                            }
                                        ])
                                    ])
                                 ]))
        guard let settingsButton else {
            return
        }
        visualEffectView.contentView.addSubview(settingsButton)
        
        let selectConfiguration: UIButton.Configuration = .configuration(.medium, .capsule, UIImage(systemName: "minus"), .medium)
        selectButton = .button(with: selectConfiguration,
                               actions: ({ _ in
            await self.mandarineCore.press(button: .select)
        }, { _ in
            await self.mandarineCore.release(button: .select)
        }))
        guard let selectButton else {
            return
        }
        visualEffectView.contentView.addSubview(selectButton)
        
        let startConfiguration: UIButton.Configuration = .configuration(.medium, .capsule, UIImage(systemName: "plus"), .medium)
        startButton = .button(with: startConfiguration,
                              actions: ({ _ in
            await self.mandarineCore.press(button: .start)
        }, { _ in
            await self.mandarineCore.release(button: .start)
        }))
        guard let startButton else {
            return
        }
        visualEffectView.contentView.addSubview(startButton)
        
        let upConfiguration: UIButton.Configuration = .configuration(.large, .capsule, UIImage(systemName: "arrow.up"))
        upButton = .button(with: upConfiguration,
                           actions: ({ _ in
            await self.mandarineCore.press(button: .up)
        }, { _ in
            await self.mandarineCore.release(button: .up)
        }))
        guard let upButton else {
            return
        }
        visualEffectView.contentView.addSubview(upButton)
        
        let downConfiguration: UIButton.Configuration = .configuration(.large, .capsule, UIImage(systemName: "arrow.down"))
        downButton = .button(with: downConfiguration,
                             actions: ({ _ in
            await self.mandarineCore.press(button: .down)
        }, { _ in
            await self.mandarineCore.release(button: .down)
        }))
        guard let downButton else {
            return
        }
        visualEffectView.contentView.addSubview(downButton)
        
        let leftConfiguration: UIButton.Configuration = .configuration(.large, .capsule, UIImage(systemName: "arrow.left"))
        leftButton = .button(with: leftConfiguration,
                             actions: ({ _ in
            await self.mandarineCore.press(button: .left)
        }, { _ in
            await self.mandarineCore.release(button: .left)
        }))
        guard let leftButton else {
            return
        }
        visualEffectView.contentView.addSubview(leftButton)
        
        let rightConfiguration: UIButton.Configuration = .configuration(.large, .capsule, UIImage(systemName: "arrow.right"))
        rightButton = .button(with: rightConfiguration,
                              actions: ({ _ in
            await self.mandarineCore.press(button: .right)
        }, { _ in
            await self.mandarineCore.release(button: .right)
        }))
        guard let rightButton else {
            return
        }
        visualEffectView.contentView.addSubview(rightButton)
        
        let crossConfiguration: UIButton.Configuration = .configuration(.large, .capsule, UIImage(systemName: "xmark"))
        crossButton = .button(with: crossConfiguration,
                              actions: ({ _ in
            await self.mandarineCore.press(button: .cross)
        }, { _ in
            await self.mandarineCore.release(button: .cross)
        }))
        guard let crossButton else {
            return
        }
        visualEffectView.contentView.addSubview(crossButton)
        
        let circleConfiguration: UIButton.Configuration = .configuration(.large, .capsule, UIImage(systemName: "circle"))
        circleButton = .button(with: circleConfiguration,
                               actions: ({ _ in
            await self.mandarineCore.press(button: .circle)
        }, { _ in
            await self.mandarineCore.release(button: .circle)
        }))
        guard let circleButton else {
            return
        }
        visualEffectView.contentView.addSubview(circleButton)
        
        let triangleConfiguration: UIButton.Configuration = .configuration(.large, .capsule, UIImage(systemName: "triangle"))
        triangleButton = .button(with: triangleConfiguration,
                                 actions: ({ _ in
            await self.mandarineCore.press(button: .triangle)
        }, { _ in
            await self.mandarineCore.release(button: .triangle)
        }))
        guard let triangleButton else {
            return
        }
        visualEffectView.contentView.addSubview(triangleButton)
        
        let squareConfiguration: UIButton.Configuration = .configuration(.large, .capsule, UIImage(systemName: "square"))
        squareButton = .button(with: squareConfiguration,
                               actions: ({ _ in
            await self.mandarineCore.press(button: .square)
        }, { _ in
            await self.mandarineCore.release(button: .square)
        }))
        guard let squareButton else {
            return
        }
        visualEffectView.contentView.addSubview(squareButton)
        
        let l1Configuration: UIButton.Configuration = .configuration(.large, .capsule, UIImage(systemName: "left"))
        l1Button = .button(with: l1Configuration,
                           actions: ({ _ in
            await self.mandarineCore.press(button: .l1)
        }, { _ in
            await self.mandarineCore.release(button: .l1)
        }))
        guard let l1Button else {
            return
        }
        visualEffectView.contentView.addSubview(l1Button)
        
        let l2Configuration: UIButton.Configuration = .configuration(.medium, .capsule, UIImage(systemName: "circle"), .medium)
        l2Button = .button(with: l2Configuration,
                           actions: ({ _ in
            await self.mandarineCore.press(button: .l2)
        }, { _ in
            await self.mandarineCore.release(button: .l2)
        }))
        guard let l2Button else {
            return
        }
        visualEffectView.contentView.addSubview(l2Button)
        
        let r1Configuration: UIButton.Configuration = .configuration(.large, .capsule, UIImage(systemName: "right"))
        r1Button = .button(with: r1Configuration,
                           actions: ({ _ in
            await self.mandarineCore.press(button: .r1)
        }, { _ in
            await self.mandarineCore.release(button: .r1)
        }))
        guard let r1Button else {
            return
        }
        visualEffectView.contentView.addSubview(r1Button)
        
        let r2Configuration: UIButton.Configuration = .configuration(.medium, .capsule, UIImage(systemName: "circle"), .medium)
        r2Button = .button(with: r2Configuration,
                           actions: ({ _ in
            await self.mandarineCore.press(button: .r2)
        }, { _ in
            await self.mandarineCore.release(button: .r2)
        }))
        guard let r2Button else {
            return
        }
        visualEffectView.contentView.addSubview(r2Button)
        
        if iPhone {
            portraitBottomConstraint = secondaryImageView.bottomAnchor.constraint(equalTo: imageView.safeAreaLayoutGuide.bottomAnchor)
            guard let portraitBottomConstraint else {
                return
            }
            
            constraints.portrait.append(contentsOf: [
                secondaryImageView.topAnchor.constraint(equalTo: view.topAnchor),
                secondaryImageView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
                secondaryImageView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
                portraitBottomConstraint,
                
                visualEffectView.topAnchor.constraint(equalTo: view.topAnchor),
                visualEffectView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
                visualEffectView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
                visualEffectView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
                
                imageView.topAnchor.constraint(equalTo: visualEffectView.contentView.safeAreaLayoutGuide.topAnchor,
                                               constant: 20.0),
                imageView.leadingAnchor.constraint(equalTo: visualEffectView.contentView.safeAreaLayoutGuide.leadingAnchor,
                                                   constant: 20.0),
                imageView.trailingAnchor.constraint(equalTo: visualEffectView.contentView.safeAreaLayoutGuide.trailingAnchor,
                                                    constant: -20.0),
                imageView.heightAnchor.constraint(equalTo: imageView.safeAreaLayoutGuide.widthAnchor,
                                                  multiplier: 3.0 / 4.0),
                
                settingsButton.bottomAnchor.constraint(equalTo: visualEffectView.contentView.bottomAnchor,
                                                       constant: -20.0),
                settingsButton.centerXAnchor.constraint(equalTo: visualEffectView.contentView.safeAreaLayoutGuide.centerXAnchor),
                
                selectButton.bottomAnchor.constraint(equalTo: visualEffectView.contentView.bottomAnchor,
                                                     constant: -20.0),
                selectButton.trailingAnchor.constraint(equalTo: settingsButton.safeAreaLayoutGuide.leadingAnchor,
                                                       constant: -20.0),
                
                startButton.bottomAnchor.constraint(equalTo: visualEffectView.contentView.bottomAnchor,
                                                    constant: -20.0),
                startButton.leadingAnchor.constraint(equalTo: settingsButton.safeAreaLayoutGuide.trailingAnchor,
                                                     constant: 20.0),
                
                crossButton.bottomAnchor.constraint(equalTo: startButton.safeAreaLayoutGuide.topAnchor,
                                                    constant: -20),
                crossButton.trailingAnchor.constraint(equalTo: circleButton.safeAreaLayoutGuide.leadingAnchor),
                
                circleButton.bottomAnchor.constraint(equalTo: crossButton.safeAreaLayoutGuide.topAnchor),
                circleButton.trailingAnchor.constraint(equalTo: visualEffectView.contentView.safeAreaLayoutGuide.trailingAnchor,
                                                       constant: -20),
                
                triangleButton.bottomAnchor.constraint(equalTo: circleButton.safeAreaLayoutGuide.topAnchor),
                triangleButton.trailingAnchor.constraint(equalTo: circleButton.safeAreaLayoutGuide.leadingAnchor),
                
                squareButton.bottomAnchor.constraint(equalTo: crossButton.safeAreaLayoutGuide.topAnchor),
                squareButton.trailingAnchor.constraint(equalTo: crossButton.safeAreaLayoutGuide.leadingAnchor),
                
                upButton.leadingAnchor.constraint(equalTo: leftButton.safeAreaLayoutGuide.trailingAnchor),
                upButton.bottomAnchor.constraint(equalTo: leftButton.safeAreaLayoutGuide.topAnchor),
                
                leftButton.leadingAnchor.constraint(equalTo: visualEffectView.contentView.safeAreaLayoutGuide.leadingAnchor,
                                                    constant: 20),
                leftButton.bottomAnchor.constraint(equalTo: downButton.safeAreaLayoutGuide.topAnchor),
                
                downButton.leadingAnchor.constraint(equalTo: leftButton.safeAreaLayoutGuide.trailingAnchor),
                downButton.bottomAnchor.constraint(equalTo: selectButton.safeAreaLayoutGuide.topAnchor,
                                                   constant: -20),
                
                rightButton.leadingAnchor.constraint(equalTo: downButton.safeAreaLayoutGuide.trailingAnchor),
                rightButton.bottomAnchor.constraint(equalTo: downButton.safeAreaLayoutGuide.topAnchor),
                
                l1Button.leadingAnchor.constraint(equalTo: visualEffectView.contentView.safeAreaLayoutGuide.leadingAnchor,
                                                  constant: 20),
                l1Button.bottomAnchor.constraint(equalTo: upButton.safeAreaLayoutGuide.topAnchor,
                                                 constant: -20),
                
                l2Button.leadingAnchor.constraint(equalTo: l1Button.safeAreaLayoutGuide.trailingAnchor,
                                                  constant: 20),
                l2Button.centerYAnchor.constraint(equalTo: l1Button.safeAreaLayoutGuide.centerYAnchor),
                
                r1Button.trailingAnchor.constraint(equalTo: visualEffectView.contentView.safeAreaLayoutGuide.trailingAnchor,
                                                   constant: -20),
                r1Button.bottomAnchor.constraint(equalTo: upButton.safeAreaLayoutGuide.topAnchor,
                                                 constant: -20),
                
                r2Button.trailingAnchor.constraint(equalTo: r1Button.safeAreaLayoutGuide.leadingAnchor,
                                                   constant: -20),
                r2Button.centerYAnchor.constraint(equalTo: r1Button.safeAreaLayoutGuide.centerYAnchor)
            ])
            
            landscapeLeftConstraint = secondaryImageView.leadingAnchor.constraint(equalTo: imageView.safeAreaLayoutGuide.leadingAnchor)
            landscapeRightConstraint = secondaryImageView.trailingAnchor.constraint(equalTo: imageView.safeAreaLayoutGuide.trailingAnchor)
            guard let landscapeLeftConstraint, let landscapeRightConstraint else {
                return
            }
            
            constraints.landscape.append(contentsOf: [
                secondaryImageView.topAnchor.constraint(equalTo: view.topAnchor),
                secondaryImageView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
                landscapeLeftConstraint,
                landscapeRightConstraint,
                
                visualEffectView.topAnchor.constraint(equalTo: view.topAnchor),
                visualEffectView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
                visualEffectView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
                visualEffectView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
                
                startButton.bottomAnchor.constraint(equalTo: visualEffectView.contentView.bottomAnchor,
                                                    constant: -20.0),
                startButton.leadingAnchor.constraint(equalTo: imageView.safeAreaLayoutGuide.trailingAnchor,
                                                     constant: 20),
                
                selectButton.bottomAnchor.constraint(equalTo: visualEffectView.contentView.bottomAnchor,
                                                     constant: -20.0),
                selectButton.trailingAnchor.constraint(equalTo: imageView.safeAreaLayoutGuide.leadingAnchor,
                                                       constant: -20.0),
                
                settingsButton.bottomAnchor.constraint(equalTo: visualEffectView.contentView.bottomAnchor,
                                                       constant: -20.0),
                settingsButton.leadingAnchor.constraint(equalTo: startButton.safeAreaLayoutGuide.trailingAnchor,
                                                        constant: 20.0),
                
                imageView.topAnchor.constraint(equalTo: visualEffectView.contentView.safeAreaLayoutGuide.topAnchor,
                                               constant: 20.0),
                imageView.bottomAnchor.constraint(equalTo: visualEffectView.contentView.bottomAnchor,
                                                  constant: -20.0),
                imageView.centerXAnchor.constraint(equalTo: visualEffectView.contentView.safeAreaLayoutGuide.centerXAnchor),
                imageView.widthAnchor.constraint(equalTo: imageView.safeAreaLayoutGuide.heightAnchor,
                                                 multiplier: 4.0 / 3.0),
                
                crossButton.bottomAnchor.constraint(equalTo: startButton.safeAreaLayoutGuide.topAnchor,
                                                    constant: -20),
                crossButton.trailingAnchor.constraint(equalTo: circleButton.safeAreaLayoutGuide.leadingAnchor),
                
                circleButton.bottomAnchor.constraint(equalTo: crossButton.safeAreaLayoutGuide.topAnchor),
                circleButton.trailingAnchor.constraint(equalTo: visualEffectView.contentView.safeAreaLayoutGuide.trailingAnchor,
                                                       constant: -20),
                
                triangleButton.bottomAnchor.constraint(equalTo: circleButton.safeAreaLayoutGuide.topAnchor),
                triangleButton.trailingAnchor.constraint(equalTo: circleButton.safeAreaLayoutGuide.leadingAnchor),
                
                squareButton.bottomAnchor.constraint(equalTo: crossButton.safeAreaLayoutGuide.topAnchor),
                squareButton.trailingAnchor.constraint(equalTo: crossButton.safeAreaLayoutGuide.leadingAnchor),
                
                upButton.leadingAnchor.constraint(equalTo: leftButton.safeAreaLayoutGuide.trailingAnchor),
                upButton.bottomAnchor.constraint(equalTo: leftButton.safeAreaLayoutGuide.topAnchor),
                
                leftButton.leadingAnchor.constraint(equalTo: visualEffectView.contentView.safeAreaLayoutGuide.leadingAnchor,
                                                    constant: 20),
                leftButton.bottomAnchor.constraint(equalTo: downButton.safeAreaLayoutGuide.topAnchor),
                
                downButton.leadingAnchor.constraint(equalTo: leftButton.safeAreaLayoutGuide.trailingAnchor),
                downButton.bottomAnchor.constraint(equalTo: selectButton.safeAreaLayoutGuide.topAnchor,
                                                   constant: -20),
                
                rightButton.leadingAnchor.constraint(equalTo: downButton.safeAreaLayoutGuide.trailingAnchor),
                rightButton.bottomAnchor.constraint(equalTo: downButton.safeAreaLayoutGuide.topAnchor),
                
                l1Button.leadingAnchor.constraint(equalTo: visualEffectView.contentView.safeAreaLayoutGuide.leadingAnchor,
                                                  constant: 20),
                l1Button.bottomAnchor.constraint(equalTo: upButton.safeAreaLayoutGuide.topAnchor,
                                                 constant: -20),
                
                l2Button.leadingAnchor.constraint(equalTo: l1Button.safeAreaLayoutGuide.trailingAnchor,
                                                  constant: 20),
                l2Button.centerYAnchor.constraint(equalTo: l1Button.safeAreaLayoutGuide.centerYAnchor),
                
                r1Button.trailingAnchor.constraint(equalTo: visualEffectView.contentView.safeAreaLayoutGuide.trailingAnchor,
                                                   constant: -20),
                r1Button.bottomAnchor.constraint(equalTo: upButton.safeAreaLayoutGuide.topAnchor,
                                                 constant: -20),
                
                r2Button.trailingAnchor.constraint(equalTo: r1Button.safeAreaLayoutGuide.leadingAnchor,
                                                   constant: -20),
                r2Button.centerYAnchor.constraint(equalTo: r1Button.safeAreaLayoutGuide.centerYAnchor)
            ])
        } else {
            portraitBottomConstraint = secondaryImageView.bottomAnchor.constraint(equalTo: imageView.safeAreaLayoutGuide.bottomAnchor)
            guard let portraitBottomConstraint else {
                return
            }
            
            constraints.portrait.append(contentsOf: [
                secondaryImageView.topAnchor.constraint(equalTo: view.topAnchor),
                secondaryImageView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
                secondaryImageView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
                portraitBottomConstraint,
                
                visualEffectView.topAnchor.constraint(equalTo: view.topAnchor),
                visualEffectView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
                visualEffectView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
                visualEffectView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
                
                imageView.topAnchor.constraint(equalTo: visualEffectView.contentView.safeAreaLayoutGuide.topAnchor,
                                               constant: 20.0),
                imageView.leadingAnchor.constraint(equalTo: visualEffectView.contentView.safeAreaLayoutGuide.leadingAnchor,
                                                   constant: 20.0),
                imageView.trailingAnchor.constraint(equalTo: visualEffectView.contentView.safeAreaLayoutGuide.trailingAnchor,
                                                    constant: -20.0),
                imageView.heightAnchor.constraint(equalTo: imageView.safeAreaLayoutGuide.widthAnchor,
                                                  multiplier: 3.0 / 4.0),
                
                settingsButton.bottomAnchor.constraint(equalTo: visualEffectView.contentView.bottomAnchor,
                                                       constant: -20.0),
                settingsButton.centerXAnchor.constraint(equalTo: visualEffectView.contentView.safeAreaLayoutGuide.centerXAnchor),
                
                selectButton.bottomAnchor.constraint(equalTo: visualEffectView.contentView.bottomAnchor,
                                                     constant: -20.0),
                selectButton.trailingAnchor.constraint(equalTo: settingsButton.safeAreaLayoutGuide.leadingAnchor,
                                                       constant: -20.0),
                
                startButton.bottomAnchor.constraint(equalTo: visualEffectView.contentView.bottomAnchor,
                                                    constant: -20.0),
                startButton.leadingAnchor.constraint(equalTo: settingsButton.safeAreaLayoutGuide.trailingAnchor,
                                                     constant: 20.0),
                
                crossButton.bottomAnchor.constraint(equalTo: startButton.safeAreaLayoutGuide.topAnchor,
                                                    constant: -20),
                crossButton.trailingAnchor.constraint(equalTo: circleButton.safeAreaLayoutGuide.leadingAnchor),
                
                circleButton.bottomAnchor.constraint(equalTo: crossButton.safeAreaLayoutGuide.topAnchor),
                circleButton.trailingAnchor.constraint(equalTo: visualEffectView.contentView.safeAreaLayoutGuide.trailingAnchor,
                                                       constant: -20),
                
                triangleButton.bottomAnchor.constraint(equalTo: circleButton.safeAreaLayoutGuide.topAnchor),
                triangleButton.trailingAnchor.constraint(equalTo: circleButton.safeAreaLayoutGuide.leadingAnchor),
                
                squareButton.bottomAnchor.constraint(equalTo: crossButton.safeAreaLayoutGuide.topAnchor),
                squareButton.trailingAnchor.constraint(equalTo: crossButton.safeAreaLayoutGuide.leadingAnchor),
                
                upButton.leadingAnchor.constraint(equalTo: leftButton.safeAreaLayoutGuide.trailingAnchor),
                upButton.bottomAnchor.constraint(equalTo: leftButton.safeAreaLayoutGuide.topAnchor),
                
                leftButton.leadingAnchor.constraint(equalTo: visualEffectView.contentView.safeAreaLayoutGuide.leadingAnchor,
                                                    constant: 20),
                leftButton.bottomAnchor.constraint(equalTo: downButton.safeAreaLayoutGuide.topAnchor),
                
                downButton.leadingAnchor.constraint(equalTo: leftButton.safeAreaLayoutGuide.trailingAnchor),
                downButton.bottomAnchor.constraint(equalTo: selectButton.safeAreaLayoutGuide.topAnchor,
                                                   constant: -20),
                
                rightButton.leadingAnchor.constraint(equalTo: downButton.safeAreaLayoutGuide.trailingAnchor),
                rightButton.bottomAnchor.constraint(equalTo: downButton.safeAreaLayoutGuide.topAnchor),
                
                l1Button.leadingAnchor.constraint(equalTo: visualEffectView.contentView.safeAreaLayoutGuide.leadingAnchor,
                                                  constant: 20),
                l1Button.bottomAnchor.constraint(equalTo: upButton.safeAreaLayoutGuide.topAnchor,
                                                 constant: -20),
                
                l2Button.leadingAnchor.constraint(equalTo: l1Button.safeAreaLayoutGuide.trailingAnchor,
                                                  constant: 20),
                l2Button.centerYAnchor.constraint(equalTo: l1Button.safeAreaLayoutGuide.centerYAnchor),
                
                r1Button.trailingAnchor.constraint(equalTo: visualEffectView.contentView.safeAreaLayoutGuide.trailingAnchor,
                                                   constant: -20),
                r1Button.bottomAnchor.constraint(equalTo: upButton.safeAreaLayoutGuide.topAnchor,
                                                 constant: -20),
                
                r2Button.trailingAnchor.constraint(equalTo: r1Button.safeAreaLayoutGuide.leadingAnchor,
                                                   constant: -20),
                r2Button.centerYAnchor.constraint(equalTo: r1Button.safeAreaLayoutGuide.centerYAnchor)
            ])
            
            landscapeLeftConstraint = secondaryImageView.leadingAnchor.constraint(equalTo: imageView.safeAreaLayoutGuide.leadingAnchor)
            landscapeRightConstraint = secondaryImageView.trailingAnchor.constraint(equalTo: imageView.safeAreaLayoutGuide.trailingAnchor)
            guard let landscapeLeftConstraint, let landscapeRightConstraint else {
                return
            }
            
            constraints.landscape.append(contentsOf: [
                secondaryImageView.topAnchor.constraint(equalTo: view.topAnchor),
                secondaryImageView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
                landscapeLeftConstraint,
                landscapeRightConstraint,
                
                visualEffectView.topAnchor.constraint(equalTo: view.topAnchor),
                visualEffectView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
                visualEffectView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
                visualEffectView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
                
                startButton.bottomAnchor.constraint(equalTo: visualEffectView.contentView.bottomAnchor,
                                                    constant: -20.0),
                startButton.leadingAnchor.constraint(equalTo: imageView.safeAreaLayoutGuide.trailingAnchor,
                                                     constant: 20),
                
                selectButton.bottomAnchor.constraint(equalTo: visualEffectView.contentView.bottomAnchor,
                                                     constant: -20.0),
                selectButton.trailingAnchor.constraint(equalTo: imageView.safeAreaLayoutGuide.leadingAnchor,
                                                       constant: -20.0),
                
                settingsButton.bottomAnchor.constraint(equalTo: visualEffectView.contentView.bottomAnchor,
                                                       constant: -20.0),
                settingsButton.leadingAnchor.constraint(equalTo: startButton.safeAreaLayoutGuide.trailingAnchor,
                                                        constant: 20.0),
                
                imageView.topAnchor.constraint(equalTo: visualEffectView.contentView.safeAreaLayoutGuide.topAnchor,
                                               constant: 20.0),
                imageView.bottomAnchor.constraint(equalTo: visualEffectView.contentView.bottomAnchor,
                                                  constant: -20.0),
                imageView.centerXAnchor.constraint(equalTo: visualEffectView.contentView.safeAreaLayoutGuide.centerXAnchor),
                imageView.widthAnchor.constraint(equalTo: imageView.safeAreaLayoutGuide.heightAnchor,
                                                 multiplier: 4.0 / 3.0),
                
                crossButton.bottomAnchor.constraint(equalTo: startButton.safeAreaLayoutGuide.topAnchor,
                                                    constant: -20),
                crossButton.trailingAnchor.constraint(equalTo: circleButton.safeAreaLayoutGuide.leadingAnchor),
                
                circleButton.bottomAnchor.constraint(equalTo: crossButton.safeAreaLayoutGuide.topAnchor),
                circleButton.trailingAnchor.constraint(equalTo: visualEffectView.contentView.safeAreaLayoutGuide.trailingAnchor,
                                                       constant: -20),
                
                triangleButton.bottomAnchor.constraint(equalTo: circleButton.safeAreaLayoutGuide.topAnchor),
                triangleButton.trailingAnchor.constraint(equalTo: circleButton.safeAreaLayoutGuide.leadingAnchor),
                
                squareButton.bottomAnchor.constraint(equalTo: crossButton.safeAreaLayoutGuide.topAnchor),
                squareButton.trailingAnchor.constraint(equalTo: crossButton.safeAreaLayoutGuide.leadingAnchor),
                
                upButton.leadingAnchor.constraint(equalTo: leftButton.safeAreaLayoutGuide.trailingAnchor),
                upButton.bottomAnchor.constraint(equalTo: leftButton.safeAreaLayoutGuide.topAnchor),
                
                leftButton.leadingAnchor.constraint(equalTo: visualEffectView.contentView.safeAreaLayoutGuide.leadingAnchor,
                                                    constant: 20),
                leftButton.bottomAnchor.constraint(equalTo: downButton.safeAreaLayoutGuide.topAnchor),
                
                downButton.leadingAnchor.constraint(equalTo: leftButton.safeAreaLayoutGuide.trailingAnchor),
                downButton.bottomAnchor.constraint(equalTo: selectButton.safeAreaLayoutGuide.topAnchor,
                                                   constant: -20),
                
                rightButton.leadingAnchor.constraint(equalTo: downButton.safeAreaLayoutGuide.trailingAnchor),
                rightButton.bottomAnchor.constraint(equalTo: downButton.safeAreaLayoutGuide.topAnchor),
                
                l1Button.leadingAnchor.constraint(equalTo: visualEffectView.contentView.safeAreaLayoutGuide.leadingAnchor,
                                                  constant: 20),
                l1Button.bottomAnchor.constraint(equalTo: upButton.safeAreaLayoutGuide.topAnchor,
                                                 constant: -20),
                
                l2Button.leadingAnchor.constraint(equalTo: l1Button.safeAreaLayoutGuide.trailingAnchor,
                                                  constant: 20),
                l2Button.centerYAnchor.constraint(equalTo: l1Button.safeAreaLayoutGuide.centerYAnchor),
                
                r1Button.trailingAnchor.constraint(equalTo: visualEffectView.contentView.safeAreaLayoutGuide.trailingAnchor,
                                                   constant: -20),
                r1Button.bottomAnchor.constraint(equalTo: upButton.safeAreaLayoutGuide.topAnchor,
                                                 constant: -20),
                
                r2Button.trailingAnchor.constraint(equalTo: r1Button.safeAreaLayoutGuide.leadingAnchor,
                                                   constant: -20),
                r2Button.centerYAnchor.constraint(equalTo: r1Button.safeAreaLayoutGuide.centerYAnchor)
            ])
        }
        
        switch interfaceOrientation() {
        case .portrait:
            view.addConstraints(constraints.portrait)
        case .landscapeLeft, .landscapeRight:
            view.addConstraints(constraints.landscape)
        default:
            break
        }
        
        Task {
            await mandarineCore.insert(cartridge: game.details.url)
            
            await mandarineCore.audioCallback { buffer, samples in
                
            }
            
            await mandarineCore.videoCallback { buffer, x, y, width, height in
                Task { @MainActor in
                    guard let cgImage: CGImage = .psx_bgr555(buffer, 1024, 512),
                    let cropped: CGImage = cgImage.cropping(to: CGRect(x: x, y: y, width: width, height: height)) else {
                        return
                    }
                    
                    imageView.image = UIImage(cgImage: cropped)
                    secondaryImageView.image = UIImage(cgImage: cropped)
                }
            }
            
            await mandarineCore.secondaryVideoCallback { buffer, x, y, width, height in
                Task { @MainActor in
                    guard let cgImage: CGImage = .psx_rgb888(buffer, 1024, 512),
                          let cropped: CGImage = cgImage.cropping(to: CGRect(x: x, y: y, width: width, height: height)) else {
                        return
                    }
                    
                    imageView.image = UIImage(cgImage: cropped)
                    secondaryImageView.image = UIImage(cgImage: cropped)
                }
            }
            
            await mandarineCore.start()
        }
        
        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
            }
            
            Task {
                await applicationState.shouldPause ? self.mandarineCore.pause() : self.mandarineCore.unpause()
            }
        }
        
        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
            }
            
            visualEffectView.contentView.subviews.filter { subview in
                subview.isKind(of: UIButton.classForCoder()) && subview != settingsButton
            }.forEach { button in
                UIView.animate(withDuration: 1 / 3) {
                    button.alpha = 1 / 3
                }
            }
            
            func input(button: CombinedCores.MandarineCore.MandarineButton, pressed: Bool) {
                Task {
                    if pressed {
                        await self.mandarineCore.press(button: button)
                    } else {
                        await self.mandarineCore.release(button: button)
                    }
                }
            }
            
            extendedGamepad.buttonA.pressedChangedHandler = { element, value, pressed in
                input(button: .cross, pressed: pressed)
            }
            
            extendedGamepad.buttonB.pressedChangedHandler = { element, value, pressed in
                input(button: .circle, pressed: pressed)
            }
            
            extendedGamepad.buttonX.pressedChangedHandler = { element, value, pressed in
                input(button: .square, pressed: pressed)
            }
            
            extendedGamepad.buttonY.pressedChangedHandler = { element, value, pressed in
                input(button: .triangle, pressed: pressed)
            }
            
            extendedGamepad.dpad.up.pressedChangedHandler = { element, value, pressed in
                input(button: .up, pressed: pressed)
            }
            
            extendedGamepad.dpad.down.pressedChangedHandler = { element, value, pressed in
                input(button: .down, pressed: pressed)
            }
            
            extendedGamepad.dpad.left.pressedChangedHandler = { element, value, pressed in
                input(button: .left, pressed: pressed)
            }
            
            extendedGamepad.dpad.right.pressedChangedHandler = { element, value, pressed in
                input(button: .right, pressed: pressed)
            }
            
            extendedGamepad.leftShoulder.pressedChangedHandler = { element, value, pressed in
                input(button: .l1, pressed: pressed)
            }
            
            extendedGamepad.leftTrigger.pressedChangedHandler = { element, value, pressed in
                input(button: .l2, pressed: pressed)
            }
            
            extendedGamepad.rightShoulder.pressedChangedHandler = { element, value, pressed in
                input(button: .r1, pressed: pressed)
            }
            
            extendedGamepad.rightTrigger.pressedChangedHandler = { element, value, pressed in
                input(button: .r2, pressed: pressed)
            }
            
            if let buttonOptions = extendedGamepad.buttonOptions {
                buttonOptions.pressedChangedHandler = { element, value, pressed in
                    input(button: .select, pressed: pressed)
                }
            }
            
            extendedGamepad.buttonMenu.pressedChangedHandler = { element, value, pressed in
                input(button: .start, pressed: pressed)
            }
        }
        
        NotificationCenter.default.addObserver(forName: .GCControllerDidDisconnect, object: nil, queue: .current) { notification in
            visualEffectView.contentView.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 {
        if iPhone {
            .all
        } else {
            .portrait
        }
    }
    
    override func viewSafeAreaInsetsDidChange() {
        super.viewSafeAreaInsetsDidChange()
        switch interfaceOrientation() {
        case .portrait:
            guard let portraitBottomConstraint else {
                return
            }
            
            portraitBottomConstraint.constant = view.safeAreaInsets.top
        case .landscapeLeft:
            guard let landscapeLeftConstraint, let landscapeRightConstraint else {
                return
            }
            
            landscapeLeftConstraint.constant = -view.safeAreaInsets.right
            landscapeRightConstraint.constant = view.safeAreaInsets.right
        case .landscapeRight:
            guard let landscapeLeftConstraint, let landscapeRightConstraint else {
                return
            }
            
            landscapeLeftConstraint.constant = -view.safeAreaInsets.left
            landscapeRightConstraint.constant = view.safeAreaInsets.left
        default:
            break
        }
        
        Task {
            view.setNeedsUpdateConstraints()
        }
    }
    
    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        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 MandarineController {
    @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)
        }
    }
}