//
//  MLQRCodeScanner.swift
//  Monal
//
//  Created by Friedrich Altheide on 20.11.20.
//  Copyright © 2020 Monal.im. All rights reserved.
//

import SafariServices

@objc protocol MLLQRCodeScannerAccountLoginDelegate : AnyObject
{
    func MLQRCodeAccountLoginScanned(jid: String, password: String)
    func closeQRCodeScanner()
}

struct XMPPLoginQRCode : Codable
{
    let usedProtocol:String
    let address:String
    let password:String

    private enum CodingKeys: String, CodingKey
    {
        case usedProtocol = "protocol", address, password
    }
}

@objc class MLQRCodeScannerController: UIViewController, AVCaptureMetadataOutputObjectsDelegate
{
    @objc weak var loginDelegate : MLLQRCodeScannerAccountLoginDelegate?

    var videoPreviewLayer: AVCaptureVideoPreviewLayer!;
    var captureSession: AVCaptureSession!;

    override func viewDidLoad()
    {
        super.viewDidLoad()
        self.title = NSLocalizedString("QR-Code Scanner", comment: "")
        view.backgroundColor = UIColor.black

        switch AVCaptureDevice.authorizationStatus(for: .video)
        {
            case .authorized:
                self.setupCaptureSession()

            case .notDetermined:
                AVCaptureDevice.requestAccess(for: .video) { granted in
                    if granted {
                        DispatchQueue.main.async {
                            self.setupCaptureSession()
                        }
                    }
                }

            case .denied:
                return

            case .restricted:
                return

            @unknown default:
                return;
        }
    }

    override func viewWillAppear(_ animated: Bool)
    {
        super.viewWillAppear(animated)

        if (captureSession?.isRunning == false)
        {
            captureSession.startRunning()
        }
    }

    override func viewWillDisappear(_ animated: Bool)
    {
        if (captureSession?.isRunning == true)
        {
            captureSession.stopRunning()
        }
        super.viewWillDisappear(animated)
    }

    func setupCaptureSession()
    {
        // init capture session
        captureSession = AVCaptureSession()
        guard let captureDevice = AVCaptureDevice.default(for: .video)
        else
        {
            errorMsg(title: NSLocalizedString("QR-Code video error", comment: "QR-Code-Scanner"), msg: NSLocalizedString("Could not get default capture device", comment: "QR-Code-Scanner"))
            return;
        }
        let videoInput: AVCaptureDeviceInput

        do
        {
            videoInput = try AVCaptureDeviceInput(device: captureDevice)
        } catch
        {
            errorMsg(title: NSLocalizedString("QR-Code video error", comment: "QR-Code-Scanner"), msg: NSLocalizedString("Could not init video session", comment: "QR-Code-Scanner"))
            return
        }
        if(captureSession.canAddInput(videoInput))
        {
            captureSession.addInput(videoInput)
        }
        else
        {
            errorMsgNoCameraFound()
            return;
        }
        let metadataOutput = AVCaptureMetadataOutput()

        if (captureSession.canAddOutput(metadataOutput)) {
            captureSession.addOutput(metadataOutput)

            metadataOutput.setMetadataObjectsDelegate(self, queue: DispatchQueue.main)
            metadataOutput.metadataObjectTypes = [.qr]
        } else {
            errorMsgNoCameraFound()
            return
        }

        videoPreviewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
        videoPreviewLayer.frame = view.layer.bounds
        videoPreviewLayer.videoGravity = .resizeAspectFill
        view.layer.addSublayer(videoPreviewLayer)

        captureSession.startRunning()
    }

    func errorMsgNoCameraFound()
    {
        captureSession = nil

        errorMsg(title: NSLocalizedString("Could not access camera", comment: "QR-Code-Scanner: camera not found"), msg: NSLocalizedString("It does not seem as your device has a camera. Please use a device with a camera for scanning", comment: "QR-Code-Scanner: Camera not found"))
    }

    override var prefersStatusBarHidden: Bool
    {
        return false
    }

    override var supportedInterfaceOrientations: UIInterfaceOrientationMask
    {
        return .portrait
    }

    func metadataOutput(_ output: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection) {
        captureSession.stopRunning()

        if let metadataObject = metadataObjects.first {
            guard let readableObject = metadataObject as? AVMetadataMachineReadableCodeObject else { return }

            guard let qrCodeAsString = readableObject.stringValue else {
                return handleQRCodeError()
            }
            AudioServicesPlaySystemSound(SystemSoundID(kSystemSoundID_Vibrate))

            //open https?:// urls in safari view controller just as they would if the qrcode was scanned using the camera app
            if qrCodeAsString.hasPrefix("https://") || qrCodeAsString.hasPrefix("http://") {
                if let url = URL(string:qrCodeAsString) {
                    let vc = SFSafariViewController(url:url, configuration:SFSafariViewController.Configuration())
                    present(vc, animated: true)
                }
            //let our app delegate handle all xmpp: urls
            } else if qrCodeAsString.hasPrefix("xmpp:") {
                guard let url = URL(string:qrCodeAsString) else {
                    return handleQRCodeError()
                }
                return (UIApplication.shared.delegate as! MonalAppDelegate).handleXMPPURL(url)
            //if none of the above: handle json provisioning qrcodes, see: https://github.com/iNPUTmice/Conversations/issues/3796
            } else {
                // check if we have a json object
                guard let qrCodeData = qrCodeAsString.data(using:.utf8) else {
                    return handleQRCodeError()
                }
                let jsonDecoder = JSONDecoder()
                do {
                    let loginData = try jsonDecoder.decode(XMPPLoginQRCode.self, from:qrCodeData)
                    handleAccountLogin(loginData:loginData)
                } catch {
                    handleQRCodeError()
                }
                return
            }
        }
    }

    func errorMsg(title: String, msg: String, startCaptureOnClose: Bool = false)
    {
        let ac = UIAlertController(title: title, message: msg, preferredStyle: .alert)
        ac.addAction(UIAlertAction(title: NSLocalizedString("Close", comment: ""), style: .default)
        {
            action -> Void in
            // start capture again after invalid qr code
            if(startCaptureOnClose == true)
            {
                self.captureSession.startRunning()
            }
            else if (self.loginDelegate != nil) {
                self.loginDelegate?.closeQRCodeScanner()
            }
        }
        )
        DispatchQueue.main.async{
            self.present(ac, animated: true)
        }
    }

    func handleAccountLogin(loginData: XMPPLoginQRCode)
    {
        if(loginData.usedProtocol == "xmpp")
        {
            if(self.loginDelegate != nil)
            {
				self.navigationController?.popViewController(animated: true)
				self.loginDelegate?.MLQRCodeAccountLoginScanned(jid: loginData.address, password: loginData.password)
            }
            else
            {
                errorMsg(title: NSLocalizedString("Wrong menu", comment: "QR-Code-Scanner: account scan wrong menu"), msg: NSLocalizedString("The qrcode contains login credentials for an acount. Go to settings -> new account and rescan the qrcode", comment: "QR-Code-Scanner: account scan wrong menu"), startCaptureOnClose: true)
            }
        }
    }

    func handleQRCodeError()
    {
        errorMsg(title: NSLocalizedString("Invalid format", comment: "QR-Code-Scanner: invalid format"), msg: NSLocalizedString("We could not find a xmpp related QR-Code", comment: "QR-Code-Scanner: invalid format"), startCaptureOnClose: true)
    }
}

struct MLQRCodeScanner : UIViewControllerRepresentable {
    let handleLogin: ((String, String) -> Void)?
    let handleClose: (() -> Void)

    class Coordinator: NSObject, MLLQRCodeScannerAccountLoginDelegate {
        let handleLogin: ((String, String) -> Void)?
        let handleClose: (() -> Void)

        func MLQRCodeAccountLoginScanned(jid: String, password: String) {
            if(self.handleLogin != nil) {
                self.handleLogin!(jid, password)
            }
        }

        func closeQRCodeScanner() {
            self.handleClose()
        }

        init(handleLogin: ((String, String) -> Void)?, handleClose: @escaping () -> Void) {
            self.handleLogin = handleLogin
            self.handleClose = handleClose
        }
    }

    func makeUIViewController(context: UIViewControllerRepresentableContext<MLQRCodeScanner>) -> MLQRCodeScannerController {
        let qrCodeScannerViewController = MLQRCodeScannerController()
        if(self.handleLogin != nil) {
            qrCodeScannerViewController.loginDelegate = context.coordinator
        }
        return qrCodeScannerViewController
    }

    func updateUIViewController(_ uiViewController: MLQRCodeScannerController, context: UIViewControllerRepresentableContext<MLQRCodeScanner>) {
    }

    func makeCoordinator() -> MLQRCodeScanner.Coordinator {
        Coordinator(handleLogin: self.handleLogin, handleClose: self.handleClose);
    }

    init(handleClose: @escaping () -> Void) {
        self.handleLogin = nil
        self.handleClose = handleClose
    }

    init(handleLogin: @escaping (String, String) -> Void, handleClose: @escaping () -> Void) {
        self.handleLogin = handleLogin
        self.handleClose = handleClose
    }
}