//
//  ComponentMessage.swift
//  
//  Copyright © 2016-2019 Apple Inc. All rights reserved.
//

import Foundation
import UIKit
import PlaygroundSupport
import SPCCore

public struct ComponentMessageOrigin: Codable, CustomStringConvertible {
    public var environment: Environment
    public var deviceName: String
    
    public static var current: ComponentMessageOrigin {
        return ComponentMessageOrigin(environment: Process.environment, deviceName: UIDevice.current.name)
    }
    
    public var description: String {
        return "(\(deviceName):\(environment == .live ? "LVP" : "UP"))"
    }
}

// An enumeration of messages that can be sent between the User and Live View environments.
public enum ComponentMessage: PlaygroundMessage {
    case initComponent(origin: ComponentMessageOrigin, type: String, identity: ComponentIdentity)
    case deinitComponent(origin: ComponentMessageOrigin, identity: ComponentIdentity)
    case event(origin: ComponentMessageOrigin, identity: ComponentIdentity, event: ComponentEvent)
    case reset(origin: ComponentMessageOrigin)
    
    public enum MessageType: String, PlaygroundMessageType {
        case initComponent
        case deinitComponent
        case event
        case reset
    }
    
    public var messageType: MessageType {
        switch self {
        case .initComponent(origin:type:identity:):
            return .initComponent
        case .deinitComponent(origin:identity:):
            return .deinitComponent
        case .event(origin:identity:event:):
            return .event
        case .reset(origin:):
            return .reset
        }
    }
    
    public init?(messageType: MessageType, encodedPayload: Data?) {
        let decoder = JSONDecoder()
        
        switch messageType {
        case .initComponent:
            guard let payload = encodedPayload,
                let object = try? decoder.decode(ComponentInitPayload.self, from: payload) else { return nil }
            self = .initComponent(origin: object.origin, type: object.type, identity: object.identity)
        case .deinitComponent:
            guard let payload = encodedPayload,
                let object = try? decoder.decode(ComponentDeinitPayload.self, from: payload) else { return nil }
            self = .deinitComponent(origin: object.origin, identity: object.identity)
        case .event:
            guard let payload = encodedPayload,
                let object = try? decoder.decode(ComponentEventPayload.self, from: payload) else { return nil }
            self = .event(origin: object.origin, identity: object.identity, event: object.event)
        case .reset:
            guard let payload = encodedPayload,
                let object = try? decoder.decode(ComponentMessageOrigin.self, from: payload) else { return nil }
            self = .reset(origin: object)

        }
    }
    
    public func encodePayload() -> Data? {
        let encoder = JSONEncoder()
        
        switch self {
        case let .initComponent(origin: origin, type: type, identity: identity):
            let payload = ComponentInitPayload(origin: origin, type: type, identity: identity)
            return try! encoder.encode(payload)
        case let .deinitComponent(origin: origin, identity: identity):
            let payload = ComponentDeinitPayload(origin: origin, identity: identity)
            return try! encoder.encode(payload)
        case let .event(origin: origin, identity: identity, event: event):
            let payload = ComponentEventPayload(origin: origin, identity: identity, event: event)
            return try! encoder.encode(payload)
        case let .reset(origin: origin):
            let payload = origin
            return try! encoder.encode(payload)
        }
    }
}

public extension ComponentMessage {
    
    func send(to destination: Environment = .live) {
        switch destination {
        case .live:
            guard let proxy = PlaygroundPage.current.liveView as? PlaygroundRemoteLiveViewProxy else { return }
            proxy.send(self.playgroundValue)
        case .user:
            guard let liveViewMessageHandler = PlaygroundPage.current.liveView as? PlaygroundLiveViewMessageHandler else { return }
            liveViewMessageHandler.send(self.playgroundValue)
        }
    }
}
