import Flutter
import UIKit
import Atomics
import OSLog
import Foundation
import Collections

public class SwiftAgineekPadPlugin: NSObject, FlutterPlugin, FlutterStreamHandler {

    public let tag: String = "SwiftAgineekPadPlugin" //Change the name
    public let logger = Logger()
    public var eventSink: FlutterEventSink? = nil
    public var nsdConnection: NsdConnection? = nil
    public var pads:[Pad] = []
    public var playerColors: [Int: String] = [:] //Havent test dictionaries but looks fine
    public let serverSocket: NWListener? = nil
    
    public var serverThread: AgineekServerThread? = nil

  // Found these resources for eventchannel, methodchannel, streamhandler
  // https://stackoverflow.com/questions/59693268/how-to-stream-data-from-swift-to-flutter-using-event-channel
  // https://github.com/blackmenthor/flutter-native-sample/blob/master/ios/Runner/StreamHandler.swift

  // private lazy var channel: FlutterMethodChannel
  // private lazy var eventChannel: FlutterEventChannel

  // public var eventSink: FlutterEventSink? = nil

  // maybe need this package? 
  // https://pub.dev/packages/flutter_nsd
  // public var nsdConnection: flutterNsd? = nil

  // this is how you do atomic booleans
  // private val serviceStarted = ManagedAtomic<Bool>(false)

  // For linked hash map i found this
  // https://stackoverflow.com/questions/28633703/insertion-order-dictionary-like-javas-linkedhashmap-in-swift

  // this is a github with java-util implemented in swift, maybe we can use this
  // https://github.com/SwiftJava/java_util/tree/master/Sources

  // for server socket
  // https://github.com/swiftsocket/SwiftSocket

    public static func register(with registrar: FlutterPluginRegistrar) {
    //Cannot sent message to debug because -> "Instance member 'logger' cannot be used on type 'SwiftAgineekPadPlugin'"
    //logger.debug("\(self.tag), register function called")
    
    //Things missing, tho the service is registered after
    //context = flutterPluginBinding.getApplcationContext()
    //nsdConnection = NsdConnection(context)
    
        let channel = FlutterMethodChannel(name: "agineek_pad_plugin", binaryMessenger: registrar.messenger())
        registrar.addMethodCallDelegate(SwiftAgineekPadPlugin(), channel: channel)
        let eventChannel = FlutterEventChannel(name: "agineek_pad_plugin/pads", binaryMessenger: registrar.messenger())
        eventChannel.setStreamHandler(SwiftAgineekPadPlugin())
        
    }

    public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
        logger.debug("\(self.tag), handle function called")
        
        switch call.method {
        case "startConnectPads":
            pads = []
            eventSink?(pads.count)
            logger.debug("\(self.tag), Pads in set: \(pads.count)")
            if serverThread == nil {
                serverThread = AgineekServerThread(self)
                serverThread?.name = "ServerThread"
            }
            if serverThread?.isExecuting != true {
                serverThread.start()
            }
            result(true)
            
        case "stopConnectPads":
            if serverThread?.isExecuting == true {
                serverThread?.sleep()
                logger.debug("\(self.tag), \(serverSocket?.debugDescription)")
                serverThread = nil //Not sure about this
            }
            serverSocket?.cancel()
            if pads.count == 0 {
                eventSink?(nil)
            }
            result(false)
            
        case "startGame":
            //This case is INCOMPLETE <- Read please
            //This part haven't been finished in kotlin
            
            //Step 0.1
            var count = 100
            for pad in pads {
                pad.uuid = count.description
                count += 100
            }
            
            //Here things get weird, testing things?
            //Step 1
            var p1: Player = Player("Alice", "blue")
            var players: [Player] = []
            players.append(p1)
            
            //Step 2
            var padSequence: PadSequence = PadSequence(GameMode.Random, GameType.Single, pads, players)
            
            //Step 2.1
            padSequence.sendSeqToPads(padSequence)
            
            //Step 3
            var game: Game = Game(padSequence, 20000, 1000, 1000)
            game.start()
            
            result(true)
            
        case "stopGame":
            result(FlutterMethodNotImplemented)
            
        case "addPlayer":
            var id = call.arguments["id"] as! Int
            var hitColor = call.arguments["hitColor"] as! String
            playerColors[id] = hitColor
            result(true)
            
        case "removePlayer":
            var id = call.arguments["id"] as! Int
            playerColors[id] = nil //This removes [id: hitColor] value inside playerColors
             result(true)
            
        default:
            logger.debug("\(self.tag), method \(call.method) not defined in handle")
            result(FlutterMethodNotImplemented)
        }
    }
    
    public func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? {
        eventSink = events
        logger.debug("\(self.tag), onListen function called with event = \(self.eventSink.debugDescription)")
        self.stateEvent()
        eventsSink(nil)
        return nil
    }
    
    public func onCancel(withArguments arguments: Any?) -> FlutterError? {
        logger.debug("\(self.tag), onCancel function called")
        eventSink = nil
        return nil
    }
    
    //Maybe gonna be used
    public func stateEvent(){
        if eventSink == nil {
            logger.debug("eventSink is null/nil")
            return
        }
    }
    
    public func broadcast(pad: Pad, intent: String, uuid: String){
        logger.debug("\(tag), broadcast(\(pad), \(intent), \(uuid)) function called")
        //var i = Intent()
        //Google-fu tells me that there is not equivalent of intent in Swift
        if intent == "PAD_CONNECTION_CHANGE" {
            logger.debug("\(tag), Broadcasting connection change")
            pads.append(pad)
        }
        else if intent == "PAD_HIT" {
            logger.debug("\(tag), Pad \(pad.uuid) hit \(pads)") //$pads ???
        }
    }
    
    //DEBUG METHOD
    public func listPadUUIDs() {
        for pad in pads{
            logger.debug("Here is a Oad, Pad \(pad.uuid)")
        }
    }
}

public class AgineekServerThread: Thread{
    
    public let tag: String = "AgineekServerThread"
    public let logger = Logger()
    public var plugin: SwiftAgineekPadPlugin
    public var running = ManagedAtomic<Bool>(false)
    public var socket:NWConnection? = nil
    
    init(plugin: SwiftAgineekPadPlugin){
        self.plugin = plugin
    }
    
    public func isRunning() -> Boolen {
        return running.load(ordering: AtomicLoadOrdering.relaxed)
    }
    
    override public func main() {
        logger.debug("\(tag), run() method called")
        running.store(true, ordering: AtomicStoreOrdering.relaxed)
        while running.load(ordering: AtomicLoadOrdering.relaxed) && current.isExecuting {
            plugin.serverSocket = try! NWListener(using: .tcp, on: self.port)
            var localPort = plugin.serverSocket?.port
            plugin.nsdConnection?.registerService(localPort)
            
            while (Thread.current.isExecuting) {
                logger.debug("\(tag), ServerSocket created, awaiting connection")
                plugin.serverSocket?.stateUpdateHandler = stateChange(to:)
                plugin.serverSocket?.newConnectionHandler = didAccept(for:)
                var pad = Pad(socket, plugin) //Pad argument needs to be changed
                pad.connect()
                plugin.pads.append(pad)
                logger.debug("\(tag), Pad size: \(plugin.pads.count)")
                self.plugin.eventSink?(self.plugin.pads.count)
            }
            
        }
    }
    
    public func stateChange(to newState: NWListener.State){
        logger.debug("NWListener stateChange: \(newState)")
    }
    
    public func didAccept(for connection: NWConnection){
        logger.debug("NWListener didAccept: \(connection)")
        socket = connection
    }
    
    override public func cancel() {
        logger.debug("\(tag), cancel() function called")
        running.store(false, ordering: AtomicStoreOrdering.relaxed)
        logger.debug("Debug-Level2, after running.store(false)")
        super.cancel()
        logger.debug("Debug-Level2, after super.cancel()")
        
        plugin.eventSink?(plugin.pads.size)
        plugin.listPadUUIDs()
        logger.debug("Debug-Level2, after for loop of pads, shutting them down")
        
        if plugin.serverSocket?.state == NWListener.State.ready {
            plugin.nsdConnection?.tearDown()
        }
        logger.debug("\(tag), Closed nsd broadcast")
        
        plugin.serverSocket?.cancel()
    }
    
}
