/**
 * 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

/**
 * Cluster集群的配置
 */
public class ClusterRedisClientConfig <: RedisClientConfigBase & Hashable & Equatable<ClusterRedisClientConfig> {
    private let clusterHostAndPorts = HashSet<HostAndPort>()

    // 最大重试次数,默认值:5
    private var maxAttemptsVal: Int64 = 5

    // 最大重试时间
    private var maxTotalRetriesDurationVal: Duration = Duration.second * 30

    // 更新槽位缓存信息的间隔时间
    private var topologyRefreshPeriodVal: ?Duration = None

    // 延迟关闭ClusterInfoCache(用于维护集群模式的槽位信息)的时间, 在该期间内ClusterInfoCache再次被引用则取消关闭
    private var closeClusterInfoCacheDelayDurationVal = Duration.second * 10

    public func addClusterHostAndPort(hostAndPort: HostAndPort) {
        if (!clusterHostAndPorts.contains(hostAndPort)) {
            clusterHostAndPorts.add(hostAndPort)
        }
        return this
    }

    public func addClusterHostAndPort(host: String, port: UInt16) {
        let hostAndPort = HostAndPort(host, port)
        addClusterHostAndPort(hostAndPort)
        return this
    }

    public func addClusterHostAndPorts(hostAndPorts: Array<HostAndPort>) {
        for (hostAndPort in hostAndPorts) {
            addClusterHostAndPort(hostAndPort)
        }
        return this
    }

    public func addClusterHostAndPorts(hostAndPorts: ArrayList<HostAndPort>) {
        for (hostAndPort in hostAndPorts) {
            addClusterHostAndPort(hostAndPort)
        }
        return this
    }

    public func getClusterHostAndPorts(): HashSet<HostAndPort> {
        return this.clusterHostAndPorts
    }

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

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

    public mut prop topologyRefreshPeriod: ?Duration {
        get() {
            return topologyRefreshPeriodVal
        }
        set(value) {
            if (let Some(value) <- value) {
                if (value > Duration.Zero) {
                    this.topologyRefreshPeriodVal = value
                }
            }
        }
    }

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

    public override func configTcpEndpointOptions(endpoint: ClientEndpointConfig) {
        super.configTcpEndpointOptions(endpoint)
        endpoint.minConnections = 0
        endpoint.maxConnections = 1
    }

    @OverflowWrapping
    public open func hashCode(): Int64 {
        let prime = 53
        var hashCode = super.computeHashCode()

        // 先排序再计算HashCode
        let sortedHostAndPorts = ArrayList<HostAndPort>()
        sortedHostAndPorts.add(all: clusterHostAndPorts)
        sort(sortedHostAndPorts)
        for (hostAndPort in sortedHostAndPorts) {
            hashCode = hashCode + prime * hostAndPort.hashCode()
        }

        hashCode = hashCode + prime * maxAttemptsVal.hashCode()
        hashCode = hashCode + prime * maxTotalRetriesDurationVal.hashCode()
        if (let Some(topologyRefreshPeriod) <- topologyRefreshPeriodVal) {
            hashCode = hashCode + prime * topologyRefreshPeriod.hashCode()
        }
        hashCode = hashCode + prime * closeClusterInfoCacheDelayDurationVal.hashCode()
        return hashCode
    }

    public operator override func ==(other: ClusterRedisClientConfig): Bool {
        if (!checkSameHostAndPorts(clusterHostAndPorts, other.clusterHostAndPorts)) {
            return false
        }

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

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

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

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

        return super.checkEquals(other)
    }

    public operator override func !=(other: ClusterRedisClientConfig): Bool {
        if (!checkSameHostAndPorts(clusterHostAndPorts, other.clusterHostAndPorts)) {
            return true
        }

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

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

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

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

        return !super.checkEquals(other)
    }

    public func getStartNodes(): String {
        let builder = StringBuilder()
        let startNodes = ArrayList<HostAndPort>()
        startNodes.add(all: this.clusterHostAndPorts)
        for (i in 0..startNodes.size) {
            builder.append(startNodes[i])
            if (i < startNodes.size - 1) {
                builder.append(",")
            }
        }

        return builder.toString()
    }

    private static func checkSameHostAndPorts(
        hostAndPorts: HashSet<HostAndPort>,
        otherHostAndPorts: HashSet<HostAndPort>
    ): Bool {
        if (hostAndPorts.size != otherHostAndPorts.size) {
            return false
        }

        let sortedHostAndPorts = ArrayList<HostAndPort>()
        sortedHostAndPorts.add(all: hostAndPorts)
        sort(sortedHostAndPorts)

        let otherSortedHostAndPorts = ArrayList<HostAndPort>()
        otherSortedHostAndPorts.add(all: otherHostAndPorts)
        sort(otherSortedHostAndPorts)

        return sortedHostAndPorts == otherSortedHostAndPorts
    }

    public func clone(): ClusterRedisClientConfig {
        let clientConfig = ClusterRedisClientConfig()
        this.cloneTo(clientConfig)
        clientConfig.clusterHostAndPorts.add(all: this.clusterHostAndPorts.clone())
        clientConfig.maxAttempts = this.maxAttempts
        clientConfig.maxTotalRetriesDuration = this.maxTotalRetriesDuration
        clientConfig.topologyRefreshPeriod = this.topologyRefreshPeriod
        clientConfig.closeClusterInfoCacheDelayDuration = this.closeClusterInfoCacheDelayDuration
        return clientConfig
    }
}