// RUN: %empty-directory(%t)
// RUN: %target-build-swift -swift-version 4   %s %import-libdispatch -o %t/a.out-4   && %target-codesign %t/a.out-4   && %target-run %t/a.out-4
// RUN: %target-build-swift -swift-version 4.2 %s %import-libdispatch -o %t/a.out-4.2 && %target-codesign %t/a.out-4.2 && %target-run %t/a.out-4.2
// REQUIRES: executable_test
// REQUIRES: libdispatch

import Dispatch
import StdlibUnittest

defer { runAllTests() }

var DispatchAPI = TestSuite("DispatchAPI")

DispatchAPI.test("dispatch_data_t enumeration") {
  // Ensure we can iterate the empty iterator
  for _ in DispatchData.empty {
    _ = 1
  }
}

DispatchAPI.test("dispatch_data_t deallocator") {
  let q = DispatchQueue(label: "dealloc queue")
  var t = 0

  do {
    let size = 1024
    let p = UnsafeMutablePointer<UInt8>.allocate(capacity: size)
    let _ = DispatchData(bytesNoCopy: UnsafeBufferPointer(start: p, count: size), deallocator: .custom(q, {
      t = 1
      p.deallocate();
    }))
  }

  q.sync {
    expectEqual(1, t)
  }
}

DispatchAPI.test("DispatchData.copyBytes") {
  let source1: [UInt8] = [0, 1, 2, 3]
  var dest: [UInt8] = [0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF]

  var dispatchData = source1.withUnsafeBytes { return DispatchData(bytes: $0) }

  dest.withContiguousMutableStorageIfAvailable { destPtr in
    // Copy from offset 0
    var count = dispatchData.copyBytes(to: destPtr, from: 0..<2)
    expectEqual(count, 2)
    expectEqual(destPtr[0], 0)
    expectEqual(destPtr[1], 1)
    expectEqual(destPtr[2], 0xFF)
    expectEqual(destPtr[3], 0xFF)
    expectEqual(destPtr[4], 0xFF)
    expectEqual(destPtr[5], 0xFF)

    // Copy from offset 2
    count = dispatchData.copyBytes(to: destPtr, from: 2..<4)
    expectEqual(count, 2)
    expectEqual(destPtr[0], 2)
    expectEqual(destPtr[1], 3)
    expectEqual(destPtr[2], 0xFF)
    expectEqual(destPtr[3], 0xFF)
    expectEqual(destPtr[4], 0xFF)
    expectEqual(destPtr[5], 0xFF)

    // Add two more regions
    let source2: [UInt8] = [0x10, 0x11, 0x12, 0x13]
    source2.withUnsafeBytes { dispatchData.append(DispatchData(bytes: $0)) }

    let source3: [UInt8] = [0x14, 0x15, 0x16]
    source3.withUnsafeBytes { dispatchData.append(DispatchData(bytes: $0)) }

    // Copy from offset 0. Copies across the first two regions
    count = dispatchData.copyBytes(to: destPtr, from: 0..<6)
    expectEqual(count, 6)
    expectEqual(destPtr[0], 0)
    expectEqual(destPtr[1], 1)
    expectEqual(destPtr[2], 2)
    expectEqual(destPtr[3], 3)
    expectEqual(destPtr[4], 0x10)
    expectEqual(destPtr[5], 0x11)

    // Copy from offset 2. Copies across the first two regions
    count = dispatchData.copyBytes(to: destPtr, from: 2..<8)
    expectEqual(count, 6)
    expectEqual(destPtr[0], 2)
    expectEqual(destPtr[1], 3)
    expectEqual(destPtr[2], 0x10)
    expectEqual(destPtr[3], 0x11)
    expectEqual(destPtr[4], 0x12)
    expectEqual(destPtr[5], 0x13)

    // Copy from offset 3. Copies across all three regions
    count = dispatchData.copyBytes(to: destPtr, from: 3..<9)
    expectEqual(count, 6)
    expectEqual(destPtr[0], 3)
    expectEqual(destPtr[1], 0x10)
    expectEqual(destPtr[2], 0x11)
    expectEqual(destPtr[3], 0x12)
    expectEqual(destPtr[4], 0x13)
    expectEqual(destPtr[5], 0x14)

    // Copy from offset 5. Skips the first region and the first byte of the second
    count = dispatchData.copyBytes(to: destPtr, from: 5..<11)
    expectEqual(count, 6)
    expectEqual(destPtr[0], 0x11)
    expectEqual(destPtr[1], 0x12)
    expectEqual(destPtr[2], 0x13)
    expectEqual(destPtr[3], 0x14)
    expectEqual(destPtr[4], 0x15)
    expectEqual(destPtr[5], 0x16)

    // Copy from offset 8. Skips the first two regions
    destPtr[3] = 0xFF
    destPtr[4] = 0xFF
    destPtr[5] = 0xFF
    count = dispatchData.copyBytes(to: destPtr, from: 8..<11)
    expectEqual(count, 3)
    expectEqual(destPtr[0], 0x14)
    expectEqual(destPtr[1], 0x15)
    expectEqual(destPtr[2], 0x16)
    expectEqual(destPtr[3], 0xFF)
    expectEqual(destPtr[4], 0xFF)
    expectEqual(destPtr[5], 0xFF)

    // Copy from offset 9. Skips the first two regions and the first byte of the third
    destPtr[2] = 0xFF
    destPtr[3] = 0xFF
    destPtr[4] = 0xFF
    destPtr[5] = 0xFF
    count = dispatchData.copyBytes(to: destPtr, from: 9..<11)
    expectEqual(count, 2)
    expectEqual(destPtr[0], 0x15)
    expectEqual(destPtr[1], 0x16)
    expectEqual(destPtr[2], 0xFF)
    expectEqual(destPtr[3], 0xFF)
    expectEqual(destPtr[4], 0xFF)
    expectEqual(destPtr[5], 0xFF)

    // Copy from offset 9, but only 1 byte. Ends before the end of the data
    destPtr[1] = 0xFF
    destPtr[2] = 0xFF
    destPtr[3] = 0xFF
    destPtr[4] = 0xFF
    destPtr[5] = 0xFF
    count = dispatchData.copyBytes(to: destPtr, from: 9..<10)
    expectEqual(count, 1)
    expectEqual(destPtr[0], 0x15)
    expectEqual(destPtr[1], 0xFF)
    expectEqual(destPtr[2], 0xFF)
    expectEqual(destPtr[3], 0xFF)
    expectEqual(destPtr[4], 0xFF)
    expectEqual(destPtr[5], 0xFF)

    // Copy from offset 2, but only 1 byte. This copy is bounded within the 
    // first region.
    destPtr[1] = 0xFF
    destPtr[2] = 0xFF
    destPtr[3] = 0xFF
    destPtr[4] = 0xFF
    destPtr[5] = 0xFF
    count = dispatchData.copyBytes(to: destPtr, from: 2..<3)
    expectEqual(count, 1)
    expectEqual(destPtr[0], 2)
    expectEqual(destPtr[1], 0xFF)
    expectEqual(destPtr[2], 0xFF)
    expectEqual(destPtr[3], 0xFF)
    expectEqual(destPtr[4], 0xFF)
    expectEqual(destPtr[5], 0xFF)
  }
}

DispatchAPI.test("DispatchData.copyBytesUnsafeRawBufferPointer") {
  let source1: [UInt8] = [0, 1, 2, 3]

  var dest: [UInt8] = [0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF]
  var dispatchData = source1.withUnsafeBytes { return DispatchData(bytes: $0) }

  dest.withContiguousMutableStorageIfAvailable { destPtr in
    // Copy from offset 0
    dispatchData.copyBytes(to: destPtr, from: 0..<2)
    expectEqual(destPtr[0], 0)
    expectEqual(destPtr[1], 1)
    expectEqual(destPtr[2], 0xFF)
    expectEqual(destPtr[3], 0xFF)
    expectEqual(destPtr[4], 0xFF)
    expectEqual(destPtr[5], 0xFF)

    // Copy from offset 2
    dispatchData.copyBytes(to: destPtr, from: 2..<4)
    expectEqual(destPtr[0], 2)
    expectEqual(destPtr[1], 3)
    expectEqual(destPtr[2], 0xFF)
    expectEqual(destPtr[3], 0xFF)
    expectEqual(destPtr[4], 0xFF)
    expectEqual(destPtr[5], 0xFF)

    // Add two more regions
    let source2: [UInt8] = [0x10, 0x11, 0x12, 0x13]
    source2.withUnsafeBytes { dispatchData.append(DispatchData(bytes: $0)) }

    let source3: [UInt8] = [0x14, 0x15, 0x16]
    source3.withUnsafeBytes { dispatchData.append(DispatchData(bytes: $0)) }

    // Copy from offset 0. Copies across the first two regions
    dispatchData.copyBytes(to: destPtr, from: 0..<6)
    expectEqual(destPtr[0], 0)
    expectEqual(destPtr[1], 1)
    expectEqual(destPtr[2], 2)
    expectEqual(destPtr[3], 3)
    expectEqual(destPtr[4], 0x10)
    expectEqual(destPtr[5], 0x11)

    // Copy from offset 2. Copies across the first two regions
    dispatchData.copyBytes(to: destPtr, from: 2..<8)
    expectEqual(destPtr[0], 2)
    expectEqual(destPtr[1], 3)
    expectEqual(destPtr[2], 0x10)
    expectEqual(destPtr[3], 0x11)
    expectEqual(destPtr[4], 0x12)
    expectEqual(destPtr[5], 0x13)

    // Copy from offset 3. Copies across all three regions
    dispatchData.copyBytes(to: destPtr, from: 3..<9)
    expectEqual(destPtr[0], 3)
    expectEqual(destPtr[1], 0x10)
    expectEqual(destPtr[2], 0x11)
    expectEqual(destPtr[3], 0x12)
    expectEqual(destPtr[4], 0x13)
    expectEqual(destPtr[5], 0x14)

    // Copy from offset 5. Skips the first region and the first byte of the second
    dispatchData.copyBytes(to: destPtr, from: 5..<11)
    expectEqual(destPtr[0], 0x11)
    expectEqual(destPtr[1], 0x12)
    expectEqual(destPtr[2], 0x13)
    expectEqual(destPtr[3], 0x14)
    expectEqual(destPtr[4], 0x15)
    expectEqual(destPtr[5], 0x16)

    // Copy from offset 8. Skips the first two regions
    destPtr[3] = 0xFF
    destPtr[4] = 0xFF
    destPtr[5] = 0xFF
    dispatchData.copyBytes(to: destPtr, from: 8..<11)
    expectEqual(destPtr[0], 0x14)
    expectEqual(destPtr[1], 0x15)
    expectEqual(destPtr[2], 0x16)
    expectEqual(destPtr[3], 0xFF)
    expectEqual(destPtr[4], 0xFF)
    expectEqual(destPtr[5], 0xFF)

    // Copy from offset 9. Skips the first two regions and the first byte of the third
    destPtr[2] = 0xFF
    destPtr[3] = 0xFF
    destPtr[4] = 0xFF
    destPtr[5] = 0xFF
    dispatchData.copyBytes(to: destPtr, from: 9..<11)
    expectEqual(destPtr[0], 0x15)
    expectEqual(destPtr[1], 0x16)
    expectEqual(destPtr[2], 0xFF)
    expectEqual(destPtr[3], 0xFF)
    expectEqual(destPtr[4], 0xFF)
    expectEqual(destPtr[5], 0xFF)

    // Copy from offset 9, but only 1 byte. Ends before the end of the data
    destPtr[1] = 0xFF
    destPtr[2] = 0xFF
    destPtr[3] = 0xFF
    destPtr[4] = 0xFF
    destPtr[5] = 0xFF
    dispatchData.copyBytes(to: destPtr, from: 9..<10)
    expectEqual(destPtr[0], 0x15)
    expectEqual(destPtr[1], 0xFF)
    expectEqual(destPtr[2], 0xFF)
    expectEqual(destPtr[3], 0xFF)
    expectEqual(destPtr[4], 0xFF)
    expectEqual(destPtr[5], 0xFF)

    // Copy from offset 2, but only 1 byte. This copy is bounded within the 
    // first region.
    destPtr[1] = 0xFF
    destPtr[2] = 0xFF
    destPtr[3] = 0xFF
    destPtr[4] = 0xFF
    destPtr[5] = 0xFF
    dispatchData.copyBytes(to: destPtr, from: 2..<3)
    expectEqual(destPtr[0], 2)
    expectEqual(destPtr[1], 0xFF)
    expectEqual(destPtr[2], 0xFF)
    expectEqual(destPtr[3], 0xFF)
    expectEqual(destPtr[4], 0xFF)
    expectEqual(destPtr[5], 0xFF)
  }
}

DispatchAPI.test("DispatchData.buffers") {
  let bytes = [UInt8(0), UInt8(1), UInt8(2), UInt8(2)]
  var ptr = UnsafeBufferPointer<UInt8>(start: bytes, count: bytes.count)
  var data = DispatchData(bytes: ptr)
  expectEqual(bytes.count, data.count)
  for i in 0..<data.count {
    expectEqual(data[i], bytes[i])
  }

  data = DispatchData(bytesNoCopy: ptr, deallocator: .custom(nil, {}))
  expectEqual(bytes.count, data.count)
  for i in 0..<data.count {
    expectEqual(data[i], bytes[i])
  }

  ptr = UnsafeBufferPointer<UInt8>(start: nil, count: 0)
  data = DispatchData(bytes: ptr)
  expectEqual(data.count, 0)

  data = DispatchData(bytesNoCopy: ptr, deallocator: .custom(nil, {}))
  expectEqual(data.count, 0)
}

DispatchAPI.test("DispatchData.bufferUnsafeRawBufferPointer") {
  let bytes = [UInt8(0), UInt8(1), UInt8(2), UInt8(2)]
  var ptr = UnsafeRawBufferPointer(start: bytes, count: bytes.count)
  var data = DispatchData(bytes: ptr)
  expectEqual(bytes.count, data.count)
  for i in 0..<data.count {
    expectEqual(data[i], bytes[i])
  }

  data = DispatchData(bytesNoCopy: ptr, deallocator: .custom(nil, {}))
  expectEqual(bytes.count, data.count)
  for i in 0..<data.count {
    expectEqual(data[i], bytes[i])
  }

  ptr = UnsafeRawBufferPointer(start: nil, count: 0)
  data = DispatchData(bytes: ptr)
  expectEqual(data.count, 0)

  data = DispatchData(bytesNoCopy: ptr, deallocator: .custom(nil, {}))
  expectEqual(data.count, 0)
}

DispatchAPI.test("DispatchData.enumerateBytes") {
  let count = 240
  var bytes = [UInt8]()
  for i in 0..<count {
    bytes.append(UInt8(i))
  }
  let data = bytes.withUnsafeBytes { return DispatchData(bytes: $0) }

  var nextIndex = 0
#if swift(>=4.2)
  data.enumerateBytes({ ptr, offset, stop in
    for i in 0..<ptr.count {
      expectEqual(ptr[i + offset], UInt8(i + offset))
    }
    nextIndex = offset + ptr.count
  })
#else
  data.enumerateBytes(block: { ptr, offset, stop in
    for i in 0..<ptr.count {
      expectEqual(ptr[i + offset], UInt8(i + offset))
    }
    nextIndex = offset + ptr.count
  })
#endif
  expectEqual(nextIndex, data.count)
}

DispatchAPI.test("DispatchData.enumerateBytesTrailingClosure") {
  let count = 240
  var bytes = [UInt8]()
  for i in 0..<count {
    bytes.append(UInt8(i))
  }
  let data = bytes.withUnsafeBytes { return DispatchData(bytes: $0) }

  var nextIndex = 0
  data.enumerateBytes() { ptr, offset, stop in
    for i in 0..<ptr.count {
      expectEqual(ptr[i + offset], UInt8(i + offset))
    }
    nextIndex = offset + ptr.count
  }
  expectEqual(nextIndex, data.count)
}
