public final class Cacher {
    let destination: URL
    private let queue = OperationQueue()
    public enum CacheDestination {
        case temporary
        case atFolder(String)
    }
    // MARK: Initialization
    public init(destination: CacheDestination) {
        // Create the URL for the location of the cache resources
        switch destination {
        case .temporary:
            self.destination = URL(fileURLWithPath: NSTemporaryDirectory())
        case .atFolder(let folder):
            let documentFolder = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0]
            self.destination = URL(fileURLWithPath: documentFolder).appendingPathComponent(folder, isDirectory: true)
        }

        let fileManager = FileManager.default

        do {
            try fileManager.createDirectory(at: self.destination, withIntermediateDirectories: true, attributes: nil)
        }
        catch {
            fatalError("Unable to create cache URL: \(error)")
        }
    }

    public func persist<T>(_ item: T, at cache: Cache<T>, completion: @escaping (_ url: URL) -> Void) {
        let url = destination.appendingPathComponent(cache.fileName, isDirectory: false)

        // Create an operation to process the request.
        let operation = BlockOperation {
            do {
                try cache.transform(item).write(to: url, options: [.atomicWrite])
            } catch {
                fatalError("Failed to write item to cache: \(error)")
            }
        }

        // Set the operation's completion block to call the request's completion handler.
        operation.completionBlock = {
            completion(url)
        }

        // Add the operation to the queue to start the work.
        queue.addOperation(operation)
    }

    public func load<T>(from cache: Cache<T>, completion: @escaping (_ item: T) -> Void) {
        let url = destination.appendingPathComponent(cache.fileName, isDirectory: false)

        // Create an operation to process the request.
        let operation = BlockOperation {
            do {
                let data = try Data(contentsOf: url, options: [])
                let item = try cache.reverse(data)
                completion(item)
            } catch {
                fatalError("Failed to read item from cache: \(error)")
            }
        }

        // Add the operation to the queue to start the work.
        queue.addOperation(operation)
    }
}