// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved.

import Foundation
import SessionUtilitiesKit

public enum SwarmDrainBehaviour {
    case alwaysRandom
    case limitedReuse(
        count: UInt,
        targetSnode: LibSession.Snode?,
        targetUseCount: Int,
        usedSnodes: Set<LibSession.Snode>,
        swarmHash: Int
    )
    
    public static func limitedReuse(count: UInt) -> SwarmDrainBehaviour {
        guard count > 1 else { return .alwaysRandom }
        
        return .limitedReuse(count: count, targetSnode: nil, targetUseCount: 0, usedSnodes: [], swarmHash: 0)
    }
    
    // MARK: - Convenience
    
    func use(snode: LibSession.Snode, from swarm: Set<LibSession.Snode>) -> SwarmDrainBehaviour {
        switch self {
            case .alwaysRandom: return .alwaysRandom
            case .limitedReuse(let count, let targetSnode, let targetUseCount, let usedSnodes, _):
                // If we are using a new snode then reset everything
                guard targetSnode == snode else {
                    return .limitedReuse(
                        count: count,
                        targetSnode: snode,
                        targetUseCount: 1,
                        usedSnodes: usedSnodes.inserting(snode),
                        swarmHash: swarm.hashValue
                    )
                }
                
                // Increment the use count and clear the target if it's been used too many times
                let updatedUseCount: Int = (targetUseCount + 1)
                
                return .limitedReuse(
                    count: count,
                    targetSnode: (updatedUseCount < count ? snode : nil),
                    targetUseCount: updatedUseCount,
                    usedSnodes: usedSnodes,
                    swarmHash: swarm.hashValue
                )
        }
    }
    
    public func clearTargetSnode() -> SwarmDrainBehaviour {
        switch self {
            case .alwaysRandom: return .alwaysRandom
            case .limitedReuse(let count, _, _, let usedSnodes, let swarmHash):
                return .limitedReuse(
                    count: count,
                    targetSnode: nil,
                    targetUseCount: 0,
                    usedSnodes: usedSnodes,
                    swarmHash: swarmHash
                )
        }
    }
    
    public func reset() -> SwarmDrainBehaviour {
        switch self {
            case .alwaysRandom: return .alwaysRandom
            case .limitedReuse(let count, _, _, _, _):
                return .limitedReuse(
                    count: count,
                    targetSnode: nil,
                    targetUseCount: 0,
                    usedSnodes: [],
                    swarmHash: 0
                )
        }
    }
}

// MARK: - Convenience

public extension Atomic where Value == SwarmDrainBehaviour {
    static var alwaysRandom: Atomic<SwarmDrainBehaviour> { Atomic(.alwaysRandom) }
}
