//
//  CameraView.swift
//  Runner
//
//  Created by PT TEKNOKREASI KARYA INDONESIA on 16/10/24.
//

import Flutter
import UIKit
import Photos
import Foundation
import CoreMotion

class CameraView: NSObject, FlutterPlatformView {
    @IBOutlet var _view: UIView!
    let cameraController = CameraController()
    var channel: FlutterMethodChannel
    private var motionManager: CMMotionManager?
    private var timer: Timer?
    var currentOrientation: UIInterfaceOrientation = .portrait
    
    init(
        frame: CGRect,
        viewIdentifier viewId: Int64,
        arguments args: Any?,
        binaryMessenger messenger: FlutterBinaryMessenger?
    ){
        _view = UIView()
        channel = FlutterMethodChannel(name: Constants.pluginName, binaryMessenger: messenger!)
        super.init()
    }
    
    func view() -> UIView {
        channel.setMethodCallHandler({
            [weak self] (call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in
            switch (call.method){
                
            case Constants.setDimension:
                self?.setViewDimension(call.arguments)
                
            case Constants.switchCamera:
                let cameraPosition = self?.switchCamera()
                result(cameraPosition)
                
            case Constants.takePhoto:
                self?.takePhoto(result: result)
                
            case Constants.canToggleFlash:
                self?.canToggleFlash(result: result)
                
            case Constants.toggleFlash:
                self?.toggleFlash(result: result)
                
            default:
                result(FlutterMethodNotImplemented)
                return
            }
        })
        
        return _view
    }
    
    func createNativeView(view _view: UIView, withFrame frame: CGRect, jenis: Double){
        _view.backgroundColor = .black
        _view.frame = frame
        
        startDeviceMotionService()
        cameraController.prepare { [unowned self] (error) in
            if let error = error { print(error) }
            if(jenis == 1.0){
                switch self.cameraController.currentCameraPosition{
                    case .none:
                        print("none camera position")
                    case .some(.front):
                        print("front camera position")
                    case .some(.rear):
                        do { try cameraController.switchCameras() }
                        catch {
                            print(error)
                        }
                        print("rear camera position")
                }
            }
            try? self.cameraController.displayPreview(on: _view, withFrame: frame)
        }
    }
    
    func setViewDimension(_ args: Any?){
        let arguments = args as! Dictionary<String, Double>
        let frame = CGRect(x: 0, y: 0, width: arguments["width"] ?? 0, height: arguments["height"] ?? 0)
        createNativeView(view: _view, withFrame: frame, jenis: arguments["absensi"]!)
    }
    
    func switchCamera() -> String {
        do { try cameraController.switchCameras() }
        catch {
            print(error)
            return "error"
        }
        
        switch cameraController.currentCameraPosition {
            case .some(.front):
                return "front"
            case .some(.rear):
                return "rear"
            case .none:
                return "none"
        }
    }
    
    func takePhoto(result: @escaping FlutterResult) {
        cameraController.captureImage { ( image, error, data ) in
            guard let image = image, let data = data else {
                print(error ?? "Image capture error")
                return
            }
            
            var newImages = image
            
            switch self.cameraController.currentCameraPosition{
                case .none:
                    print("none camera position")
                case .some(.front):
                    if(self.currentOrientation.rawValue == 1){
                        newImages = UIImage(cgImage: newImages.cgImage!, scale: 1.0, orientation: .leftMirrored)
                    }else if(self.currentOrientation.rawValue == 3){
                        newImages = UIImage(cgImage: newImages.cgImage!, scale: 1.0, orientation: .downMirrored)
                    }else if(self.currentOrientation.rawValue == 4){
                        newImages = UIImage(cgImage: newImages.cgImage!, scale: 1.0, orientation: .upMirrored)
                    }
                    print("front camera position")
                case .some(.rear):
                    if(self.currentOrientation.rawValue == 1){
//                        RAW VALUE = 1 POTRAIT NORMAL
                    }else if(self.currentOrientation.rawValue == 2){
//                        RAW VALUE = 2 POTRAIT TERBALIK
                    }else if(self.currentOrientation.rawValue == 3){
//                        RAW VALUE = 3 LANSCAPE PUTAR BERLAWANAN JARUM JAM
                        newImages = UIImage(cgImage: newImages.cgImage!, scale: 1.0, orientation: .up)
                    }else if(self.currentOrientation.rawValue == 4){
//                        RAW VALUE = 4 LANSCAPE PUTAR SEARAH JARUM JAM
                        newImages = UIImage(cgImage: newImages.cgImage!, scale: 1.0, orientation: .down)
                    }
                    print("rear camera position")
            }
            
            newImages = self.resizeImage(image: newImages, newWidth: 400)
            result(FlutterStandardTypedData(bytes: newImages.jpegData(compressionQuality: 0.3)!))
        }
    }
    
    func resizeImage(image: UIImage, newWidth: CGFloat) -> UIImage {

        let scale = newWidth / image.size.width
        let newHeight = image.size.height * scale
        UIGraphicsBeginImageContext(CGSizeMake(newWidth, newHeight))
        image.draw(in: CGRectMake(0, 0, newWidth, newHeight))
        let newImage = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()

        return newImage!
    }
    
    func canToggleFlash(result: @escaping FlutterResult){
        if let cameraPosition = cameraController.currentCameraPosition {
            result(cameraPosition == .rear)
        } else {
            result(Bool(false))
        }
    }
    
    func toggleFlash(result: @escaping FlutterResult) {
        guard cameraController.currentCameraPosition != nil && cameraController.currentCameraPosition == .rear else { return }
        
        do {
            try cameraController.rearCamera?.lockForConfiguration()
            cameraController.flashMode = cameraController.flashMode == .on ? .off : .on
            cameraController.rearCamera?.torchMode = cameraController.flashMode == .on ? .on : .off
            cameraController.rearCamera?.unlockForConfiguration()
            result(Bool(cameraController.flashMode == .on))
        }
        
        catch {
            print("Unable to toggle flashlight")
        }
    }
}

extension UIImage {
    func rotate(radians: CGFloat) -> UIImage {
        let rotatedSize = CGRect(origin: .zero, size: size)
            .applying(CGAffineTransform(rotationAngle: CGFloat(radians)))
            .integral.size
        UIGraphicsBeginImageContext(rotatedSize)
        if let context = UIGraphicsGetCurrentContext() {
            let origin = CGPoint(x: rotatedSize.width / 2.0,
                                 y: rotatedSize.height / 2.0)
            context.translateBy(x: origin.x, y: origin.y)
            context.rotate(by: radians)
            draw(in: CGRect(x: -origin.y, y: -origin.x,
                            width: size.width, height: size.height))
            let rotatedImage = UIGraphicsGetImageFromCurrentImageContext()
            UIGraphicsEndImageContext()

            return rotatedImage ?? self
        }

        return self
    }
}


extension CameraView {
    
    private func startDeviceMotionService() {
        motionManager = CMMotionManager()
        guard let motionManager = motionManager,
            motionManager.isDeviceMotionAvailable else {
                assertionFailure()
                return
        }
        
        motionManager.deviceMotionUpdateInterval = Constantsss.deviceMotionServiceTimeInterval
        motionManager.showsDeviceMovementDisplay = true
        motionManager.startDeviceMotionUpdates(using: .xMagneticNorthZVertical)
        var _currentOrientation: UIInterfaceOrientation = .landscapeRight {
            didSet {
                if _currentOrientation != oldValue {
                    deviceOrientationChanged(to: _currentOrientation)
                }
            }
        }
        
        let block: (Timer) -> Void = { [weak self] (_) in
            if let data = self?.motionManager?.deviceMotion {
                let xAxis = data.gravity.x
                if xAxis > Constantsss.minDeviceMaxXAxis {
                    _currentOrientation = .landscapeLeft
                    self!.currentOrientation = .landscapeLeft
                } else if xAxis < Constantsss.minDeviceMinXAxis {
                    _currentOrientation = .landscapeRight
                    self!.currentOrientation = .landscapeRight
                } else {
                    _currentOrientation = .portrait
                    self!.currentOrientation = .portrait
                }
            }
        }
        timer = Timer(fire: Date(),
                      interval: Constantsss.deviceMotionServiceTimeInterval,
                      repeats: true,
                      block: block)
        
        guard let timer = self.timer else {
            assertionFailure()
            return
        }
        RunLoop.current.add(timer, forMode: RunLoop.Mode.default)
    }
    
    private func stopMotionUpdates() {
        timer?.invalidate()
        motionManager?.stopDeviceMotionUpdates()
    }
    
    private func deviceOrientationChanged(to: UIInterfaceOrientation) {
        
    }
}

extension CameraView {
    private enum Constantsss {
        static let minDeviceMaxXAxis: Double = 0.65
        static let minDeviceMinXAxis: Double = -0.65
        static let deviceMotionServiceTimeInterval: TimeInterval = 1.0 / 60.0
    }
}
