import Foundation
import PhotosUI
import Capacitor
import AVFoundation
import NativeLib

@available(iOS 14, *)
@objc(CameraCapturePlugin)
public class CameraCapturePlugin: CAPPlugin,
      CameraViewControllerDelegate, PHPickerViewControllerDelegate, UIAdaptivePresentationControllerDelegate,
      UIImagePickerControllerDelegate & UINavigationControllerDelegate {
    
    
    var cameraViewController: CameraViewController?
    var call: CAPPluginCall?
    var pickerController: PHPickerViewController?
    var imageCropperViewController: ImageCropperViewController?
    var videoController: UIImagePickerController?
    var multiple = false
    var isVideo = false
    var dataResult: [[String : Any]] = []
    
    @objc func capture(_ call: CAPPluginCall) {
        call.keepAlive = true
        self.call = call
        
        AVCaptureDevice.requestAccess(for: .video, completionHandler: { (granted: Bool) in
            guard granted else {
                call.reject("permission failed")
                return
            }
            
            self.takePhoto()
        })
    }
    
    private func takePhoto() {
        DispatchQueue.main.async {
            self.cameraViewController = CameraViewController()
            self.cameraViewController?.modalPresentationStyle = .fullScreen
            self.cameraViewController?.delegate = self
            self.bridge?.viewController?.present(self.cameraViewController!, animated: true, completion: nil)
        }
    }
    
    public func getResult(result: UIImage?, error: String) {
        if error != "" {
            call?.reject(error)
            self.call = nil
            self.pickerController = nil
            self.imageCropperViewController = nil
            return
        }
        doCropSingle(image: result!)
    }
    
    @objc func pickSimpleImage(_ call: CAPPluginCall) {
        call.keepAlive = true
        self.call = call
        multiple = false
        isVideo = false
        dataResult = []
        doPickAndCropImage()
    }
    
    @objc func pickMultipleImage(_ call: CAPPluginCall) {
        call.keepAlive = true
        self.call = call
        multiple = true
        isVideo = false
        dataResult = []
        doPickAndCropImage()
    }
    
    @objc func pickSimpleVideo(_ call: CAPPluginCall) {
        call.keepAlive = true
        self.call = call
        multiple = false
        isVideo = true
        dataResult = []
        videoPicker()
    }
    
    @objc func pickMultipleVideo(_ call: CAPPluginCall) {
        call.keepAlive = true
        self.call = call
        multiple = true
        isVideo = true
        dataResult = []
        videoPicker()
    }
    
    @objc func captureVideo(_ call: CAPPluginCall) {
        call.keepAlive = true
        self.call = call
        DispatchQueue.main.async {
            self.videoController = UIImagePickerController()
            self.videoController?.sourceType = .camera
            self.videoController?.mediaTypes = ["public.movie"]
            self.videoController?.cameraCaptureMode = .video
            self.videoController?.videoQuality = .typeHigh
            self.videoController?.allowsEditing = false
            self.videoController?.modalPresentationStyle = .fullScreen
            self.videoController?.delegate = self
            self.bridge?.viewController?.present(self.videoController!, animated: true)
        }
    }
    
    func convertFromUIImagePickerControllerInfoKeyDictionary(_ input: [UIImagePickerController.InfoKey: Any]) -> [String: Any] {
        return Dictionary(uniqueKeysWithValues: input.map {key, value in (key.rawValue, value)})
    }
    
    public func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
        self.videoController?.dismiss(animated: true)
        self.call?.reject("Canceled")
        self.call = nil
        self.videoController = nil
    }
    
    func convertFromUIImagePickerControllerInfoKey(_ input: UIImagePickerController.InfoKey) -> String {
        return input.rawValue
    }
    
    public func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
        self.videoController?.dismiss(animated: true)
        let info = convertFromUIImagePickerControllerInfoKeyDictionary(info)
        let mediaType:AnyObject? = info[convertFromUIImagePickerControllerInfoKey(UIImagePickerController.InfoKey.mediaType)] as AnyObject?
        
        if let type:AnyObject = mediaType {
            if type is String {
                let stringType = type as! String
                    let urlOfVideo = info[convertFromUIImagePickerControllerInfoKey(UIImagePickerController.InfoKey.mediaURL)] as? URL
                    if let url = urlOfVideo {
                        do {
                            let newUrl = try copyVideo(url);
                            self.call?.resolve(["data": self.bridge?.portablePath(fromLocalURL: newUrl)?.absoluteString])
                            self.call = nil
                            self.videoController = nil
                            return
                        } catch {
                            self.call?.reject(error.localizedDescription)
                            return;
                        }
                        
                    }
            }
        }
        self.call?.reject("Canceled")
        self.call = nil
        self.videoController = nil
    }
    
    private func videoPicker() {
        DispatchQueue.main.async {
            var configuration = PHPickerConfiguration()
            configuration.filter = .videos
            configuration.selectionLimit = self.multiple ? 0 : 1
            self.pickerController = PHPickerViewController(configuration: configuration)
            self.pickerController?.delegate = self
            self.pickerController?.presentationController?.delegate = self
            self.bridge?.viewController?.present(self.pickerController!, animated: true)
        }
    }
    
    private func doPickAndCropImage() {
        DispatchQueue.main.async {
            var configuration = PHPickerConfiguration()
            configuration.filter = .images
            configuration.selectionLimit = self.multiple ? 0 : 1
            self.pickerController = PHPickerViewController(configuration: configuration)
            self.pickerController?.delegate = self
            self.pickerController?.presentationController?.delegate = self
            self.bridge?.viewController?.present(self.pickerController!, animated: true)
        }
    }
    
    private func convertToUIImageArray(fromResults results: [PHPickerResult], onComplete: @escaping ([UIImage]?, Error?) -> Void) {
        var images = [UIImage]()

        let dispatchGroup = DispatchGroup()

        for result in results {
          dispatchGroup.enter()
          let itemProvider = result.itemProvider
          if itemProvider.canLoadObject(ofClass: UIImage.self) {
            itemProvider.loadObject(ofClass: UIImage.self) { (imageOrNil, errorOrNil) in
              if let error = errorOrNil {
                onComplete(nil, error)
                dispatchGroup.leave()
              }
              if let image = imageOrNil as? UIImage {
                images.append(image)
                dispatchGroup.leave()
              }
            }
          }
        }
        dispatchGroup.notify(queue: .main) {
          onComplete(images, nil)
        }
    }
    
    private func convertToUrlArray(fromResults results: [PHPickerResult], onComplete: @escaping ([URL]?, Error?) -> Void) {
        var images = [URL]()

        let dispatchGroup = DispatchGroup()

        for result in results {
            dispatchGroup.enter()
            let itemProvider = result.itemProvider
            itemProvider.loadFileRepresentation(forTypeIdentifier: UTType.movie.identifier) {(videoURL, error) in
                if let error = error {
                  onComplete(nil, error)
                  dispatchGroup.leave()
                }
                if let url = videoURL {
                    do {
                        let newUrl = try self.copyVideo(url)
                        images.append(newUrl)
                        dispatchGroup.leave()
                    } catch {
                        onComplete(nil, error)
                        dispatchGroup.leave()
                    }
                    
                }
            }
        }
        dispatchGroup.notify(queue: .main) {
          onComplete(images, nil)
        }
    }
    
    public func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
        if !isVideo {
            convertToUIImageArray(fromResults: results) {images, error in
                DispatchQueue.main.async {
                    self.pickerController?.dismiss(animated: true) {
                        if (error != nil) {
                            self.call?.reject(error.debugDescription)
                            self.call = nil
                            self.pickerController = nil
                            self.imageCropperViewController = nil
                            return
                        }
                        if images == nil || images!.count < 1 {
                            self.call?.reject("Canceled")
                            self.call = nil
                            self.pickerController = nil
                            self.imageCropperViewController = nil
                            return
                        }
                        if self.multiple {
                            self.doCropMultiple(images: images!)
                        } else {
                            let image = images![0]
                            self.doCropSingle(image: image)
                        }
                    }
                }
            }
        } else {
            convertToUrlArray(fromResults: results) {urls, error in
                DispatchQueue.main.async {
                    self.pickerController?.dismiss(animated: true) {
                        if (error != nil) {
                            self.call?.reject(error.debugDescription)
                            self.call = nil
                            self.pickerController = nil
                            return
                        }
                        if urls == nil || urls!.count < 1 {
                            self.call?.reject("Canceled")
                            self.call = nil
                            self.pickerController = nil
                            return
                        }
                        do {
                            if self.multiple {
                                var uris = [String]()
                                for url in urls! {
                                    if let abUrl = self.bridge?.portablePath(fromLocalURL: url)?.absoluteString {
                                        uris.append(abUrl)
                                    }
                                }
                                self.call?.resolve(["urls": uris])
                                self.call = nil
                                self.pickerController = nil
                            } else {
                                let url = urls![0]
                                self.call?.resolve(["url": self.bridge?.portablePath(fromLocalURL: url)?.absoluteString])
                                self.call = nil
                                self.pickerController = nil
                            }
                        } catch {
                            self.call?.reject(error.localizedDescription)
                            self.call = nil
                            self.pickerController = nil
                        }
                    }
                    
                }
            }
        }
    }
    
    private func getConfig(image: UIImage) -> ImageCropperConfiguration {
        let rounded = self.call?.getBool("rounded", false) ?? false
        let ratioY = self.call?.getDouble("aspectRatioY", 1) ?? 1
        let ratioX = self.call?.getDouble("aspectRatioX", 1) ?? 1
        let type: ImageCropperConfiguration.ImageCropperFigureType = rounded ? .circle : ratioX == ratioY ? .square : .customRect
        
        var config = ImageCropperConfiguration(with: image, and: type)
        if (!rounded && ratioY != ratioX) {
            config.customRatio = CGSize(width: ratioX, height: ratioY)
        }
        config.maskFillColor = UIColor(red: 0.23, green: 0.15, blue: 0.46, alpha: 0.75)
        config.borderColor = UIColor(red: 0.4, green: 0.25, blue: 0.58, alpha: 1)
        config.gridColor = UIColor(red: 0.23, green: 0.15, blue: 0.46, alpha: 0.85)
        config.showGrid = true
        config.message = call?.getString("message", "No message") ?? "No message";
        config.doneTitle = ""
        config.cancelTitle = ""
        return config
    }
    
    private func addResult(dataUrl: URL, dataThumbUrl: URL, rect: CGRect) {
        self.dataResult.append([
            "data": self.bridge?.portablePath(fromLocalURL: dataUrl)?.absoluteString,
            "thumb": self.bridge?.portablePath(fromLocalURL: dataThumbUrl)?.absoluteString,
            "coords": ["x": rect.minX, "y": rect.minY],
            "dimensions": ["width": rect.width, "height": rect.height]
        ])
    }
    
    private func doCropMultiple(images: [UIImage], animated: Bool = true) {
        self.imageCropperViewController = nil
        self.pickerController = nil
        if let image = images.last {
            let config = getConfig(image: image)
            self.imageCropperViewController = ImageCropperViewController.initialize(with: config, completionHandler: { croppedImage, rect in
                if let dataUrl = self.createTmpFileFromImage(image), let dataThumbUrl = self.createTmpFileFromImage(croppedImage)  {
                    self.addResult(dataUrl: dataUrl, dataThumbUrl: dataThumbUrl, rect: rect)
                }
                self.imageCropperViewController?.dismiss(animated: true) {
                    let newImages = Array(images.prefix(images.count - 1))
                    self.doCropMultiple(images: newImages)
                }
            }) { reload in
                if reload {
                    self.imageCropperViewController?.dismiss(animated: false) {
                        self.doCropMultiple(images: images)
                    }
                    return
                }
                self.imageCropperViewController?.dismiss(animated: true) {
                    let newImages = Array(images.prefix(images.count - 1))
                    self.doCropMultiple(images: newImages)
                }
            }
            self.imageCropperViewController?.modalPresentationStyle = .fullScreen
            DispatchQueue.main.async {
                self.bridge?.viewController?.present(self.imageCropperViewController!, animated: animated)
            }
        } else {
            if dataResult.isEmpty {
                self.call?.reject("Canceled")
            } else {
                self.call?.resolve([
                    "data": dataResult
                ])
                dataResult = []
            }
            self.call = nil
        }
    }
    
    private func doCropSingle(image: UIImage, animated: Bool = true) {
        let config = getConfig(image: image)
        self.imageCropperViewController = ImageCropperViewController.initialize(with: config, completionHandler: { croppedImage, rect in
            let dataUrl = self.createTmpFileFromImage(image)
            let dataThumbUrl = self.createTmpFileFromImage(croppedImage)
            if(dataUrl == nil || dataThumbUrl == nil) {
                self.call?.reject("Error ocurred")
            } else {
                self.call?.resolve([
                    "data": self.bridge?.portablePath(fromLocalURL: dataUrl)?.absoluteString,
                    "thumb": self.bridge?.portablePath(fromLocalURL: dataThumbUrl)?.absoluteString,
                    "coords": ["x": rect.minX, "y": rect.minY],
                    "dimensions": ["width": rect.width, "height": rect.height]
                ])
            }
            self.imageCropperViewController?.dismiss(animated: true)
            self.call = nil
            self.pickerController = nil
            self.imageCropperViewController = nil
        }) {reload in
            if reload {
                self.imageCropperViewController?.dismiss(animated: false) {
                    self.doCropSingle(image: image)
                }
                return
            }
            self.imageCropperViewController?.dismiss(animated: true)
            self.call?.reject("Canceled")
            self.call = nil
            self.pickerController = nil
            self.imageCropperViewController = nil
        }
        self.imageCropperViewController?.modalPresentationStyle = .fullScreen
        DispatchQueue.main.async {
            self.bridge?.viewController?.present(self.imageCropperViewController!, animated: animated)
        }
    }
    
    public func presentationControllerDidDismiss(_ presentationController: UIPresentationController) {
        self.call?.reject("Canceled");
        self.call = nil
        self.pickerController = nil
        self.imageCropperViewController = nil
    }
    
    private func copyVideo(_ url: URL) throws -> URL {
        let newUrl = FileManager.default.temporaryDirectory
            .appendingPathComponent(UUID().uuidString)
            .appendingPathExtension("mp4")
        
        try FileManager.default.copyItem(at: url, to: newUrl)
        return newUrl
    }
    
    private func createTmpFileFromImage(_ image: UIImage) -> URL? {
        let fileName = NSUUID().uuidString
        guard let imageData = image.jpegData(compressionQuality: 1) else {
                return nil
        }
        let imageURL = FileManager.default.temporaryDirectory.appendingPathComponent(fileName).appendingPathExtension("jpg")
        do {
            try imageData.write(to: imageURL)
        } catch {
            return nil
        }
        return imageURL
    }
}

