/*
 * @Copyright (c) Huawei Technologies Co., Ltd. 2024-2024. All rights reserved.
 */

package memorycache

import std.fs.*
import std.posix
import std.random.*

public class LruDiskCache {
    public static let DEFAULT_MAX_SIZE: Int64 = 3 * 1024 * 1024

    public static let DEFAULT_PATH: String = "./cache"

    public static let DEFAULT_NAME: String = "LruDiskCache"

    private var path: String = ""

    private var size: Int64 = 0

    private var maxSize: Int64 = 0

    private var cacheMap: CustomMap<String, DiskCacheEntry> = CustomMap<String, DiskCacheEntry>()

    private var id: UInt64 = 0

    public init(path!: String = DEFAULT_PATH + FileUtils.SEPARATOR + DEFAULT_NAME, maxSize!: Int64 = DEFAULT_MAX_SIZE) {
        let random: Random = Random()
        id = random.nextBits(60)
        this.path = path + "/${id}"
        setMaxSize(maxSize)
        trimToSize()
        var tempPath: String = path + "/${id}"
        var sInstance = FileUtils.getInstance()
        while (sInstance.existFolder(path+"/${id}")) {
            id = random.nextBits(60)
        }
        try {
            if (!sInstance.existFolder(tempPath)) {
                sInstance.createFolder(tempPath, recursive: true)
            }
        } catch (e: Exception) {
            throw MemoryCacheException("create cache folder failed")
        }
        if (!tempPath.endsWith("/")) {
            this.path = tempPath + FileUtils.SEPARATOR
        }
    }

    public func getPath(): String {
        return this.path
    }

    public func getId(): UInt64 {
        return this.id
    }

    public func getCacheMap(): CustomMap<String, DiskCacheEntry> {
        return this.cacheMap
    }

    public func getSize(): Int64 {
        return this.size
    }

    public func getMaxSize(): Int64 {
        return this.maxSize
    }

    public func set(key: String, context: String) {
        if (key.isEmpty()) {
            throw MemoryCacheException("empty key value")
        }
        if (context.size > this.maxSize) {
            throw MemoryCacheException("context size is bigger than maxSize")
        }
        var tempsize = context.size
        size = size + tempsize
        putCacheMap(key, tempsize)
        trimToSize()
        FileUtils.sInstance.writeNewFile(path + key, context)
    }

    public func set(key: String, context: Array<UInt8>) {
        if (key.isEmpty()) {
            throw MemoryCacheException("empty key value")
        }
        if (context.size > this.maxSize) {
            throw MemoryCacheException("context size is bigger than maxSize")
        }
        var tempsize = context.size
        size = size + tempsize
        putCacheMap(key, tempsize)
        trimToSize()
        FileUtils.sInstance.writeNewFile(path + key, context)
    }

    public operator func [](key: String, context: String) {
        set(key, context)
    }

    public operator func [](key: String, context: Array<UInt8>) {
        set(key, context)
    }

    public func get(key: String): Array<UInt8> {
        var tempPath: String = path + key
        if (FileUtils.sInstance.exists(tempPath)) {
            var res = FileUtils.sInstance.readFile(path + key)
            this.putCacheMap(key, res.size)
            return res
        }
        return Array<UInt8>()
    }

    public func contains(key: String): Bool {
        if (cacheMap.hasKey(key)) {
            return true
        }
        if (FileUtils.sInstance.exists(path + key)) {
            if (posix.isDir(path)) {
                return false
            }
            return true
        }
        return false
    }

    public func remove(key: String): Unit {
        if (cacheMap.hasKey(key)) {
            var tempPath: String = path + key
            var tempsize = FileUtils.sInstance.getFileSize(tempPath)
            FileUtils.sInstance.deleteFile(tempPath)
            cacheMap.remove(key)
            size = size - tempsize
        }
    }

    public func setMaxSize(size: Int64) {
        if (size <= 0 || size > DEFAULT_MAX_SIZE) {
            throw MemoryCacheException("size must be greater than 0 and less than DEFAULT_MAX_SIZE")
        }
        this.maxSize = size
        trimToSize()
    }

    func putCacheMap(key: String, size: Int64) {
        if (size > 0) {
            cacheMap.put(key, DiskCacheEntry(key, size))
        } else {
            cacheMap.put(key, DiskCacheEntry(key, size))
        }
    }

    func trimToSize() {
        var tempkey = ""
        var tempsize = 0
        while (size > maxSize) {
            tempkey = cacheMap.getFirstKey()
            var tempPath = path + tempkey
            var sInstance = FileUtils.sInstance
            tempsize = sInstance.getFileSize(tempPath)
            if (tempsize > 0) {
                size = size - tempsize
            }
            sInstance.deleteFile(tempPath)
            cacheMap.remove(tempkey)
        }
    }
}