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

import Foundation
import SessionUtilitiesKit

class MockCaches: CachesType {
    private var cacheInstances: [Int: MutableCacheType] = [:]
    
    // MARK: - Immutable Access
    
    public subscript<M, I>(cache: CacheInfo.Config<M, I>) -> I {
        get { MockCaches.getValueSettingIfNull(cache: cache, &cacheInstances) }
    }
    
    public subscript<M, I>(cache: CacheInfo.Config<M, I>) -> M? {
        get { return (cacheInstances[cache.key] as? M) }
        set { cacheInstances[cache.key] = newValue.map { cache.mutableInstance($0) } }
    }
    
    public func setting<M, I>(cache: CacheInfo.Config<M, I>, to value: M?) -> MockCaches {
        self[cache] = value
        return self
    }
    
    // MARK: - Mutable Access
    
    @discardableResult public func mutate<M, I, R>(
        cache: CacheInfo.Config<M, I>,
        _ mutation: (inout M) -> R
    ) -> R {
        var value: M = ((cacheInstances[cache.key] as? M) ?? cache.createInstance())
        return mutation(&value)
    }
    
    @discardableResult public func mutate<M, I, R>(
        cache: CacheInfo.Config<M, I>,
        _ mutation: (inout M) throws -> R
    ) throws -> R {
        var value: M = ((cacheInstances[cache.key] as? M) ?? cache.createInstance())
        return try mutation(&value)
    }
    
    @discardableResult private static func getValueSettingIfNull<M, I>(
        cache: CacheInfo.Config<M, I>,
        _ store: inout [Int: MutableCacheType]
    ) -> I {
        guard let value: M = (store[cache.key] as? M) else {
            let value: M = cache.createInstance()
            let mutableInstance: MutableCacheType = cache.mutableInstance(value)
            store[cache.key] = mutableInstance
            return cache.immutableInstance(value)
        }
        
        return cache.immutableInstance(value)
    }
}
