//
//  CameraModel.swift
//  Stages-demo
//
//  Created by Uldis Zingis on 14/09/2022.
//

import AVFoundation
import UIKit
import SwiftUI

struct CameraView: UIViewControllerRepresentable {
    typealias UIViewType = CameraPreviewController

    @Binding var isPreviewActive: Bool
    @Binding var isFrontCameraActive: Bool

    func makeUIViewController(context: Context) -> CameraPreviewController {
        return CameraPreviewController()
    }

    func updateUIViewController(_ uiViewController: CameraPreviewController, context: Context) {
        if isPreviewActive {
            uiViewController.startCaptureSession()
        } else {
            uiViewController.stopCaptureSession()
        }

        if (uiViewController.activeCamera?.position == .front && !isFrontCameraActive) ||
            (uiViewController.activeCamera?.position == .back && isFrontCameraActive) {
            uiViewController.swapCamera()
        }
    }
}

class CameraPreviewController: UIViewController {
    var captureSession = AVCaptureSession()
    var cameraPreviewLayer: AVCaptureVideoPreviewLayer?

    var frontCamera: AVCaptureDevice?
    var backCamera: AVCaptureDevice?
    var activeCamera: AVCaptureDevice?
    var captureDeviceInput: AVCaptureDeviceInput?

    private var configurationInProgress: Bool = false

    override func viewDidLoad() {
        super.viewDidLoad()
        setupPreviewLayer()
        setupCameras()
    }

    func setupPreviewLayer() {
        captureSession.sessionPreset = AVCaptureSession.Preset.hd1280x720

        cameraPreviewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
        cameraPreviewLayer?.session = captureSession
        cameraPreviewLayer?.frame = CGRect(x: view.frame.width / 5,
                                           y: 0,
                                           width: 225,
                                           height: 400)
        cameraPreviewLayer?.videoGravity = AVLayerVideoGravity.resizeAspectFill
        cameraPreviewLayer?.connection?.videoOrientation = AVCaptureVideoOrientation.portrait
        cameraPreviewLayer?.backgroundColor = UIColor.black.cgColor
        view.layer.insertSublayer(cameraPreviewLayer!, at: 0)
    }

    func setupCameras() {
        let deviceDiscoverySession = AVCaptureDevice.DiscoverySession(
            deviceTypes: [
                .builtInDualCamera,
                .builtInTrueDepthCamera,
                .builtInWideAngleCamera,
                .builtInDualWideCamera,
                .builtInTripleCamera
            ],
            mediaType: AVMediaType.video,
            position: .unspecified)
        frontCamera = deviceDiscoverySession.devices.first(where: { $0.position == .front })
        backCamera = deviceDiscoverySession.devices.last(where: { $0.position == .back })
        let isFrontCameraSelected = (UserDefaults.standard.value(forKey: Constants.kActiveFrontCamera) as? Bool) ?? true
        activeCamera = isFrontCameraSelected ? frontCamera : backCamera
    }

    func setupInput(_ camera: AVCaptureDevice) {
        if configurationInProgress {
            print("ℹ cameras configuration already in progress")
            return
        }

        do {
            captureDeviceInput = try AVCaptureDeviceInput(device: camera)

            captureSession.beginConfiguration()
            configurationInProgress = true

            for input in captureSession.inputs {
                captureSession.removeInput(input)
            }

            guard captureSession.canAddInput(captureDeviceInput!) else {
                print("ℹ ❌ AVCaptureSession can't add input \(captureDeviceInput!)")
                return
            }

            captureSession.addInput(captureDeviceInput!)
            activeCamera = camera
            print("ℹ camera preview input set to \(camera)")
        } catch {
            print("ℹ ❌ Error creating AVCaptureDeviceInput with camera: \(error)")
        }
        captureSession.commitConfiguration()
        configurationInProgress = false
    }

    func swapCamera() {
        let isFrontCameraSelected = (UserDefaults.standard.value(forKey: Constants.kActiveFrontCamera) as? Bool) ?? true
        if isFrontCameraSelected {
            guard let backCamera = backCamera else {
                print("ℹ ❌ no camera available for preview")
                return
            }
            setupInput(backCamera)
        } else {
            guard let frontCamera = frontCamera else {
                print("ℹ ❌ no camera available for preview")
                return
            }
            setupInput(frontCamera)
        }
        UserDefaults.standard.set(!isFrontCameraSelected, forKey: Constants.kActiveFrontCamera)
    }

    public func startCaptureSession() {
        if captureSession.isRunning {
            print("ℹ camera preview capture session already running")
            return
        }

        if let activeCamera = activeCamera {
            setupInput(activeCamera)
        }
        DispatchQueue.global().async {
            self.captureSession.startRunning()
        }
    }

    public func stopCaptureSession() {
        captureSession.stopRunning()
        if let input = captureDeviceInput {
            captureSession.removeInput(input)
            captureDeviceInput = nil
        }
    }
}