// RUN: %target-run-simple-swift | %FileCheck %s
// REQUIRES: executable_test

func curry<T, U, V>(_ f: @escaping (T, U) -> V) -> (T) -> (U) -> V {
  return { x in { y in f(x, y) } }
}

func curry<T1, T2, T3, T4>(_ f: @escaping (T1, T2, T3) -> T4) -> (T1) -> (T2) -> (T3) -> T4 {
  return { x in { y in { z in f(x, y, z) } } }
}

func concat(_ x: String, _ y: String, _ z: String) -> String {
  return x + y + z
}

@inline(never)
public func test_concat_closure(_ x: Int) -> String {
  var x = x
  let insult = curry(concat)("one ")(" two ")
  var gs = insult(" three ")
  if (x > 0) {
    gs = gs + insult(" four ")
    gs = gs + insult(" five ")
  } else {
    gs = gs + insult(" six ")
    gs = gs + insult(" seven ")
  }
  if (x > 10) {
     x += 100
  }
  return gs
}

public protocol P {
  func val() -> Int32
}

struct CP: P {
  let v: Int32

  func val() -> Int32 {
     return v
  }

  init(_ v: Int32) {
    self.v = v
  }
}

func compose(_ x: P, _ y: P, _ z: P) -> Int32 {
  return x.val() + y.val() + z.val()
}


@inline(never)
public func test_compose_closure(_ x: Int) -> Int32 {
  var x = x
  let insult = curry(compose)(CP(1))(CP(2))
  var gs = insult(CP(3))
  if (x > 0) {
    gs = gs + insult(CP(4))
    gs = gs + insult(CP(5))
  } else {
    gs = gs + insult(CP(6))
    gs = gs + insult(CP(7))
  }
  if (x > 10) {
     x += 100
  }
  return gs
}

let insult = curry(+)("I'm with stupid ☞ ")
print(insult("😡")) // CHECK: I'm with stupid ☞ 😡

let plus1 = curry({ $0 + $1 })(1)
print(plus1(5)) // CHECK-NEXT: 6

let plus5 = curry({ $0 + $1 })(5)
print(plus5(5)) // CHECK-NEXT: 10

print(insult("😰")) // CHECK-NEXT: I'm with stupid ☞ 😰


let concat_one_two = curry(concat)("one ")(" two ")
print(concat_one_two(" three ")) // CHECK-NEXT: one two three

print(test_concat_closure(20)) // CHECK-NEXT: one  two  three one  two  four one  two  five

print(test_compose_closure(20)) // CHECK-NEXT: 21

// rdar://problem/18988428

func clamp<T: Comparable>(_ minValue: T, _ maxValue: T) -> (T) -> T {
    return { n in max(minValue, min(n, maxValue)) }
}

let clampFoo2 = clamp(10.0, 30.0)

print(clampFoo2(3.0)) // CHECK-NEXT: 10.0

// rdar://problem/19195470

func pair<T,U> (_ a: T) -> (U) -> (T,U) {
	return { b in (a,b)	}
}

func pair_<T,U> (_ a: T) -> (_ b: U) -> (T,U) {
	return { b in (a,b) }
}

infix operator <+>
func <+><T,U,V> (lhs: T?, rhs: @escaping (T) -> (U) -> V) -> (U) -> V? {
	if let x = lhs {
		return { y in .some(rhs(x)(y)) }
	} else {
		return { _ in nil }
	}
}

let a : Int? = 23
let b : Int? = 42
print((b <+> pair)(a!) as Any) // CHECK-NEXT: (42, 23)
print((b <+> pair_)(a!) as Any) // CHECK-NEXT: (42, 23)

//
// rdar://problem/20475584
//

struct Identity<A> { let value: A }
struct Const<A, B> { let value: A }

func fmap<A, B>(_ f: @escaping (A) -> B) -> (Identity<A>) -> Identity<B> {
	return { identity in Identity(value: f(identity.value)) }
}

func fmap<A, B>(_ f: @escaping (A) -> B) -> (Const<A, B>) -> Const<A, B> {
	return { const in const }
}

// really Const()
func _Const<A, B>(_ a: A) -> Const<A, B> {
	return Const(value: a)
}
func const<A, B>(_ a: A) -> (B) -> A {
	return { _ in a }
}

// really Identity()
func _Identity<A>(_ a: A) -> Identity<A> {
	return Identity(value: a)
}

func getConst<A, B>(_ c: Const<A, B>) -> A {
	return c.value
}

func runIdentity<A>(_ i: Identity<A>) -> A {
	return i.value
}


func view<S, A>(_ lens: @escaping (@escaping (A) -> Const<A, S>) -> (S) -> (@escaping (@escaping (A) -> S) -> (Const<A, S>) -> Const<A, S>) -> Const<A, S>) -> (S) -> A {
	return { s in getConst(lens(_Const)(s)(fmap)) }
}

func over<S, A>(_ lens: @escaping (@escaping (A) -> Identity<A>) -> (S) -> (@escaping (@escaping (A) -> S) -> (Identity<A>) -> Identity<S>) -> Identity<S>) -> (@escaping (A) -> A) -> (S) -> S {
	return { f in { s in runIdentity(lens({ _Identity(f($0)) })(s)(fmap)) } }
}

func set<S, A>(_ lens: @escaping (@escaping (A) -> Identity<A>) -> (S) -> (@escaping (@escaping (A) -> S) -> (Identity<A>) -> Identity<S>) -> Identity<S>) -> (A) -> (S) -> S {
	return { x in { y in over(lens)(const(x))(y) } }
}

func _1<A, B, C, D>(_ f: @escaping (A) -> C) -> ((A, B)) -> (@escaping (@escaping (A) -> (A, B)) -> (C) -> D) -> D {
	return { pair in { fmap in fmap({ ($0, pair.1) })(f(pair.0)) } }
}

func _2<A, B, C, D>(_ f: @escaping (B) -> C) -> ((A, B)) -> (@escaping (@escaping (B) -> (A, B)) -> (C) -> D) -> D {
    return { pair in { fmap in fmap({ (pair.0, $0) })(f(pair.1)) } }
}


public func >>> <T, U, V> (f: @escaping (T) -> U, g: @escaping (U) -> V) -> (T) -> V {
	return { g(f($0)) }
}

public func <<< <T, U, V> (f: @escaping (U) -> V, g: @escaping (T) -> U) -> (T) -> V {
	return { f(g($0)) }
}


infix operator >>>
infix operator <<<

let pt1 = view(_1)((1, 2))
print(pt1) // CHECK-NEXT: 1
let pt2 = over(_1)({ $0 * 4 })((1, 2))
print(pt2) // CHECK-NEXT: (4, 2)
let pt3 = set(_1)(3)((1, 2))
print(pt3) // CHECK-NEXT: (3, 2)
let pt4 = view(_2)(("hello", 5))
print(pt4) // CHECK-NEXT: 5

