// RUN: %target-run-simple-swift
// REQUIRES: executable_test

import StdlibUnittest
import Swift

let ResultTests = TestSuite("Result")

fileprivate enum Err: Error, Equatable {
  case err
  case derr
}

fileprivate let string = "string"

fileprivate extension Result {
  var success: Success? {
    switch self {
    case let .success(success):
      return success
    case .failure:
      return nil
    }
  }
  
  var failure: Failure? {
    switch self {
    case .success:
      return nil
    case let .failure(failure):
      return failure
    }
  }
}

ResultTests.test("Construction") {
  let result1: Result<String, Err> = .success(string)
  let result2: Result<String, Err> = .failure(.err)
  let string1: String? = {
    switch result1 {
      case let .success(string): 
        return string
      case .failure: 
        expectUnreachable()
        return nil
    }
  }()
  let error: Err? = {
    switch result2 {
      case let .failure(failure): 
        return failure
      case .success: 
        expectUnreachable()
        return nil
    }
  }()

  expectEqual(string, string1)
  expectEqual(.err, error)
}

ResultTests.test("Throwing Initialization and Unwrapping") {
  func notThrowing() throws -> String {
    return string
  }

  func throwing() throws -> String {
    throw Err.err
  }

  func throwingTyped() throws(Err) -> String {
    throw .err
  }

  func knownNotThrowing() -> String { return string }

  let result1 = Result { try throwing() }
  let result2 = Result { try notThrowing() }

  expectEqual(result1.failure as? Err, Err.err)
  expectEqual(result2.success, string)
    
  do {
    _ = try result1.get()
  } catch let error as Err {
    expectEqual(error, Err.err)
  } catch {
    expectUnreachable()
  }
    
  do {
    let unwrapped = try result2.get()
    expectEqual(unwrapped, string)
  } catch {
    expectUnreachable()
  }
    
  // Test unwrapping strongly typed error.
  let result3 = Result<String, Err>.failure(Err.err)
  do {
    _ = try result3.get()
  } catch let error as Err {
    expectEqual(error, Err.err)
  } catch {
    expectUnreachable()
  }

  // Test strongly typed error via closure.
  // FIXME: Type inference should eliminate the need for the throws(Err)
  // annotations below.
  let result4 = Result { () throws(Err) in try throwingTyped() }
  let _: Result<String, Err> = result4 // check the type
  expectEqual(result4.failure, .err)
  do throws(Err) {
    _ = try result4.get()
  } catch let error {
    expectEqual(error, .err)
  }

  let result5 = Result { knownNotThrowing() }
  let _: Result<String, Never> = result5 // check the type
  _ = result5.get() // no need for 'try'
}

ResultTests.test("Functional Transforms") {
  func transformDouble(_ int: Int) -> Int {
    return 2 * int
  }
  
  func transformTriple(_ int: Int) -> Int {
    return 3 * int
  }
  
  func transformError(_ err: Err) -> Err {
    if err == .err {
      return .derr
    } else {
      return .err
    }
  }

  func resultValueTransform(_ int: Int) -> Result<Int, Err> {
    return .success(transformDouble(int))
  }
  
  func resultErrorTransform(_ err: Err) -> Result<Int, Err> {
    return .failure(transformError(err))
  }
    
  let result1: Result<Int, Err> = .success(1)
  let newResult1 = result1.map(transformDouble)
    
  expectEqual(newResult1, .success(2))
    
  let result2: Result<Int, Err> = .failure(.err)
  let newResult2 = result2.mapError(transformError)
    
  expectEqual(newResult2, .failure(.derr))
    
  let result3: Result<Int, Err> = .success(1)
  let newResult3 = result3.flatMap(resultValueTransform)
    
  expectEqual(newResult3, .success(2))
    
  let result4: Result<Int, Err> = .failure(.derr)
  let newResult4 = result4.flatMapError(resultErrorTransform)
    
  expectEqual(newResult4, .failure(.err))
}

ResultTests.test("Equatable") {
  let result1: Result<Int, Err> = .success(1)
  let result2: Result<Int, Err> = .failure(.err)

  expectEqual(result1, .success(1))
  expectNotEqual(result1, .success(2))
  expectNotEqual(result1, .failure(.err))
  expectNotEqual(result1, .failure(.derr))

  expectNotEqual(result2, .success(1))
  expectNotEqual(result2, .success(2))
  expectEqual(result2, .failure(.err))
  expectNotEqual(result2, .failure(.derr))
  
  let confusables: [Result<Err, Err>] = [
    .success(.err),
    .success(.derr),
    .failure(.err),
    .failure(.derr)
  ]
  
  checkEquatable(confusables, oracle: { $0 == $1 })
}

ResultTests.test("Hashable") {
  let result1: Result<Int, Err> = .success(1)
  let result2: Result<Int, Err> = .success(2)
  let result3: Result<Int, Err> = .failure(.err)
  checkHashable([result1, result2, result3], equalityOracle: { $0 == $1 })

  let confusables: [Result<Err, Err>] = [
    .success(.err),
    .success(.derr),
    .failure(.err),
    .failure(.derr)
  ]
  checkHashable(confusables, equalityOracle: { $0 == $1 })
}

runAllTests()
