/**
 * 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 redis_sdk.client

public abstract class RedisClientConfigBase {
    private var respVersionVal: ?ProtocolVersion = None

    private var userVal: ?String = None

    private var passwordVal: ?Array<Byte> = None

    private var clientNameVal: ?String = None

    /**
     * 发送Redis命令后,等待响应到达的超时时间
     */
    private var waitResponseTimeoutVal = Duration.minute * 5

    // 默认300毫秒
    private var shutdownTimeoutVal: Duration = Duration.millisecond * 300

    private var waitConnectionTimeoutVal: Duration = Duration.second * 30

    private var checkSubscribeMessageIntervalVal: Duration = Duration.second * 30

    // 每个Socket连接使用专有的写线程
    private var asyncWriteVal: Bool = true

    // 写队列长度,asyncWrite为true时使用
    private var writeQueueSizeVal: Int64 = 8192

    // 写缓冲区大小,asyncWrite为true时使用,可以减少Socket write系统调用
    private var writeBufferSizeVal: Int64 = 8192

    // 为每个连接固定一个读数据的线程
    private var stickyReadVal: Bool = true

    // 在读线程上执行编解码
    private var execOnReadThreadVal: Bool = true

    // 将要处理的消息报文从读缓存通过片的方式分割出来,可以减少数组的拷贝
    private var sliceExceedBufferVal: Bool = true

    /**
     * 能处理的最大消息长度
     */
    private var maxMessageSizeInBytesVal: Int64 = 64 * 1024 * 1024

    private var receiveBufferSizeVal: ?Int64 = None

    private var sendBufferSizeVal: ?Int64 = None

    private var noDelayVal: ?Bool = None

    private var lingerVal: ?Duration = None

    private var readTimeoutVal: ?Duration = None

    private var writeTimeoutVal: ?Duration = None

    private var idleTimeoutVal: Duration = Duration.minute * 20

    /**
     * 默认分配的ByteBuffer的容量
     *
     */
    private var bufferAllocateSizeVal: Int64 = 8192

    /**
     * 是否将ByteBuffer缓存到池中
     *
     */
    private var usePooledBufferAllocatorVal: Bool = false

    /**
     * 最大缓存的ByteBuffer的数量
     *
     */
    private var maxPooledBuffersVal: Int64 = 2048

    private var threadPoolNameVal: String = "RedisClient"

    private var threadPoolQueueSizeVal: Int64 = 4096

    private var maxThreadsVal: Int64 = 256

    private var minThreadsVal: Int64 = 0

    private var threadIdleTimeoutVal: Duration = Duration.minute * 2

    /**
     * 持久化订阅的最大恢复时间
     */
    private var maxSubscriberRecoverDurationVal = Duration.minute * 20

    /**
     * 持久化订阅每分钟尝试的恢复次数
     */
    private var recoverAttemptsPerMinuteVal = 3

    // 使用允许取出的连接被多个线程并发操作的连接池
    private var useNonExclusiveObjectPoolVal: Bool = false

    private var objectPoolEvictionIntervalsVal: ?Duration = None

    private var objectPoolLifoVal: Bool = true

    private var testOnCreateVal: Bool = false

    private var testOnBorrowVal: Bool = true

    private var testOnReturnVal: Bool = false

    private var testWhileIdleVal: Bool = false

    public mut prop respVersion: ?ProtocolVersion {
        get() {
            return respVersionVal
        }
        set(value) {
            this.respVersionVal = value
        }
    }

    public mut prop user: ?String {
        get() {
            return userVal
        }
        set(value) {
            this.userVal = value
        }
    }

    public mut prop password: ?Array<Byte> {
        get() {
            return passwordVal
        }
        set(value) {
            this.passwordVal = value
        }
    }

    public mut prop clientName: ?String {
        get() {
            return clientNameVal
        }
        set(value) {
            this.clientNameVal = value
        }
    }

    public mut prop waitResponseTimeout: Duration {
        get() {
            return waitResponseTimeoutVal
        }
        set(value) {
            if (value > Duration.Zero) {
                this.waitResponseTimeoutVal = value
            }
        }
    }

    public mut prop shutdownTimeout: Duration {
        get() {
            return shutdownTimeoutVal
        }
        set(value) {
            this.shutdownTimeoutVal = value
        }
    }

    public mut prop waitConnectionTimeout: Duration {
        get() {
            return waitConnectionTimeoutVal
        }
        set(value) {
            this.waitConnectionTimeoutVal = value
        }
    }

    public mut prop checkSubscribeMessageInterval: Duration {
        get() {
            return checkSubscribeMessageIntervalVal
        }
        set(value) {
            this.checkSubscribeMessageIntervalVal = value
        }
    }

    public mut prop asyncWrite: Bool {
        get() {
            return asyncWriteVal
        }
        set(value) {
            this.asyncWriteVal = value
        }
    }

    public mut prop writeQueueSize: Int64 {
        get() {
            writeQueueSizeVal
        }
        set(v) {
            if (v > 0) {
                this.writeQueueSizeVal = v
            }
        }
    }

    public mut prop writeBufferSize: Int64 {
        get() {
            writeBufferSizeVal
        }
        set(v) {
            if (v > 0) {
                this.writeBufferSizeVal = v
            }
        }
    }

    public mut prop stickyRead: Bool {
        get() {
            stickyReadVal
        }
        set(v) {
            this.stickyReadVal = v
        }
    }

    public mut prop execOnReadThread: Bool {
        get() {
            execOnReadThreadVal
        }
        set(v) {
            this.execOnReadThreadVal = v
        }
    }

    public mut prop sliceExceedBuffer: Bool {
        get() {
            sliceExceedBufferVal
        }
        set(v) {
            this.sliceExceedBufferVal = v
        }
    }

    public mut prop maxMessageSizeInBytes: Int64 {
        get() {
            maxMessageSizeInBytesVal
        }
        set(v) {
            if (v > 0) {
                this.maxMessageSizeInBytesVal = v
            }
        }
    }

    public mut prop receiveBufferSize: ?Int64 {
        get() {
            return receiveBufferSizeVal
        }
        set(value) {
            this.receiveBufferSizeVal = value
        }
    }

    public mut prop sendBufferSize: ?Int64 {
        get() {
            return sendBufferSizeVal
        }
        set(value) {
            this.sendBufferSizeVal = value
        }
    }

    public mut prop noDelay: ?Bool {
        get() {
            return noDelayVal
        }
        set(value) {
            this.noDelayVal = value
        }
    }

    public mut prop linger: ?Duration {
        get() {
            return lingerVal
        }
        set(value) {
            this.lingerVal = value
        }
    }

    public mut prop readTimeout: ?Duration {
        get() {
            return readTimeoutVal
        }
        set(value) {
            this.readTimeoutVal = value
        }
    }

    public mut prop writeTimeout: ?Duration {
        get() {
            return writeTimeoutVal
        }
        set(value) {
            this.writeTimeoutVal = value
        }
    }

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

    public mut prop bufferAllocateSize: Int64 {
        get() {
            bufferAllocateSizeVal
        }
        set(v) {
            if (v > 0 && v < maxMessageSizeInBytesVal) {
                this.bufferAllocateSizeVal = v
            }
        }
    }

    public mut prop usePooledBufferAllocator: Bool {
        get() {
            usePooledBufferAllocatorVal
        }
        set(v) {
            this.usePooledBufferAllocatorVal = v
        }
    }

    public mut prop maxPooledBuffers: Int64 {
        get() {
            maxPooledBuffersVal
        }
        set(v) {
            if (v > 0) {
                this.maxPooledBuffersVal = v
            }
        }
    }

    public mut prop threadPoolName: String {
        get() {
            return threadPoolNameVal
        }
        set(value) {
            this.threadPoolNameVal = value
        }
    }

    public mut prop threadPoolQueueSize: Int64 {
        get() {
            return threadPoolQueueSizeVal
        }
        set(value) {
            this.threadPoolQueueSizeVal = value
        }
    }

    public mut prop maxThreads: Int64 {
        get() {
            return maxThreadsVal
        }
        set(value) {
            this.maxThreadsVal = value
        }
    }

    public mut prop minThreads: Int64 {
        get() {
            return minThreadsVal
        }
        set(value) {
            this.minThreadsVal = value
        }
    }

    public mut prop threadIdleTimeout: Duration {
        get() {
            return threadIdleTimeoutVal
        }
        set(value) {
            this.threadIdleTimeoutVal = value
        }
    }

    public mut prop maxSubscriberRecoverDuration: Duration {
        get() {
            return maxSubscriberRecoverDurationVal
        }
        set(value) {
            this.maxSubscriberRecoverDurationVal = value
        }
    }

    public mut prop recoverAttemptsPerMinute: Int64 {
        get() {
            return recoverAttemptsPerMinuteVal
        }
        set(value) {
            this.recoverAttemptsPerMinuteVal = value
        }
    }

    public mut prop useNonExclusiveObjectPool: Bool {
        get() {
            return useNonExclusiveObjectPoolVal
        }
        set(value) {
            this.useNonExclusiveObjectPoolVal = value
        }
    }

    public mut prop objectPoolEvictionIntervals: ?Duration {
        get() {
            return objectPoolEvictionIntervalsVal
        }
        set(value) {
            this.objectPoolEvictionIntervalsVal = value
        }
    }

    public mut prop objectPoolLifo: Bool {
        get() {
            return objectPoolLifoVal
        }
        set(value) {
            this.objectPoolLifoVal = value
        }
    }

    public mut prop testOnCreate: Bool {
        get() {
            return testOnCreateVal
        }
        set(value) {
            this.testOnCreateVal = value
        }
    }

    public mut prop testOnBorrow: Bool {
        get() {
            return testOnBorrowVal
        }
        set(value) {
            this.testOnBorrowVal = value
        }
    }

    public mut prop testOnReturn: Bool {
        get() {
            return testOnReturnVal
        }
        set(value) {
            this.testOnReturnVal = value
        }
    }

    public mut prop testWhileIdle: Bool {
        get() {
            return testWhileIdleVal
        }
        set(value) {
            this.testWhileIdleVal = value
        }
    }

    public open func configTcpEndpointOptions(endpoint: ClientEndpointConfig) {
        endpoint.waitConnectionTimeout = waitConnectionTimeout

        endpoint.asyncWrite = this.asyncWrite
        endpoint.writeQueueSize = this.writeQueueSize
        endpoint.writeBufferSize = this.writeBufferSize
        endpoint.stickyRead = this.stickyRead
        endpoint.execOnReadThread = this.execOnReadThread
        endpoint.sliceExceedBuffer = this.sliceExceedBuffer
        endpoint.maxMessageSizeInBytes = this.maxMessageSizeInBytes

        if (let Some(receiveBufferSize) <- this.receiveBufferSize) {
            if (receiveBufferSize > 0) {
                endpoint.receiveBufferSize = receiveBufferSize
            }
        }

        if (let Some(sendBufferSize) <- this.sendBufferSize) {
            if (sendBufferSize > 0) {
                endpoint.sendBufferSize = sendBufferSize
            }
        }

        if (let Some(noDelay) <- this.noDelay) {
            endpoint.noDelay = noDelay
        }

        if (let Some(linger) <- this.linger) {
            endpoint.linger = linger
        }

        if (let Some(readTimeout) <- this.readTimeout) {
            endpoint.readTimeout = readTimeout
        }

        if (let Some(writeTimeout) <- this.writeTimeout) {
            endpoint.writeTimeout = writeTimeout
        }

        endpoint.idleTimeout = this.idleTimeout

        endpoint.bufferAllocateSize = this.bufferAllocateSize
        endpoint.usePooledBufferAllocator = this.usePooledBufferAllocator
        endpoint.maxPooledBuffers = this.maxPooledBuffers

        endpoint.lifo = this.objectPoolLifo

        endpoint.useNonExclusiveObjectPool = this.useNonExclusiveObjectPool

        if (let Some(objectPoolEvictionIntervals) <- this.objectPoolEvictionIntervals) {
            endpoint.evictionIntervals = objectPoolEvictionIntervals
        }

        endpoint.testOnCreate = this.testOnCreate

        endpoint.testOnBorrow = this.testOnBorrow

        endpoint.testOnReturn = this.testOnReturn

        endpoint.testWhileIdle = this.testWhileIdle
    }

    @OverflowWrapping
    public open func computeHashCode(): Int64 {
        let prime = 53
        var hashCode = 0
        if (let Some(respVersion) <- respVersionVal) {
            hashCode = hashCode + prime * respVersion.hashCode()
        }

        if (let Some(user) <- userVal) {
            hashCode = hashCode + prime * user.hashCode()
        }

        if (let Some(password) <- passwordVal) {
            hashCode = hashCode + prime * String.fromUtf8(password).hashCode()
        }

        if (let Some(clientName) <- clientNameVal) {
            hashCode = hashCode + prime * clientName.hashCode()
        }

        hashCode = hashCode + prime * shutdownTimeoutVal.hashCode()
        hashCode = hashCode + prime * waitConnectionTimeoutVal.hashCode()
        hashCode = hashCode + prime * checkSubscribeMessageIntervalVal.hashCode()
        hashCode = hashCode + prime * asyncWriteVal.hashCode()
        hashCode = hashCode + prime * writeQueueSize.hashCode()
        hashCode = hashCode + prime * writeBufferSize.hashCode()
        hashCode = hashCode + prime * stickyRead.hashCode()
        hashCode = hashCode + prime * execOnReadThread.hashCode()

        if (let Some(receiveBufferSize) <- receiveBufferSizeVal) {
            hashCode = hashCode + prime * receiveBufferSize.hashCode()
        }

        if (let Some(sendBufferSize) <- sendBufferSizeVal) {
            hashCode = hashCode + prime * sendBufferSize.hashCode()
        }

        if (let Some(noDelay) <- noDelayVal) {
            hashCode = hashCode + prime * noDelay.hashCode()
        }

        if (let Some(linger) <- lingerVal) {
            hashCode = hashCode + prime * linger.hashCode()
        }

        if (let Some(readTimeout) <- readTimeoutVal) {
            hashCode = hashCode + prime * readTimeout.hashCode()
        }

        if (let Some(writeTimeout) <- writeTimeoutVal) {
            hashCode = hashCode + prime * writeTimeout.hashCode()
        }

        hashCode = hashCode + prime * idleTimeoutVal.hashCode()

        hashCode = hashCode + prime * bufferAllocateSizeVal.hashCode()
        hashCode = hashCode + prime * usePooledBufferAllocatorVal.hashCode()
        hashCode = hashCode + prime * maxPooledBuffersVal.hashCode()

        hashCode = hashCode + prime * threadPoolNameVal.hashCode()
        hashCode = hashCode + prime * threadPoolQueueSizeVal.hashCode()
        hashCode = hashCode + prime * maxThreadsVal.hashCode()
        hashCode = hashCode + prime * minThreadsVal.hashCode()
        hashCode = hashCode + prime * threadIdleTimeoutVal.hashCode()
        hashCode = hashCode + prime * useNonExclusiveObjectPoolVal.hashCode()

        hashCode = hashCode + prime * objectPoolLifoVal.hashCode()
        hashCode = hashCode + prime * testOnCreateVal.hashCode()
        hashCode = hashCode + prime * testOnBorrowVal.hashCode()
        hashCode = hashCode + prime * testOnReturnVal.hashCode()
        hashCode = hashCode + prime * testWhileIdleVal.hashCode()

        return hashCode
    }

    public func checkEquals(other: RedisClientConfigBase): Bool {
        if (respVersionVal != other.respVersionVal) {
            return false
        }

        if (userVal != other.userVal) {
            return false
        }

        var myPassword: ?String = None
        var otherPassword: ?String = None
        if (let Some(password) <- passwordVal) {
            myPassword = String.fromUtf8(password)
        }

        if (let Some(otherPass) <- other.passwordVal) {
            otherPassword = String.fromUtf8(otherPass)
        }

        if (myPassword != otherPassword) {
            return false
        }

        if (clientNameVal != other.clientNameVal) {
            return false
        }

        if (shutdownTimeoutVal != other.shutdownTimeoutVal) {
            return false
        }

        if (waitConnectionTimeoutVal != other.waitConnectionTimeoutVal) {
            return false
        }

        if (checkSubscribeMessageIntervalVal != other.checkSubscribeMessageIntervalVal) {
            return false
        }

        if (asyncWriteVal != other.asyncWriteVal) {
            return false
        }

        if (writeQueueSizeVal != other.writeQueueSizeVal) {
            return false
        }

        if (writeBufferSizeVal != other.writeBufferSizeVal) {
            return false
        }

        if (stickyReadVal != other.stickyReadVal) {
            return false
        }

        if (execOnReadThreadVal != other.execOnReadThreadVal) {
            return false
        }

        if (receiveBufferSizeVal != other.receiveBufferSizeVal) {
            return false
        }

        if (sendBufferSizeVal != other.sendBufferSizeVal) {
            return false
        }

        if (noDelayVal != other.noDelayVal) {
            return false
        }

        if (lingerVal != other.lingerVal) {
            return false
        }

        if (readTimeoutVal != other.readTimeoutVal) {
            return false
        }

        if (writeTimeoutVal != other.writeTimeoutVal) {
            return false
        }

        if (idleTimeoutVal != other.idleTimeoutVal) {
            return false
        }

        if (bufferAllocateSizeVal != other.bufferAllocateSizeVal) {
            return false
        }

        if (usePooledBufferAllocatorVal != other.usePooledBufferAllocatorVal) {
            return false
        }

        if (maxPooledBuffersVal != other.maxPooledBuffersVal) {
            return false
        }

        if (threadPoolNameVal != other.threadPoolNameVal) {
            return false
        }

        if (threadPoolQueueSizeVal != other.threadPoolQueueSizeVal) {
            return false
        }

        if (maxThreadsVal != other.maxThreadsVal) {
            return false
        }

        if (minThreadsVal != other.minThreadsVal) {
            return false
        }

        if (threadIdleTimeoutVal != other.threadIdleTimeoutVal) {
            return false
        }

        if (objectPoolLifoVal != other.objectPoolLifoVal) {
            return false
        }

        if (testOnCreateVal != other.testOnCreateVal) {
            return false
        }

        if (testOnBorrowVal != other.testOnBorrowVal) {
            return false
        }

        if (testOnReturnVal != other.testOnReturnVal) {
            return false
        }

        if (testWhileIdleVal != other.testWhileIdleVal) {
            return false
        }

        if (useNonExclusiveObjectPoolVal != other.useNonExclusiveObjectPoolVal) {
            return false
        }

        return true
    }

    public func cloneTo(other: RedisClientConfigBase) {
        other.respVersion = this.respVersion
        other.user = this.user
        other.password = this.password
        other.clientName = this.clientName
        other.shutdownTimeout = this.shutdownTimeout
        other.waitConnectionTimeout = this.waitConnectionTimeout
        other.checkSubscribeMessageInterval = this.checkSubscribeMessageInterval
        other.asyncWrite = this.asyncWrite
        other.writeQueueSize = this.writeQueueSize
        other.writeBufferSize = this.writeBufferSize
        other.stickyRead = this.stickyRead
        other.execOnReadThread = this.execOnReadThread
        other.receiveBufferSize = this.receiveBufferSize
        other.sendBufferSize = this.sendBufferSize
        other.noDelay = this.noDelay
        other.linger = this.linger
        other.readTimeout = this.readTimeout
        other.writeTimeout = this.writeTimeout
        other.idleTimeout = this.idleTimeout
        other.bufferAllocateSize = this.bufferAllocateSize
        other.usePooledBufferAllocator = this.usePooledBufferAllocator
        other.maxPooledBuffers = this.maxPooledBuffers
        other.threadPoolName = this.threadPoolName
        other.threadPoolQueueSize = this.threadPoolQueueSize
        other.maxThreads = this.maxThreads
        other.minThreads = this.minThreads
        other.threadIdleTimeout = this.threadIdleTimeout
        other.maxSubscriberRecoverDuration = this.maxSubscriberRecoverDuration
        other.recoverAttemptsPerMinute = this.recoverAttemptsPerMinute
        other.objectPoolLifo = this.objectPoolLifo
        other.testOnCreate = this.testOnCreate
        other.testOnBorrow = this.testOnBorrow
        other.testOnReturn = this.testOnReturn
        other.testWhileIdle = this.testWhileIdle
        other.useNonExclusiveObjectPool = this.useNonExclusiveObjectPool
    }
}

public class RedisClientConfig <: RedisClientConfigBase & Hashable & Equatable<RedisClientConfig> {
    private var hostVal: ?String = None

    private var portVal: ?UInt16 = None

    public mut prop host: ?String {
        get() {
            return hostVal
        }
        set(value) {
            this.hostVal = value
        }
    }

    public mut prop port: ?UInt16 {
        get() {
            return portVal
        }
        set(value) {
            this.portVal = value
        }
    }

    public override func configTcpEndpointOptions(endpoint: ClientEndpointConfig) {
        super.configTcpEndpointOptions(endpoint)
        // Redis连接带认证等状态,所以每个RedisClient只使用一个TCP连接
        endpoint.minConnections = 0
        endpoint.maxConnections = 1
    }

    @OverflowWrapping
    public open func hashCode(): Int64 {
        var hashCode = super.computeHashCode()
        let prime = 53
        if (let Some(host) <- hostVal) {
            hashCode = hashCode + prime * host.hashCode()
        }

        if (let Some(port) <- portVal) {
            hashCode = hashCode + prime * port.hashCode()
        }

        return hashCode
    }

    public operator override func ==(other: RedisClientConfig): Bool {
        if (hostVal != other.hostVal) {
            return false
        }

        if (portVal != other.portVal) {
            return false
        }

        return super.checkEquals(other)
    }

    public operator override func !=(other: RedisClientConfig): Bool {
        if (hostVal != other.hostVal) {
            return true
        }

        if (portVal != other.portVal) {
            return true
        }

        return !super.checkEquals(other)
    }

    public func clone(): RedisClientConfig {
        let clientConfig = RedisClientConfig()
        this.cloneTo(clientConfig)
        clientConfig.host = this.host
        clientConfig.port = this.port
        return clientConfig
    }
}