//
//  SailassistApp.swift
//  Sailassist
//
//  Created by Mamoru Sugita on 2023/10/13.
//

import SwiftUI
import UIKit
import BackgroundTasks
import Combine

@main
struct SailassistApp: App {
    @UIApplicationDelegateAdaptor(AppDelegate.self) private var delegate
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

private let appRefreshIdentifier: String = "com.sailassist.refresh"
private let appProcessIdentifier: String = "com.sailassist.process"

//@UIApplicationMain
class AppDelegate: NSObject, UIApplicationDelegate{
    func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
        print(debug: "called")
        let config = UISceneConfiguration(name: nil, sessionRole: connectingSceneSession.role)
        config.delegateClass = SceneDelegate.self
        return config
    }

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        print(debug: "called")
        // 第一引数: Info.plistで定義したIdentifierを指定
        // 第二引数: タスクを実行するキューを指定。nilの場合は、デフォルトのバックグラウンドキューが利用されます。
        // 第三引数: 実行する処理
        BGTaskScheduler.shared.register(forTaskWithIdentifier: appRefreshIdentifier, using: nil) { task in
            print(debug: "タスクの登録(Refresh)")
            print(task)
            self.handleAppRefresh(task: task as! BGAppRefreshTask)
        }

        BGTaskScheduler.shared.register(forTaskWithIdentifier: appProcessIdentifier, using: nil) { task in
            print(debug: "タスクの登録(Processing)")
            print(task)
            self.handleAppProcessing(task: task as! BGProcessingTask)
        }

        // 予約されているタスクの確認
        Task {
            print(debug: "タスクの確認")
            let allRequests = await BGTaskScheduler.shared.pendingTaskRequests()
            print(debug: allRequests)
        }
        return true
    }

    // バックグラウンド処理したい内容
    private func handleAppRefresh(task: BGAppRefreshTask) {
        // 1日の間、何度も実行したい場合は、1回実行するごとに新たにスケジューリングに登録します
        scheduleAppRefresh()

        let queue = OperationQueue()
        queue.maxConcurrentOperationCount = 1

        // 時間内に実行完了しなかった場合は、処理を解放します
        // バックグラウンドで実行する処理は、次回に回しても問題ない処理のはずなので、これでOK
        task.expirationHandler = {
            queue.cancelAllOperations()
        }

        let url_string : String = HttpRequestType.RegisterLogin.rawValue
        guard let req_url = URL(string : url_string) else {
            return
        }
        let operation = EcaTask(url: req_url)
        operation.completionBlock = {
            task.setTaskCompleted(success: operation.isFinished)
        }
        queue.addOperation(operation)

        // サンプルの処理をキューに詰めます
//        let array = [1, 2, 3, 4, 5]
//        array.enumerated().forEach { arg in
//            let (offset, value) = arg
//            let operation = PrintProcessing(id: value)
//            if offset == array.count - 1 {
//                operation.completionBlock = {
//                    // 最後の処理が完了したら、必ず完了したことを伝える必要があります
//                    task.setTaskCompleted(success: operation.isFinished)
//                }
//            }
//            queue.addOperation(operation)
//        }
    }

    private func handleAppProcessing(task: BGProcessingTask) {
        // 1日の間、何度も実行したい場合は、1回実行するごとに新たにスケジューリングに登録します
        scheduleAppProcessing()

        let queue = OperationQueue()
        queue.maxConcurrentOperationCount = 1

        // 時間内に実行完了しなかった場合は、処理を解放します
        // バックグラウンドで実行する処理は、次回に回しても問題ない処理のはずなので、これでOK
        task.expirationHandler = {
            queue.cancelAllOperations()
        }

//        let url_string : String = HttpRequestType.RegisterLogin.rawValue
//        guard let req_url = URL(string : url_string) else {
//            return
//        }
//        let operation = EcaTask(url: req_url)
//        operation.completionBlock = {
//            task.setTaskCompleted(success: operation.isFinished)
//        }
//        queue.addOperation(operation)

        // サンプルの処理をキューに詰めます
        let array = [1, 2, 3, 4, 5]
        array.enumerated().forEach { arg in
            let (offset, value) = arg
            let operation = PrintOperation(id: value)
            if offset == array.count - 1 {
                operation.completionBlock = {
                    // 最後の処理が完了したら、必ず完了したことを伝える必要があります
                    task.setTaskCompleted(success: operation.isFinished)
                }
            }
            queue.addOperation(operation)
        }
    }
}

class SceneDelegate: UIResponder, UIWindowSceneDelegate, ObservableObject {
    weak var windowScene: UIWindowScene?
    var tabWindow: UIWindow?

    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        windowScene = scene as? UIWindowScene
    }

    func addTabBar(_ selectedTabModel: SelectedTabModel){
        guard let scene = windowScene else{
            return
        }

        let tabBarController = UIHostingController(rootView:
                                                    CustomTabBar()
            .environmentObject(selectedTabModel)
            .frame(maxHeight: .infinity, alignment: .bottom)
        )

        tabBarController.view.backgroundColor = .clear

        let tabWindow = PassThroughWindow(windowScene: scene)
        tabWindow.rootViewController = tabBarController
        tabWindow.isHidden = false

        self.tabWindow = tabWindow
    }

    //アプリがフォアグラウンドからバックグラウンドに移行
    func sceneDidEnterBackground(_ scene: UIScene) {
        print(debug: "called")
        scheduleAppRefresh()
        scheduleAppProcessing()
    }
}

class PassThroughWindow: UIWindow{
    override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
        guard let view = super.hitTest(point, with: event) else { return nil}
        return rootViewController?.view == view ? nil : view
    }
}

private func scheduleAppRefresh() {
    if let scheduledTime = Calendar.current.date(byAdding: .minute, value: 5, to: Date()) {
        print(debug: "called")
        // Info.plistで定義したIdentifierを指定
        let request = BGAppRefreshTaskRequest(identifier: appRefreshIdentifier)
        // 最低で、どの程度の期間を置いてから実行するか指定
        //    request.earliestBeginDate = Date(timeIntervalSinceNow: 1 * 60)
        request.earliestBeginDate = scheduledTime

        do {
            // スケジューラーに実行リクエストを登録
            print(debug: request)
            try BGTaskScheduler.shared.submit(request)
        } catch {
            print("Could not schedule app refresh: \(error)")
        }
    }
}

private func scheduleAppProcessing() {
    if let scheduledTime = Calendar.current.date(byAdding: .minute, value: 5, to: Date()) {
        print(debug: "called")
        // Info.plistで定義したIdentifierを指定
        let request = BGProcessingTaskRequest(identifier: appProcessIdentifier)
        // 通信が発生するか否かを指定
//        request.requiresNetworkConnectivity = false
        // CPU監視の必要可否を設定
//        request.requiresExternalPower = true

        request.earliestBeginDate = scheduledTime

        // ネットワーク接続を必要とするならtrueにする
        request.requiresNetworkConnectivity = true

        // 外部電源に接続されている場合にのみ実行するならtrue
        request.requiresExternalPower = false

        do {
            // スケジューラーに実行リクエストを登録
            print(debug: request)
            try BGTaskScheduler.shared.submit(request)
        } catch {
            print("Could not schedule app processing: \(error)")
        }
    }
}

func periodicalFunction() {
    print("update")
}

class PrintOperation: Operation {
    let id: Int

    init(id: Int) {
        print(debug: "タスクの初期化(Refresh)")
        self.id = id
    }

    override func main() {
        print(debug: "タスクの実行(Refresh)")
        print("this operation refresh id is \(self.id)")
    }
}

class PrintProcessing: Operation {
    let id: Int

    init(id: Int) {
        print(debug: "タスクの初期化(Processing)")
        self.id = id
    }

    override func main() {
        print(debug: "タスクの実行(Processing)")
        print("this operation processing id is \(self.id)")
    }
}

// MARK: コメント出力を消す
func print(debug: Any = "", function: String = #function, file: String = #file, line: Int = #line) {
    #if DEBUG
        var filename: NSString = file as NSString
        filename = filename.lastPathComponent as NSString
        // Swift.print("File: \(filename), Line: \(line), Func: \(function) \n\(debug)")

        let outstr = String( format: "APPDebug File: %@, Line: %d, Func: %@\n\(debug)", filename, line, function )
        // Swift.print( outstr ) NSLog がある時は、両方に出るのでいらない
        NSLog( outstr )     // デバッグコンソールにも出力
    #endif
}
