// RUN: rm -rf %t
// RUN: mkdir -p %t
// RUN: %target-build-swift %s -o %t/a.out -O
// RUN: %target-run %t/a.out
// REQUIRES: executable_test
// REQUIRES: objc_interop

import SwiftPrivate
import StdlibUnittest


import Foundation

protocol TestableUnicodeCodec : UnicodeCodec {
  associatedtype CodeUnit : FixedWidthInteger
  static func encodingId() -> String.Encoding
  static func name() -> NSString
}

extension UTF8 : TestableUnicodeCodec {
  static func encodingId() -> String.Encoding {
    return .utf8
  }
  static func name() -> NSString {
    return "UTF8"
  }
}

extension UTF16 : TestableUnicodeCodec {
  static func encodingId() -> String.Encoding {
    return .utf16LittleEndian
  }
  static func name() -> NSString {
    return "UTF16"
  }
}

extension UTF32 : TestableUnicodeCodec {
  static func encodingId() -> String.Encoding {
    return .utf32LittleEndian
  }
  static func name() -> NSString {
    return "UTF32"
  }
}

// The valid ranges of Unicode scalar values
var unicodeScalarRanges: [CountableClosedRange<UInt32>] = [UInt32(0)...0xd7ff, 0xe000...0x10ffff]

var unicodeScalarCount: Int {
  var count = 0
  for r in unicodeScalarRanges {
    count += Int(r.upperBound - r.lowerBound)
  }
  return count
}

func nthUnicodeScalar(_ n: UInt32) -> UnicodeScalar {
  var count: UInt32 = 0
  for r in unicodeScalarRanges {
    count += r.upperBound - r.lowerBound
    if count > n {
      return UnicodeScalar(r.upperBound - (count - n))!
    }
  }
  _preconditionFailure("Index out of range")
}

// `buffer` should have a length >= 4
func nsEncode<CodeUnit>(
  _ c: UInt32,
  _ encoding: String.Encoding,
  _ buffer: inout [CodeUnit],
  _ used: inout Int
) {
  var c = c
  _precondition(buffer.count >= 4, "buffer is not large enough")

  let s = NSString(
    bytes: &c,
    length: 4,
    encoding: String.Encoding.utf32LittleEndian.rawValue)!

  s.getBytes(
    &buffer,
    maxLength: buffer.count,
    usedLength: &used,
    encoding: encoding.rawValue,
    options: [],
    range: NSRange(location: 0, length: s.length),
    remaining: nil)
}

final class CodecTest<Codec : TestableUnicodeCodec> {
  var used = 0
  typealias CodeUnit = Codec.CodeUnit
  var nsEncodeBuffer: [CodeUnit] = Array(repeating: 0, count: 4)
  var encodeBuffer: [CodeUnit] = Array(repeating: 0, count: 4)

  final func testOne(_ scalar: UnicodeScalar) {
    /* Progress reporter
    if (scalar.value % 0x1000) == 0 {
      print("\(asHex(scalar.value))")
    }
    */

    // Use Cocoa to encode the scalar
    nsEncode(scalar.value, Codec.encodingId(), &nsEncodeBuffer, &used)
    let nsEncoded = nsEncodeBuffer[0..<(used/MemoryLayout<CodeUnit>.size)]
    var encodeIndex = encodeBuffer.startIndex
    let encodeOutput: (CodeUnit) -> Void = {
      self.encodeBuffer[encodeIndex] = $0
      encodeIndex += 1
    }

    var iter = nsEncoded.makeIterator()
    var decoded: UnicodeScalar
    var decoder = Codec()
    switch decoder.decode(&iter) {
    case .scalarValue(let us):
      decoded = us
    default:
      fatalError("decoding failed")
    }
    expectEqualTest(
      scalar, decoded,
      "Decoding failed: \(asHex(scalar.value)) => " +
      "\(asHex(nsEncoded)) => \(asHex(decoded.value))"
    ) { $0 == $1 }

    encodeIndex = encodeBuffer.startIndex
    Codec.encode(scalar, into: encodeOutput)
    expectEqualTest(
      nsEncoded, encodeBuffer[0..<encodeIndex],
      "Decoding failed: \(asHex(nsEncoded)) => " +
        "\(asHex(scalar.value)) => \(asHex(self.encodeBuffer[0]))"
    ) { $0 == $1 }
  }

  final func run(_ minScalarOrd: Int, _ maxScalarOrd: Int) {
    print("testing \(Codec.name())")
    for i in minScalarOrd..<maxScalarOrd {
      testOne(nthUnicodeScalar(UInt32(i)))
    }
  }
}

var UTFEncoders = TestSuite("UTFEncoders")

UTFEncoders.test("encode") {
  let minScalarOrd = 0
  let maxScalarOrd = unicodeScalarCount
  CodecTest<UTF8>().run(minScalarOrd, maxScalarOrd)
  CodecTest<UTF16>().run(minScalarOrd, maxScalarOrd)
  CodecTest<UTF32>().run(minScalarOrd, maxScalarOrd)
}

runAllTests()

