/**
 * Copyright 2024 Beijing Baolande Software Corporation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * Runtime Library Exception to the Apache 2.0 License:
 *
 * As an exception, if you use this Software to compile your source code and
 * portions of this Software are embedded into the binary product as a result,
 * you may redistribute such product without providing attribution as would
 * otherwise be required by Sections 4(a), 4(b) and 4(d) of the License.
 */

package hyperion.transport

/**
 * Socket连接的封装
 *
 * @author yangfuping
 */
public open class SocketConnection <: Connection {
    protected static let logger = LoggerFactory.getLogger("transport")

    private let id: Int64

    private static let idGenerator = AtomicInt64(0)

    private let socket: StreamingSocket

    private var readTimeout: ?Duration = None

    private var asyncWrite: Bool = true

    private let remoteAddress: SocketAddress

    private let localAddress: SocketAddress

    private let listeners = LinkedList<ConnectionLister>()

    private let writeLock = Mutex()

    private let readCompletionHandler = AtomicOptionReference<ReadCompletionHandler>()

    private let writeEventLoop = AtomicOptionReference<WriteEventLoopHandler>()

    private var closed = false

    protected var invalidated = false

    private let expirableDelegate = ExpirableItem()

    public init(socket: StreamingSocket) {
        this.id = idGenerator.fetchAdd(1)
        this.socket = socket
        // 避免Socket被对方关闭后,调用hashcode和equals比较连接出错
        this.remoteAddress = socket.remoteAddress
        this.localAddress = socket.localAddress
    }

    public func config(options: TcpSocketOptions): Unit {
        if (let Some(readTimeout) <- options.readTimeout) {
            this.readTimeout = readTimeout
        }

        if (let Some(idleTimeout) <- options.idleTimeout) {
            this.idleTimeout = idleTimeout
        }

        this.asyncWrite = options.asyncWrite
        if (asyncWrite) {
            let created = WriteEventLoopHandler(this, options)
            writeEventLoop.compareAndSwap(None, created)
        }
    }

    public mut prop idleTimeout: ?Duration {
        get() {
            return expirableDelegate.idleTimeout
        }
        set(value) {
            expirableDelegate.idleTimeout = value
        }
    }

    public func isAsyncWrite(): Bool {
        return this.asyncWrite
    }

    public func cancelExpireTime(): Unit {
        expirableDelegate.cancelExpireTime()
    }

    public func setExpireTimeIfAbsent(): Unit {
        expirableDelegate.setExpireTimeIfAbsent()
    }

    public func setExpireTime(): Unit {
        expirableDelegate.setExpireTime()
    }

    public func isExpired(): Bool {
        return expirableDelegate.isExpired()
    }

    public func addListener(listener: ConnectionLister): Unit {
        checkOpen()
        listeners.addLast(listener)
    }

    public func getReadCompletionHandler(): ?ReadCompletionHandler {
        return readCompletionHandler.load()
    }

    private func createOrLoadReadCompletionHandler(): ReadCompletionHandler {
        if (let Some(handler) <- readCompletionHandler.load()) {
            return handler
        }

        var handler = ReadCompletionHandler()
        if (readCompletionHandler.compareAndSwap(None, handler)) {
            return handler
        } else {
            return readCompletionHandler
                .load()
                .getOrThrow({
                    => return TransportException("Failure to obtain ReadCompletionHandler")
                })
        }
    }

    public func addReadCompletionListener(completionListener: ReadCompletionListener): Unit {
        createOrLoadReadCompletionHandler().addListener(completionListener)
    }

    public func removeReadCompletionHandler() {
        if (let Some(handler) <- readCompletionHandler.load()) {
            readCompletionHandler.compareAndSwap(handler, None)
        }
    }

    public func realWrite(buffer: Array<Byte>): Unit {
        checkOpen()
        try {
            if (logger.isTraceEnabled()) {
                logger.log(LogLevel.TRACE, "Real write buffer[${0}..${buffer.size}] by ${this}")
            }

            socket.write(buffer)
        } catch (ex: SocketException | SocketTimeoutException | IOException) {
            close(markInvalid: true)
            logger.log(
                LogLevel.ERROR,
                "Failure to write message",
                ex
            )
            throw TransportException("Failure to write message", ex)
        }
    }

    public func realWrite(buffers: Array<Array<Byte>>): Unit {
        checkOpen()
        try {
            for (buffer in buffers) {
                if (logger.isTraceEnabled()) {
                    logger.log(LogLevel.TRACE, "Real write buffer[${0}..${buffer.size}] by ${this}")
                }
                socket.write(buffer)
            }
        } catch (ex: SocketException | SocketTimeoutException | IOException) {
            close(markInvalid: true)
            logger.log(
                LogLevel.ERROR,
                "Failure to write message",
                ex
            )
            throw TransportException("Failure to write message", ex)
        }
    }

    public func realWrite(buffer: Array<Byte>, promise: WriteBeforePromise): Unit {
        checkOpen()
        try {
            if (logger.isTraceEnabled()) {
                logger.log(LogLevel.TRACE, "Real write buffer[${0}..${buffer.size}] by ${this}")
            }

            promise.beforeWrite()
            socket.write(buffer)
        } catch (ex: SocketException | SocketTimeoutException | IOException) {
            close(markInvalid: true)
            logger.log(
                LogLevel.ERROR,
                "Failure to write message",
                ex
            )
            throw TransportException("Failure to write message", ex)
        }
    }

    public func realWrite(buffers: Array<Array<Byte>>, promise: WriteBeforePromise): Unit {
        checkOpen()
        try {
            promise.beforeWrite()
            for (buffer in buffers) {
                if (logger.isTraceEnabled()) {
                    logger.log(LogLevel.TRACE, "Real write buffer[${0}..${buffer.size}] by ${this}")
                }
                socket.write(buffer)
            }
        } catch (ex: SocketException | SocketTimeoutException | IOException) {
            close(markInvalid: true)
            logger.log(
                LogLevel.ERROR,
                "Failure to write message",
                ex
            )
            throw TransportException("Failure to write message", ex)
        }
    }

    private func doSyncWrite(buffer: Array<Byte>) {
        synchronized(writeLock) {
            realWrite(buffer)
        }
    }

    private func doSyncWrite(buffers: Array<Array<Byte>>) {
        synchronized(writeLock) {
            realWrite(buffers)
        }
    }

    private func doSyncWrite(buffer: Array<Byte>, promise: WriteBeforePromise) {
        synchronized(writeLock) {
            realWrite(buffer, promise)
        }
    }

    private func doSyncWrite(buffers: Array<Array<Byte>>, promise: WriteBeforePromise) {
        synchronized(writeLock) {
            realWrite(buffers, promise)
        }
    }

    private func doAsyncWrite(writeEvent: WriteEvent) {
        if (let Some(writeEventLoop) <- writeEventLoop.load()) {
            writeEventLoop.offerWriteEvent(writeEvent)
        } else {
            throw TransportException("Async write messsage failure, WriteEventLoopHandler is not initialized")
        }
    }

    public func write(buffer: Array<Byte>): Unit {
        checkOpen()

        if (logger.isTraceEnabled()) {
            logger.log(LogLevel.TRACE, "Write buffer[${0}..${buffer.size}] by ${this}")
        }

        if (asyncWrite) {
            doAsyncWrite(WriteEvent(buffer, promise: None))
        } else {
            doSyncWrite(buffer)
        }
    }

    public func write(buffer: Array<Byte>, startPos: Int64, length: Int64): Unit {
        checkOpen()

        if (startPos < 0) {
            throw IllegalArgumentException("Wrong start position: ${startPos}")
        }

        if (length < 0) {
            throw IllegalArgumentException("Wrong length: ${length}")
        }

        if (startPos + length > buffer.size) {
            throw IllegalArgumentException(
                "Index out of range: ${startPos + length},  start position: ${startPos}, length: ${length}, array size: ${buffer.size}"
            )
        }

        if (logger.isTraceEnabled()) {
            logger.log(LogLevel.TRACE, "Write buffer[${startPos}..${startPos + length}] by ${this}")
        }

        let slice = buffer[startPos..startPos + length]
        if (asyncWrite) {
            doAsyncWrite(WriteEvent(slice, promise: None))
        } else {
            doSyncWrite(buffer)
        }
    }

    public func write(buffer: Array<Byte>, promise: WriteBeforePromise): Unit {
        checkOpen()

        if (logger.isTraceEnabled()) {
            logger.log(LogLevel.TRACE, "Write buffer[${0}..${buffer.size}] by ${this}")
        }

        if (asyncWrite) {
            doAsyncWrite(WriteEvent(buffer, promise: promise))
        } else {
            doSyncWrite(buffer, promise)
        }
    }

    public func write(buffer: Array<Byte>, startPos: Int64, length: Int64, promise: WriteBeforePromise): Unit {
        checkOpen()

        if (startPos < 0) {
            throw IllegalArgumentException("Wrong start position: ${startPos}")
        }

        if (length < 0) {
            throw IllegalArgumentException("Wrong length: ${length}")
        }

        if (startPos + length > buffer.size) {
            throw IllegalArgumentException(
                "Index out of range: ${startPos + length},  start position: ${startPos}, length: ${length}, array size: ${buffer.size}"
            )
        }

        if (logger.isTraceEnabled()) {
            logger.log(LogLevel.TRACE, "Write buffer[${startPos}..${startPos + length}] by ${this}")
        }

        let slice = buffer[startPos..startPos + length]
        if (asyncWrite) {
            doAsyncWrite(WriteEvent(slice, promise: promise))
        } else {
            doSyncWrite(buffer, promise)
        }
    }

    public func gatheringWrite(gatheredBuffers: Array<Array<Byte>>): Unit {
        if (asyncWrite) {
            let writeItem = WriteEvent(gatheredBuffers, promise: None)
            doAsyncWrite(writeItem)
        } else {
            doSyncWrite(gatheredBuffers)
        }
    }

    public func gatheringWrite(gatheredBuffers: Array<Array<Byte>>, promise: WriteBeforePromise): Unit {
        if (asyncWrite) {
            let writeItem = WriteEvent(gatheredBuffers, promise: promise)
            doAsyncWrite(writeItem)
        } else {
            doSyncWrite(gatheredBuffers, promise)
        }
    }

    public func write(buffer: ByteBuffer, promise: WriteBeforePromise): Unit {
        write(buffer.toArray(), promise)
    }

    public func write(buffer: ByteBuffer): Unit {
        write(buffer.toArray())
    }

    public func gatheringWrite(gatheredBuffers: Array<ByteBuffer>): Unit {
        let complex = Array<Array<Byte>>(gatheredBuffers.size, repeat: Array<Byte>())
        for (i in 0..gatheredBuffers.size) {
            complex[i] = gatheredBuffers[i].toArray()
        }
        if (asyncWrite) {
            let writeItem = WriteEvent(complex, promise: None)
            doAsyncWrite(writeItem)
        } else {
            doSyncWrite(complex)
        }
    }

    public func gatheringWrite(gatheredBuffers: Array<ByteBuffer>, promise: WriteBeforePromise): Unit {
        let complex = Array<Array<Byte>>(gatheredBuffers.size, repeat: Array<Byte>())
        for (i in 0..gatheredBuffers.size) {
            complex[i] = gatheredBuffers[i].toArray()
        }

        if (asyncWrite) {
            let writeItem = WriteEvent(complex, promise: promise)
            doAsyncWrite(writeItem)
        } else {
            doSyncWrite(complex, promise)
        }
    }

    public func read(buffer: Array<Byte>): Int64 {
        checkOpen()

        try {
            // 仅读取一次
            let count = socket.read(buffer)

            if (logger.isTraceEnabled()) {
                logger.log(LogLevel.TRACE, "Read ${count} bytes by ${this}")
            }

            if (count == 0 || count == -1) {
                throw IOException("EOF encounted, read ${count} bytes")
            }

            return count
        } catch (ex: SocketException | IOException) {
            if (!isClosed()) {
                close(markInvalid: true)
                logger.log(
                    LogLevel.ERROR,
                    "Failure to read message on connection ${this}",
                    ex
                )
            } else {
                if (logger.isDebugEnabled()) {
                    logger.log(LogLevel.DEBUG, "Connection already closed: ${this}", ex)
                }
            }

            throw TransportException("Failure to read message on connection ${this}", ex)
        } catch (ex: SocketTimeoutException) {
            // 未读到数据,保留连接
            if (logger.isDebugEnabled()) {
                logger.log(
                    LogLevel.DEBUG,
                    "SocketTimeoutException occured while read messsage on connection ${this}",
                    ex
                )
            }

            // 抛出原始的SocketTimeoutException
            throw ex
        }
    }

    public func read(buffer: Array<Byte>, startPos: Int64, length: Int64): Int64 {
        checkOpen()

        if (startPos < 0) {
            throw IllegalArgumentException("Wrong start position: ${startPos}")
        }

        if (length < 0) {
            throw IllegalArgumentException("Wrong length: ${length}")
        }

        if (startPos + length > buffer.size) {
            throw IllegalArgumentException(
                "Index out of range: ${startPos + length},  start position: ${startPos}, length: ${length}, array size: ${buffer.size}"
            )
        }

        try {
            // 仅读取一次
            let slice = buffer[startPos..startPos + length]
            let count = socket.read(slice)

            if (logger.isTraceEnabled()) {
                logger.log(
                    LogLevel.TRACE,
                    "Read ${count} bytes fill into buffer[${startPos}..${startPos + length}] by ${this}"
                )
            }

            if (count == -1) {
                throw IOException("EOF encounted")
            }

            return count
        } catch (ex: SocketException | IOException) {
            if (!isClosed()) {
                close(markInvalid: true)
                logger.log(
                    LogLevel.ERROR,
                    "Failure to read message on connection ${this}",
                    ex
                )
            } else {
                if (logger.isDebugEnabled()) {
                    logger.log(LogLevel.DEBUG, "Connection already closed: ${this}", ex)
                }
            }

            throw TransportException("Failure to read message on connection ${this}", ex)
        } catch (ex: SocketTimeoutException) {
            // 未读到数据,保留连接
            if (logger.isDebugEnabled()) {
                logger.log(
                    LogLevel.DEBUG,
                    "SocketTimeoutException occured while read messsage on connection ${this}",
                    ex
                )
            }

            // 抛出原始的SocketTimeoutException
            throw ex
        }
    }

    public func readFully(buffer: Array<Byte>): Int64 {
        readFully(buffer, 0, buffer.size)
    }

    public func readFully(buffer: Array<Byte>, startPos: Int64, length: Int64): Int64 {
        checkOpen()

        if (startPos < 0) {
            throw IllegalArgumentException("Wrong start position: ${startPos}")
        }

        if (length < 0) {
            throw IllegalArgumentException("Wrong length: ${length}")
        }

        if (startPos + length > buffer.size) {
            throw IllegalArgumentException(
                "Index out of range: ${startPos + length},  start position: ${startPos}, length: ${length}, array size: ${buffer.size}"
            )
        }

        if (logger.isTraceEnabled()) {
            logger.log(LogLevel.TRACE, "Trying to fully fill buffer[${startPos}..${startPos + length}]")
        }

        var totalRedCount = 0
        var endPos = startPos + length
        try {
            // 循环多次,直到读取到需要的数据
            while (totalRedCount < length) {
                let slice = buffer[(startPos + totalRedCount)..endPos]
                let readCount = socket.read(slice)

                if (readCount == -1) {
                    throw IOException("EOF encounted")
                }
                totalRedCount = totalRedCount + readCount
            }

            if (logger.isTraceEnabled()) {
                logger.log(
                    LogLevel.TRACE,
                    "Fully filled buffer[${startPos}..${startPos + length}], read ${totalRedCount} bytes"
                )
            }

            return totalRedCount
        } catch (ex: SocketException | SocketTimeoutException | IOException) {
            if (!isClosed()) {
                close(markInvalid: true)
                logger.log(
                    LogLevel.ERROR,
                    "Could not fill buffer[${startPos}..${startPos + length}] use connection ${this}, buffer size: ${buffer.size}",
                    ex
                )
            } else {
                if (logger.isDebugEnabled()) {
                    logger.log(LogLevel.DEBUG, "Connection already closed: ${this}", ex)
                }
            }

            throw TransportException(
                "Could not fill buffer[${startPos}..${startPos + length}] use connection ${this}, buffer size: ${buffer.size}",
                ex
            )
        }
    }

    public func read(buffer: ByteBuffer): Int64 {
        let count = read(buffer.toArray())
        buffer.position(buffer.position() + count)
        return count
    }

    public func readFully(buffer: ByteBuffer): Int64 {
        let count = readFully(buffer.toArray())
        buffer.position(buffer.position() + count)
        return count
    }

    public func flush(): Unit {
        checkOpen()
        try {
            socket.flush()
        } catch (ioEx: IOException) {
            close(markInvalid: true)
            throw ioEx
        }
    }

    public func checkOpen(): Unit {
        if (closed) {
            throw TransportException("Connection ${this} closed")
        }

        if (socket.isClosed()) {
            close(markInvalid: true)
            throw TransportException("Connection ${this} closed")
        }
    }

    public func markInvalid(): Unit {
        this.invalidated = true
    }

    public func isInvalid(): Bool {
        return this.invalidated
    }

    public func setTimeoutInfinite(): Unit {
        if (!closed && !socket.isClosed()) {
            socket.readTimeout = None
        }
    }

    public func rollbackTimeout(): Unit {
        if (let Some(readTimeout) <- this.readTimeout) {
            if (!closed && !socket.isClosed()) {
                socket.readTimeout = readTimeout
            }
        }
    }

    public func isClosed(): Bool {
        return closed
    }

    public open func close(): Unit {
        internalClose()
    }

    public open func close(markInvalid!: Bool): Unit {
        if (markInvalid) {
            this.markInvalid()
        }

        internalClose()
    }

    protected open func internalClose() {
        try {
            if (!closed) {
                closed = true
                if (logger.isDebugEnabled()) {
                    logger.debug("Close ${this}")
                }

                socket.close()

                if (!listeners.isEmpty()) {
                    for (listener in listeners) {
                        listener.onClose()
                    }
                }
            }
        } catch (ex: Exception) {
            // ignore
        }
    }

    public open func toString() {
        return "SocketConnection[id: ${id}, socket: ${socket}]"
    }

    public open func simpleName() {
        if (let Some(localAddress) <- (localAddress as IPSocketAddress)) {
            if (let Some(remoteAddress) <- (remoteAddress as IPSocketAddress)) {
                return "L${localAddress.port}-R${remoteAddress.port}"
            }
        }
        return "L${localAddress}-R${remoteAddress}"
    }

    @OverflowWrapping
    public override func hashCode(): Int64 {
        var hashCode = 57 * id
        hashCode = hashCode + remoteAddress.hashCode()
        hashCode = hashCode + localAddress.hashCode()
        return hashCode
    }

    public operator override func ==(other: Connection): Bool {
        if (let Some(otherConn) <- (other as SocketConnection)) {
            if (id != otherConn.id) {
                return false
            }

            if (remoteAddress != otherConn.remoteAddress) {
                return false
            }

            if (localAddress != otherConn.localAddress) {
                return false
            }

            return true
        }

        return false
    }

    public operator override func !=(other: Connection): Bool {
        if (let Some(otherConn) <- (other as SocketConnection)) {
            if (id != otherConn.id) {
                return false
            }

            if (remoteAddress != otherConn.remoteAddress) {
                return true
            }

            if (localAddress != otherConn.localAddress) {
                return true
            }

            return false
        }

        return true
    }

    /**
     * 配置TcpSocket相关属性
     *
     * @param socket TcpSocket
     * @param socketOptions TcpSocketOptions
     */
    public static func configSocketOptions(socket: TcpSocket, socketOptions: TcpSocketOptions) {
        if (let Some(receiveBufferSize) <- socketOptions.receiveBufferSize) {
            if (receiveBufferSize > 0) {
                socket.receiveBufferSize = receiveBufferSize
            }
        }

        if (let Some(sendBufferSize) <- socketOptions.sendBufferSize) {
            if (sendBufferSize > 0) {
                socket.sendBufferSize = sendBufferSize
            }
        }

        if (let Some(readTimeout) <- socketOptions.readTimeout) {
            socket.readTimeout = readTimeout
        }

        if (let Some(writeTimeout) <- socketOptions.writeTimeout) {
            socket.writeTimeout = writeTimeout
        }

        if (let Some(noDelay) <- socketOptions.noDelay) {
            socket.noDelay = noDelay
        }

        if (let Some(linger) <- socketOptions.linger) {
            socket.linger = linger
        }
    }
}