import Foundation
import SwiftSignalKit

private final class TempBoxFileContext {
    let directory: String
    let fileName: String
    var subscribers = Set<Int>()
    
    var path: String {
        return self.directory + "/" + self.fileName
    }
    
    init(directory: String, fileName: String) {
        self.directory = directory
        self.fileName = fileName
    }
}

private struct TempBoxKey: Equatable, Hashable {
    let path: String?
    let fileName: String
    let uniqueId: Int?
}

public final class TempBoxFile {
    fileprivate let key: TempBoxKey
    fileprivate let id: Int
    public let path: String
    
    fileprivate init(key: TempBoxKey, id: Int, path: String) {
        self.key = key
        self.id = id
        self.path = path
    }
}

private final class TempBoxContexts {
    private var nextId: Int = 0
    private var contexts: [TempBoxKey: TempBoxFileContext] = [:]
    
    func file(basePath: String, path: String, fileName: String) -> TempBoxFile {
        let key = TempBoxKey(path: path, fileName: fileName, uniqueId: nil)
        let context: TempBoxFileContext
        if let current = self.contexts[key] {
            context = current
        } else {
            let id = self.nextId
            self.nextId += 1
            let dirName = "\(id)"
            let dirPath = basePath + "/" + dirName
            var cleanName = fileName
            if cleanName.hasPrefix("..") {
                cleanName = "__" + String(cleanName[cleanName.index(cleanName.startIndex, offsetBy: 2)])
            }
            cleanName = cleanName.replacingOccurrences(of: "/", with: "_")
            context = TempBoxFileContext(directory: dirPath, fileName: cleanName)
            self.contexts[key] = context
            let _ = try? FileManager.default.createDirectory(atPath: dirPath, withIntermediateDirectories: true, attributes: nil)
            let _ = try? FileManager.default.linkItem(atPath: path, toPath: context.path)
        }
        let id = self.nextId
        self.nextId += 1
        context.subscribers.insert(id)
        return TempBoxFile(key: key, id: id, path: context.path)
    }
    
    func tempFile(basePath: String, fileName: String) -> TempBoxFile {
        let id = self.nextId
        self.nextId += 1
        
        let key = TempBoxKey(path: nil, fileName: fileName, uniqueId: id)
        let context: TempBoxFileContext
        
        let dirName = "\(id)"
        let dirPath = basePath + "/" + dirName
        var cleanName = fileName
        if cleanName.hasPrefix("..") {
            cleanName = "__" + String(cleanName[cleanName.index(cleanName.startIndex, offsetBy: 2)])
        }
        cleanName = cleanName.replacingOccurrences(of: "/", with: "_")
        context = TempBoxFileContext(directory: dirPath, fileName: cleanName)
        self.contexts[key] = context
        let _ = try? FileManager.default.createDirectory(atPath: dirPath, withIntermediateDirectories: true, attributes: nil)
    
        context.subscribers.insert(id)
        return TempBoxFile(key: key, id: id, path: context.path)
    }
    
    func dispose(_ file: TempBoxFile) -> [String] {
        if let context = self.contexts[file.key] {
            context.subscribers.remove(file.id)
            if context.subscribers.isEmpty {
                self.contexts.removeValue(forKey: file.key)
                return [context.directory]
            }
        }
        return []
    }
}

private var sharedValue: TempBox?

public final class TempBox {
    private let basePath: String
    private let processType: String
    private let launchSpecificId: Int64
    private let currentBasePath: String
    
    private let contexts = Atomic<TempBoxContexts>(value: TempBoxContexts())
    
    public static func initializeShared(basePath: String, processType: String, launchSpecificId: Int64) {
        sharedValue = TempBox(basePath: basePath, processType: processType, launchSpecificId: launchSpecificId)
    }
    
    public static var shared: TempBox {
        return sharedValue!
    }
    
    private init(basePath: String, processType: String, launchSpecificId: Int64) {
        self.basePath = basePath
        self.processType = processType
        self.launchSpecificId = launchSpecificId
        
        self.currentBasePath = basePath + "/temp/" + processType + "/temp-" + String(UInt64(bitPattern: launchSpecificId), radix: 16)
        self.cleanupPreviousLaunches(path: basePath + "/temp/" + processType, currentLaunchSpecificId: launchSpecificId)
    }
    
    private func cleanupPreviousLaunches(path: String, currentLaunchSpecificId: Int64) {
        DispatchQueue.global(qos: .background).async {
            let currentName = "temp-" + String(UInt64(bitPattern: currentLaunchSpecificId), radix: 16)
            if let files = try? FileManager.default.contentsOfDirectory(at: URL(fileURLWithPath: path), includingPropertiesForKeys: [], options: []) {
                for url in files {
                    if url.lastPathComponent.hasPrefix("temp-") && url.lastPathComponent != currentName {
                        let _ = try? FileManager.default.removeItem(atPath: url.path)
                    }
                }
            }
        }
    }
    
    public func file(path: String, fileName: String) -> TempBoxFile {
        return self.contexts.with { contexts in
            return contexts.file(basePath: self.currentBasePath, path: path, fileName: fileName)
        }
    }
    
    public func tempFile(fileName: String) -> TempBoxFile {
        return self.contexts.with { contexts in
            return contexts.tempFile(basePath: self.currentBasePath, fileName: fileName)
        }
    }
    
    public func dispose(_ file: TempBoxFile) {
        let removePaths = self.contexts.with { contexts in
            return contexts.dispose(file)
        }
        if !removePaths.isEmpty {
            DispatchQueue.global(qos: .background).async {
                for path in removePaths {
                    let _ = try? FileManager.default.removeItem(atPath: path)
                }
            }
        }
    }
}