import Foundation
import Capacitor
import Network

/**
 * Please read the Capacitor iOS Plugin Development Guide
 * here: https://capacitorjs.com/docs/plugins/ios
 *
 * https://rderik.com/blog/building-a-server-client-aplication-using-apple-s-network-framework/
 */
@available(iOS 12.0, *)
@objc(CapacitorTcp)
public class CapacitorTcp: CAPPlugin {
  private var sockets: [Int: TcpSocket] = [Int: TcpSocket]()
  private var nextSocketId: Int = 0

  @objc func echo(_ call: CAPPluginCall) {
      let value = call.getString("value") ?? ""
      call.success([
          "value": value
      ])
  }

  @objc func create(_ call: CAPPluginCall) {
    let properties = call.getObject("properties")
    let socket = TcpSocket.init(id: nextSocketId, properties: properties)
    nextSocketId+=1
    sockets[socket.socketId] = socket;

    print("[TCP] Create Socket \(socket.socketId)")

    call.success(["socketId":socket.socketId,"ipv4":"","ipv6":"" ])
  }

  @objc func update(_ call: CAPPluginCall) {
    let socketId:Int = call.getInt("socketId") ?? -1
    let properties:[String:Any] = call.getObject("properties") ?? [String:Any]()
    let socket = sockets[socketId]
    if (socket != nil){
        socket?.setProperties(properties)
        call.success()
    } else {
        call.error("no socket found")
    }
  }

  @objc func setPaused(_ call: CAPPluginCall) {
    let socketId:Int = call.getInt("socketId") ?? -1
    let paused = call.getBool("paused")
    let socket = sockets[socketId]
    if (socket != nil && paused != nil){
        //socket?.setPaused(paused ?? false) // TODO
        call.success()
    } else {
        call.error("setPaused failed")
    }
  }

  @objc func setKeepAlive(_ call: CAPPluginCall) {
    let socketId:Int = call.getInt("socketId") ?? -1
    let enable = call.getBool("enable")
    let socket = sockets[socketId]
    if (socket != nil && enable != nil){
        //socket?.setKeepAlive(enable ?? false) // TODO
        call.success()
    } else {
        call.error("setKeepAlive failed")
    }
  }

  @objc func setNoDelay(_ call: CAPPluginCall) {
    let socketId:Int = call.getInt("socketId") ?? -1
    let noDelay = call.getBool("noDelay")
    let socket = sockets[socketId]
    if (socket != nil && noDelay != nil){
        //socket?.setNoDelay(noDelay ?? false) // TODO
        call.success()
    } else {
        call.error("setNoDelay failed")
    }
  }

  @objc func connect(_ call: CAPPluginCall) {
    let socketId:Int = call.getInt("socketId") ?? -1
    let peerAddress = call.getString("peerAddress")
    let peerPort = call.getInt("peerPort") ?? -1
    let socket = sockets[socketId]

    if (socket != nil && peerAddress != nil && peerPort != -1) {
      print("[TCP] Connect Socket IP: " + peerAddress!)
      print("[TCP] Connect Socket IP: \(peerPort)")
      socket?.connect(peerAddress: peerAddress!, peerPort: peerPort)
      call.success()
    } else {
      call.reject("connect failed")
    }
  }

  @objc func disconnect(_ call: CAPPluginCall) {
    let socketId:Int = call.getInt("socketId") ?? -1
    let socket = sockets[socketId]

    if (socket != nil) {
      socket?.disconnect()

      call.success()
    } else {
      call.reject("disconnect failed")
    }
  }

  @objc func secure(_ call: CAPPluginCall) {
    // TODO: Implement
    call.success()
  }

  @objc func send(_ call: CAPPluginCall) {
    let socketId:Int = call.getInt("socketId") ?? -1
    let data = call.getString("data")
    let socket = sockets[socketId]
    print("[TCP] Send Message on socket id: \(socketId)")
    if (socket != nil) {
      print("[TCP] Send Message: " + data!)
      socket?.sendMessage(message: data!)

      call.success()
    } else {
      call.reject("send failed")
    }
  }

  @objc func close(_ call: CAPPluginCall) {
    let socketId:Int = call.getInt("socketId") ?? -1
    let socket = sockets[socketId]

    if (socket != nil) {
      socket?.disconnect() // TODO: Different from disconnect?

      call.success()
    } else {
      call.reject("close failed")
    }
  }

  @objc func getInfo(_ call: CAPPluginCall) {
    let socketId:Int = call.getInt("socketId") ?? -1
    let socket = sockets[socketId]
    if(socket == nil) {
        call.error("Invalid Argument")
        return
    }
    call.success(socket?.getInfo() ?? [String:Any]())
  }

  @objc func getSockets(_ call: CAPPluginCall) {
    var socketsInfo = [Any]()
    for (_, socket) in sockets {
        socketsInfo.append(socket.getInfo())
    }
    call.success(["sockets":socketsInfo])
  }

  private class ClientConnection {
    let nwConnection: NWConnection
    let queue = DispatchQueue(label: "Client connection Q")

    init(nwConnection: NWConnection) {
        self.nwConnection = nwConnection
    }

    var didStopCallback: ((Error?) -> Void)? = nil

    func start() {
        print("connection will start")
        nwConnection.stateUpdateHandler = stateDidChange(to:)
        setupReceive()
        nwConnection.start(queue: queue)
    }

    private func stateDidChange(to state: NWConnection.State) {
        switch state {
        case .waiting(let error):
            connectionDidFail(error: error)
        case .ready:
            print("Client connection ready")
        case .failed(let error):
            connectionDidFail(error: error)
        default:
            break
        }
    }

    private func setupReceive() {
        nwConnection.receive(minimumIncompleteLength: 1, maximumLength: 65536) { (data, _, isComplete, error) in
            if let data = data, !data.isEmpty {
                let message = String(data: data, encoding: .utf8)
                print("connection did receive, data: \(data as NSData) string: \(message ?? "-" )")
            }
            if isComplete {
                self.connectionDidEnd()
            } else if let error = error {
                self.connectionDidFail(error: error)
            } else {
                self.setupReceive()
            }
        }
    }

    func send(data: Data) {
        nwConnection.send(content: data, completion: .contentProcessed( { error in
            if let error = error {
                self.connectionDidFail(error: error)
                return
            }
                print("connection did send, data: \(data as NSData)")
        }))
    }

    func stop() {
        print("connection will stop")
        stop(error: nil)
    }

    private func connectionDidFail(error: Error) {
        print("connection did fail, error: \(error)")
        self.stop(error: error)
    }

    private func connectionDidEnd() {
        print("connection did end")
        self.stop(error: nil)
    }

    private func stop(error: Error?) {
        self.nwConnection.stateUpdateHandler = nil
        self.nwConnection.cancel()
        if let didStopCallback = self.didStopCallback {
            self.didStopCallback = nil
            didStopCallback(error)
        }
    }
  }

  private class TcpClient {
    let peerAddress: String
    let peerPort: Int
    let socketId: Int
    let connection: ClientConnection
    var connected: Bool

    init(socketId: Int, peerAddress: String, peerPort: Int) {
      self.peerAddress = peerAddress
      self.peerPort = peerPort
      self.socketId = socketId
      self.connected = false
      let serverEndpoint = NWEndpoint.Host(peerAddress)
      let portEndpoint = NWEndpoint.Port(rawValue: UInt16(peerPort))
      let nwConnection = NWConnection(host: serverEndpoint, port: portEndpoint!, using: .tcp)
      self.connection = ClientConnection(nwConnection: nwConnection)
    }

    func start() {
      print("Client started \(self.peerAddress) \(self.peerPort)")
      connection.didStopCallback = didStopCallback(error:)
      connection.start()
      self.connected = true
    }

    func stop() {
        connection.stop()
    }

    func send(data: Data) {
        connection.send(data: data)
    }

    func didStopCallback(error: Error?) {
      print("in didStopCallback")
      print("should remove socket \(self.socketId)")
      // TODO: Remove the socket.
      self.connected = false
    }
  }

  private class TcpSocket {
    let socketId: Int
    var client: TcpClient?
    private var name: String
    private var bufferSize: NSNumber
    var paused: Bool

    init (id: Int , properties: [String:Any]?) {
      self.socketId = id
      self.bufferSize = 4096
      self.name = ""
      self.paused = false
      self.setProperties(properties ?? [String:Any]())
    }

    func setProperties(_ properties:[String: Any]){
      name = properties["name"] as? String ?? name
      bufferSize = properties["bufferSize"] as? NSNumber ?? bufferSize
    }

    func connect(peerAddress: String, peerPort: Int) {
      self.client = TcpClient.init(socketId: self.socketId, peerAddress: peerAddress, peerPort: peerPort)
      self.client?.start()
    }

    func disconnect() {
      self.client?.stop()
    }

    func sendMessage(message: String) {
      let data: Data? = message.data(using: .utf8)
      self.client?.send(data: data!)
    }

    func getInfo() -> [String: Any] {
      var socketInfo: [String: Any] = ["socketId":socketId,"name":name,"bufferSize":bufferSize,"paused":paused,"connected":self.client?.connected]
      return socketInfo
    }
  }

}
