import StdlibUnittest

%{
import gyb
from SwiftFloatingPointTypes import all_floating_point_types, getFtoIBounds
from SwiftIntTypes import all_integer_types
}%

var FixedPointConversionTruncations = TestSuite("FixedPointToFloatingPointConversionTruncations")
var FixedPointConversionFailures = TestSuite("FixedPointToFloatingPointConversionFailures")

var FloatingPointConversionTruncations = TestSuite("FloatingPointToFloatingPointConversionTruncations")
var FloatingPointConversionFailures = TestSuite("FloatingPointToFloatingPointConversionFailures")

%  for self_type in all_floating_point_types():
%   SelfSignificandBits = self_type.bits
%   Self = self_type.stdlib_name

%  if Self == 'Float16':
#if !os(macOS) && !(os(iOS) && targetEnvironment(macCatalyst))
%  end
%  if Self == 'Float80':
#if !os(Windows) && (arch(i386) || arch(x86_64))
%  end

%  for other_type in all_floating_point_types():
%   OtherSignificandBits = other_type.bits
%   OtherFloat = other_type.stdlib_name

%   if OtherFloat == 'Float16':
#if !os(macOS) && !(os(iOS) && targetEnvironment(macCatalyst))
%   end
%   if OtherFloat == 'Float80':
#if !os(Windows) && (arch(i386) || arch(x86_64))
%   end

%   if OtherSignificandBits <= SelfSignificandBits:

%    if OtherFloat == 'Float16':
if #available(iOS 14.0, watchOS 7.0, tvOS 14.0, *) {
%    end
FloatingPointConversionTruncations.test("${OtherFloat}To${Self}Conversion")

  .forEach(in: [
     ${OtherFloat}.greatestFiniteMagnitude,
    -${OtherFloat}.greatestFiniteMagnitude,
     (1.0 as ${OtherFloat}).nextUp,
     (1.0 as ${OtherFloat}).nextDown,
    (-1.0 as ${OtherFloat}).nextUp,
    (-1.0 as ${OtherFloat}).nextDown,
  ]) {
  input in
  // FIXME: we should have a stronger postcondition here.
  let result = ${Self}(input)
  let resultConvertedBack = ${OtherFloat}(result)
  expectEqual(input, resultConvertedBack)
}
%    if OtherFloat == 'Float16':
}
%    end

%   else:

%    if Self == 'Float16':
if #available(iOS 14.0, watchOS 7.0, tvOS 14.0, *) {
%    end
FloatingPointConversionTruncations.test("${OtherFloat}To${Self}Conversion")
  .forEach(in: [
    ( ${OtherFloat}.greatestFiniteMagnitude,  ${Self}.infinity),
    (-${OtherFloat}.greatestFiniteMagnitude, -${Self}.infinity),
    ( (1.0 as ${OtherFloat}).nextUp,    1.0 as ${Self}),
    ( (1.0 as ${OtherFloat}).nextDown,  1.0 as ${Self}),
    ((-1.0 as ${OtherFloat}).nextUp,   -1.0 as ${Self}),
    ((-1.0 as ${OtherFloat}).nextDown, -1.0 as ${Self}),
  ]) {
  (input, expectedResult) in
  expectEqual(expectedResult, ${Self}(input))
}
%    if Self == 'Float16':
}
%    end

%   end

%    if 'Float16' in [Self, OtherFloat]:
if #available(iOS 14.0, watchOS 7.0, tvOS 14.0, *) {
%    end
FloatingPointConversionTruncations.test("${OtherFloat}To${Self}Conversion/special") {
  expectEqual( 1.0 as ${Self},   ${Self}(exactly:  1.0 as ${OtherFloat}))
  expectEqual(-1.0 as ${Self},   ${Self}(exactly: -1.0 as ${OtherFloat}))
  expectEqual( ${Self}.infinity, ${Self}( ${OtherFloat}.infinity))
  expectEqual(-${Self}.infinity, ${Self}(-${OtherFloat}.infinity))
  expectTrue(${Self}(${OtherFloat}.nan).isNaN)
}

FloatingPointConversionFailures.test("${OtherFloat}To${Self}FailableConversion")
  .forEach(in: [
    ${OtherFloat}.greatestFiniteMagnitude,
    -${OtherFloat}.greatestFiniteMagnitude,
    (1.0 as ${OtherFloat}).nextUp,
    (1.0 as ${OtherFloat}).nextDown,
    (-1.0 as ${OtherFloat}).nextUp,
    (-1.0 as ${OtherFloat}).nextDown,
  ]) {
  input in
  let result = ${Self}(exactly: input)
%   if OtherSignificandBits <= SelfSignificandBits:
  if let result = expectNotNil(result) {
    // FIXME: we should have a stronger postcondition here.
    expectEqual(input, ${OtherFloat}(result))
  }
%   else:
  expectNil(result)
%   end
}

FloatingPointConversionFailures.test("${OtherFloat}To${Self}Conversion/AlwaysSuccess") {
  expectEqual( 1.0 as ${Self},   ${Self}(exactly:  1.0 as ${OtherFloat}))
  expectEqual(-1.0 as ${Self},   ${Self}(exactly: -1.0 as ${OtherFloat}))
  expectEqual( ${Self}.infinity, ${Self}(exactly:  ${OtherFloat}.infinity))
  expectEqual(-${Self}.infinity, ${Self}(exactly: -${OtherFloat}.infinity))
  expectNil(${Self}(exactly: ${OtherFloat}.nan))
}
%    if 'Float16' in [Self, OtherFloat]:
}
%    end

%  if OtherFloat == 'Float16':
#endif
%  end
%   if OtherFloat == 'Float80':
#endif
%   end

%  end # for in all_floating_point_types (Other)

#if _pointerBitWidth(_32)
% int_types = all_integer_types(32)
#elseif _pointerBitWidth(_64)
% int_types = all_integer_types(64)
#else
_UnimplementedError()
#endif

% for int_ty in int_types:
%  OtherInt = int_ty.stdlib_name

extension ${OtherInt} {
  %    if Self == 'Float16':
  @available(iOS 14.0, watchOS 7.0, tvOS 14.0, *)
  @available(macOS, unavailable)
  %    end
  static var _test${Self}Conversion: [(${OtherInt}, ${OtherInt}, ${OtherInt}?)] {
    if bitWidth > ${Self}.significandBitCount + 1 {
      let bitOffset = ${Self}.significandBitCount + 1
      let limit: ${OtherInt} = ~(~0 << bitOffset)
      let over: ${OtherInt} = 1 + limit << 1
      return [
        (0, 0, 0),
        (limit, limit, limit),
        (over, over + 1, nil),
%   if int_ty.is_signed:
        (-limit, -limit, -limit),
        (-over, -(over + 1), nil),
%   end
      ]
    } else {
      return [
        (0, 0, 0),
        (.min, .min, .min),
        (.max, .max, .max),
      ]
    }
  }
}

%    if Self == 'Float16':
if #available(iOS 14.0, watchOS 7.0, tvOS 14.0, *) {
%    end
FixedPointConversionTruncations.test("${OtherInt}to${Self}")
  .forEach(in: ${OtherInt}._test${Self}Conversion) {
    value, roundedExpectation, exactExpectation in

  let roundedResult = ${Self}(value)
  expectEqual(roundedResult, ${Self}(roundedExpectation))

  let exactResult = ${Self}(exactly: value)
  if let expectation = exactExpectation {
    expectEqual(exactResult!, ${Self}(expectation))
  } else {
    expectNil(exactResult)
  }
}
%    if Self == 'Float16':
}
%    end

%  end # for in int_types

%  if Self == 'Float16':
#endif
%  end
%  if Self == 'Float80':
#endif
%  end

% end # for in all_floating_point_types (Self)

runAllTests()
