//
// Copyright 2022 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

import Foundation
import UIKit

/// UIKitBackgroundTask is a concrete implementation of BackgroundTaskProtocol using UIApplication background task.
class UIKitBackgroundTask: BackgroundTaskProtocol {
    let name: String
    var isRunning: Bool {
        identifier != .invalid
    }

    let isReusable: Bool
    let expirationHandler: BackgroundTaskExpirationHandler?
    var elapsedTime: TimeInterval {
        Date().timeIntervalSince(startDate) * 1000
    }

    private let application: ApplicationProtocol
    private var identifier: UIBackgroundTaskIdentifier = .invalid
    private var useCounter = 0
    private let startDate = Date()

    /// Initializes and starts a new background task
    /// - Parameters:
    ///   - name: name
    ///   - isReusable: flag indicating the task is reusable
    ///   - application: application instance
    ///   - expirationHandler: expiration handler
    init?(name: String,
          isReusable: Bool,
          application: ApplicationProtocol,
          expirationHandler: BackgroundTaskExpirationHandler?) {
        self.name = name
        self.isReusable = isReusable
        self.application = application
        self.expirationHandler = expirationHandler

        // attempt to start
        identifier = application.beginBackgroundTask(withName: name) { [weak self] in
            guard let self else { return }
            self.stop()
            self.expirationHandler?(self)
        }

        if identifier == .invalid {
            MXLog.error("Do not start background task: \(name), as OS declined")
            //  call expiration handler immediately
            expirationHandler?(self)
            return nil
        }

        if isReusable {
            //  creation itself is a use
            reuse()
        }

        MXLog.verbose("Start background task #\(identifier.rawValue) - \(name)")
    }

    func reuse() {
        guard isReusable else {
            return
        }
        useCounter += 1
    }

    func stop() {
        if isReusable {
            useCounter -= 1
            if useCounter <= 0 {
                endTask()
            }
        } else {
            endTask()
        }
    }

    private func endTask() {
        if identifier != .invalid {
            MXLog.verbose("End background task #\(identifier.rawValue) - \(name) after \(readableElapsedTime)")

            application.endBackgroundTask(identifier)
            identifier = .invalid
        }
    }

    private var readableElapsedTime: String {
        String(format: "%.3fms", elapsedTime)
    }
}
