import Foundation
import SwiftSignalKit

private func wrappedWrite(_ fd: Int32, _ data: UnsafeRawPointer, _ count: Int) -> Int {
    return write(fd, data, count)
}

private func wrappedRead(_ fd: Int32, _ data: UnsafeMutableRawPointer, _ count: Int) -> Int {
    return read(fd, data, count)
}

public final class ManagedFile {
    public enum Mode {
        case read
        case readwrite
        case append
    }
    
    private let queue: Queue?
    private let fd: Int32
    private let mode: Mode
    private var isClosed: Bool = false
    
    public init?(queue: Queue?, path: String, mode: Mode) {
        if let queue = queue {
            assert(queue.isCurrent())
        }
        self.queue = queue
        self.mode = mode
        let fileMode: Int32
        let accessMode: UInt16
        switch mode {
            case .read:
                fileMode = O_RDONLY
                accessMode = S_IRUSR
            case .readwrite:
                fileMode = O_RDWR | O_CREAT
                accessMode = S_IRUSR | S_IWUSR
            case .append:
                fileMode = O_WRONLY | O_CREAT | O_APPEND
                accessMode = S_IRUSR | S_IWUSR
        }
        let fd = open(path, fileMode, accessMode)
        if fd >= 0 {
            self.fd = fd
        } else {
            return nil
        }
    }
    
    deinit {
        if let queue = self.queue {
            assert(queue.isCurrent())
        }
        if !self.isClosed {
            close(self.fd)
        }
    }
    
    public func _unsafeClose() {
        if let queue = self.queue {
            assert(queue.isCurrent())
        }
        if !self.isClosed {
            close(self.fd)
            self.isClosed = true
        }
    }
    
    public func write(_ data: UnsafeRawPointer, count: Int) -> Int {
        if let queue = self.queue {
            assert(queue.isCurrent())
        }
        assert(!self.isClosed)
        return wrappedWrite(self.fd, data, count)
    }
    
    public func read(_ data: UnsafeMutableRawPointer, _ count: Int) -> Int {
        if let queue = self.queue {
            assert(queue.isCurrent())
        }
        assert(!self.isClosed)
        return wrappedRead(self.fd, data, count)
    }
    
    public func readData(count: Int) -> Data {
        if let queue = self.queue {
            assert(queue.isCurrent())
        }
        assert(!self.isClosed)
        var result = Data(count: count)
        result.withUnsafeMutableBytes { buffer -> Void in
            guard let bytes = buffer.baseAddress?.assumingMemoryBound(to: UInt8.self) else {
                return
            }
            let readCount = self.read(bytes, count)
            assert(readCount == count)
        }
        return result
    }
    
    @discardableResult
    public func seek(position: Int64) -> Bool {
        if let queue = self.queue {
            assert(queue.isCurrent())
        }
        assert(!self.isClosed)
        let result = lseek(self.fd, position, SEEK_SET)
        return result == position
    }
    
    public func truncate(count: Int64) {
        if let queue = self.queue {
            assert(queue.isCurrent())
        }
        assert(!self.isClosed)
        ftruncate(self.fd, count)
    }
    
    public func getSize() -> Int64? {
        if let queue = self.queue {
            assert(queue.isCurrent())
        }
        assert(!self.isClosed)
        var value = stat()
        if fstat(self.fd, &value) == 0 {
            return value.st_size
        } else {
            return nil
        }
    }
    
    public func position() -> Int64 {
        if let queue = self.queue {
            assert(queue.isCurrent())
        }
        assert(!self.isClosed)
        
        return lseek(self.fd, 0, SEEK_CUR);
    }
    
    public func sync() {
        if let queue = self.queue {
            assert(queue.isCurrent())
        }
        assert(!self.isClosed)
        fsync(self.fd)
    }
}

public extension ManagedFile {
    func write(_ data: Data) -> Int {
        if data.isEmpty {
            return 0
        }
        return data.withUnsafeBytes { bytes -> Int in
            return self.write(bytes.baseAddress!, count: bytes.count)
        }
    }
    
    func write(_ value: Int32) {
        var value = value
        let _ = self.write(&value, count: 4)
    }
    
    func write(_ value: UInt32) {
        var value = value
        let _ = self.write(&value, count: 4)
    }
    
    func write(_ value: Int64) {
        var value = value
        let _ = self.write(&value, count: 8)
    }
    
    func write(_ value: UInt64) {
        var value = value
        let _ = self.write(&value, count: 8)
    }
    
    func write(_ value: Float32) {
        var value = value
        let _ = self.write(&value, count: 4)
    }
}