//===--- FileImageSource.swift - An image source that reads from a file ---===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2023 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
//
// Defines FileImageSource, an image source that reads data from a file.
//
//===----------------------------------------------------------------------===//

import Swift

#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
internal import Darwin
#elseif os(Windows)
internal import ucrt
#elseif canImport(Glibc)
internal import Glibc
#elseif canImport(Musl)
internal import Musl
#endif

enum FileImageSourceError: Error {
  case posixError(Int32)
  case outOfRangeRead
}

class FileImageSource: ImageSource {
  private var _mapping: UnsafeRawBufferPointer

  public var isMappedImage: Bool { return false }

  private var _path: String
  public var path: String? { return _path }

  public var bounds: Bounds? {
    return Bounds(base: 0, size: Size(_mapping.count))
  }

  public init(path: String) throws {
    _path = path
    let fd = open(path, O_RDONLY, 0)
    if fd < 0 {
      throw FileImageSourceError.posixError(errno)
    }
    defer { close(fd) }
    let size = lseek(fd, 0, SEEK_END)
    if size < 0 {
      throw FileImageSourceError.posixError(errno)
    }
    let base = mmap(nil, Int(size), PROT_READ, MAP_FILE|MAP_PRIVATE, fd, 0)
    if base == nil || base! == UnsafeRawPointer(bitPattern: -1)! {
      throw FileImageSourceError.posixError(errno)
    }
    _mapping = UnsafeRawBufferPointer(start: base, count: Int(size))
  }

  deinit {
    munmap(UnsafeMutableRawPointer(mutating: _mapping.baseAddress),
           _mapping.count)
  }

  public func fetch(from addr: Address,
                    into buffer: UnsafeMutableRawBufferPointer) throws {
    let start = Int(addr)
    guard _mapping.indices.contains(start) else {
      throw FileImageSourceError.outOfRangeRead
    }
    let slice = _mapping[start...]
    guard slice.count >= buffer.count else {
      throw FileImageSourceError.outOfRangeRead
    }
    buffer.copyBytes(from: slice[start..<start+buffer.count])
  }
}
