import Foundation
import PRESTOplay

open class BaseEventEmitter {
    var eventDetailsCache = SynchronizedMap<EventDetailsCacheKey, [String: Any]>()

    // MARK: Lifecycle

    public init(_ parentModule: AnyObject) {
        self.parentModule = parentModule
    }

    public func stopObserving() {
        hasListener = false
    }

    public func startObserving() {
        hasListener = true
    }

    public func supportedEvents() -> [String] {
        [BaseEventEmitter.EVENT_NAME]
    }

    public func resetEventDetailsCache() {
        eventDetailsCache.removeAll()
    }

    public func emitError(playerId: String, error coreSdkError: PRESTOerror) {
        let error = PrestoPlayError(coreSdkError)
        
        emit(
            consumerId: playerId,
            eventType: "error",
            eventDetails: {
            var details: [String: Any] = [
                "severity": error.severity.rawValue,
                "code": error.code.rawValue,
            ]

            if let message = error.message {
                details["message"] = message
            }

            if let causeMessage = error.causeMessage {
                details["causeMessage"] = causeMessage
            }

            if let metadata = error.metadata {
                details["metadata"] = metadata.toDictionary()
            }
                
            if let nativeError = error.nativeError,
                let nativeErrorDict = nativeError.toDictionary() {
                details["nativeError"] = nativeErrorDict
            }

            return details
            }()
        )
    }

    public func emit(consumerId: String, eventType: String, eventDetails: [String: Any], preventDuplicates: Bool = false) {
        if hasListener {
            if preventDuplicates {
                let eventDetailsCacheKey = EventDetailsCacheKey(consumerId: consumerId, eventType: eventType)
                if let lastEventDetails = eventDetailsCache.get(key: eventDetailsCacheKey),
                   deepCompare(lastEventDetails, eventDetails)
                {
                    return
                }

                eventDetailsCache.add(key: eventDetailsCacheKey, value: eventDetails)
            }

            parentModule.sendEvent(
                withName: Self.EVENT_NAME,
                body: [
                    "consumerId": consumerId,
                    "type": eventType,
                    "timestamp": Int(Date().timeIntervalSince1970 * 1000),
                    "details": eventDetails,
                ]
            )
        }
    }

    // MARK: Private

    private static let EVENT_NAME = "PrestoPlayEvent"

    private var hasListener = false
    private let parentModule: AnyObject
}

struct EventDetailsCacheKey: Hashable {

    let consumerId: String
    let eventType: String

    func hash(into hasher: inout Hasher) {
        hasher.combine(self.consumerId)
        hasher.combine(self.eventType)
    }

    static func ==(lhs: EventDetailsCacheKey, rhs: EventDetailsCacheKey) -> Bool {
        return lhs.consumerId == rhs.consumerId && lhs.eventType == rhs.eventType
    }
}
