//
//  SocketController.swift
//  wifiprint
//
//  Created by woosimsystems on 2021/05/18.
//  Copyright © 2021 Woosim Systems. All rights reserved.
//

import Foundation

let WSSocketDidConnectNotification = "WSSocketConnect"
let WSSocketDidDisconnectNotification = "WSSocketDisconnect"
let WSSocketDataReceivedNotification = "WSSocketDataReceived"

class SocketController: NSObject, StreamDelegate {
    
    var host: String?
    var port: Int?
    var inputStream: InputStream?
    var outputStream: OutputStream?
    var outData: NSMutableData?
    var inData: NSMutableData?

    // connect to host and set up the input and output stream
    func connect(host: String, port: Int) -> Bool {
        self.host = host
        self.port = port
        Stream.getStreamsToHost(withName: host, port: port, inputStream: &inputStream, outputStream: &outputStream)
        guard inputStream != nil && outputStream != nil else { return false }
        inputStream!.delegate = self
        inputStream!.schedule(in: .current, forMode: .default)
        inputStream!.open()
        outputStream!.delegate = self
        outputStream!.schedule(in: .current, forMode: .default)
        outputStream!.open()
        return true
    }
    
    // disconnect host
    func disconnect() {
        inputStream?.close()
        inputStream?.remove(from: .current, forMode: .default)
        inputStream?.delegate = nil
        inputStream = nil
        outputStream?.close()
        outputStream?.remove(from: .current, forMode: .default)
        outputStream?.delegate = nil
        outputStream = nil
        outData = nil
        inData = nil
        NotificationCenter.default.post(name: NSNotification.Name(rawValue: WSSocketDidDisconnectNotification), object: nil)
    }
    
    // write data to the output stream
    func writeData(_ data: Data) {
        guard outputStream != nil else { return }
        if outData == nil {
            outData = NSMutableData()
        }
        outData!.append(data)
        writeStream()
    }
    
    private func writeStream() {
        while (outputStream?.hasSpaceAvailable ?? false) && (outData?.length ?? 0) > 0 {
            let bytePointer = outData!.bytes.bindMemory(to: UInt8.self, capacity: outData!.length)
            let bytesWritten = outputStream!.write(bytePointer, maxLength: outData!.length)
            if bytesWritten == -1 {
                print("Write Error")
                break
            } else if bytesWritten > 0 {
                outData!.replaceBytes(in: NSMakeRange(0, bytesWritten), withBytes: nil, length: 0)
            }
        }
    }

    // read data from inData
    func readData(bytesToRead: Int) -> Data {
        var data = Data()
        if (inData?.length ?? 0) >= bytesToRead {
            let range = NSMakeRange(0, bytesToRead)
            data = inData!.subdata(with: range)
            inData?.replaceBytes(in: range, withBytes: nil, length: 0)
        }
        return data;
    }
    
    // get number of bytes read into local buffer
    func readBytesAvailable() -> Int {
        return inData?.length ?? 0
    }
    

    // MARK: - StreamDelegate
    func stream(_ stream: Stream, handle eventCode: Stream.Event) {
        switch eventCode {
        case .openCompleted:
            if stream == outputStream {
                NotificationCenter.default.post(name: NSNotification.Name(rawValue: WSSocketDidConnectNotification), object: nil)
            }
        case .hasBytesAvailable:
            if stream == inputStream {
                readStream()
            }
        case .hasSpaceAvailable:
            if stream == outputStream {
                writeStream()
            }
        case .errorOccurred:
            disconnect()
        case .endEncountered:
            break
        default:
            break
        }
    }
    
    // read data from input stream and send notification
    private func readStream() {
        let bufferSize = 512
        let buffer = UnsafeMutablePointer<UInt8>.allocate(capacity: bufferSize)
        while inputStream?.hasBytesAvailable ?? false {
            let bytesRead = inputStream!.read(buffer, maxLength: bufferSize)
            if inData == nil {
                inData = NSMutableData()
            }
            inData!.append(buffer, length: bytesRead)
        }
        NotificationCenter.default.post(name: NSNotification.Name(rawValue: WSSocketDataReceivedNotification), object: nil)
    }
}
