/**
 * Meta
 */

print = console.log
y = f => x => f(y(f))(x)
k = t => f => t
ki = t => f => f

/**
 * Logic
 */

// true/false Church booleans
tru = t => f => t
fls = t => f => f

// evaluate 
evalBool = bool => bool("true")("false")

print(evalBool(tru)) // "true"
print(evalBool(fls)) // "false"

// negate (c combinator)
negate = bool => t => f => bool(f)(t)

print(evalBool(negate(tru))) // "false"
print(evalBool(negate(fls))) // "true"

// logical and
and = a => b => b(a)(b)

// how?
// - if b is true, then b is re-bound to a (first arg, see lines 1,3)
// - if b is false, then b is re-bound to b (second arg, always false, see lines 2,4)

print(evalBool(and(tru)(tru))) // "true"
print(evalBool(and(tru)(fls))) // "false"
print(evalBool(and(fls)(tru))) // "false"
print(evalBool(and(fls)(fls))) // "false"

/**
 * Church Pairs
 */

// construction
cons = a => b => s => s(a)(b)

// example
examplePair = cons("a")("b")

// selectors
car = pair => pair(a => b => a)
cdr = pair => pair(a => b => b)

print(car(examplePair)) // "a"
print(cdr(examplePair)) // "b"

/**
 * Church Lists
 */

// ["a", "b", "c"] => pair "a" (pair "b" (pair "c" nil))
//                 => s1 => (s1 "a" (s2 => (s2 "b" (s3 => (s3 "c" nil)))))

// end of list
nil = head => tail => tail

exampleList = cons("a")(cons("b")(cons("c")(nil)))

// true if list is empty
isNil = list => list(head => tail => rest => fls)(tru)
// first argument gets substituted into first selector (s1)
//   just ignore all arguments and return false
//   "rest" is the right argument "tru"
// nil ignores first argument, second one is just returned as is ("true")

print(evalBool(isNil(nil))) // "true"
print(evalBool(isNil(exampleList))) // "false"

// length = y(rec => n => list => isNil(list)(n)(rec(n + 1)(cdr(list))))(0)

// thunked due to JS' strictness..
length = y(rec => n => list => isNil(list)(() => n)(() => rec(n + 1)(cdr(list))()))(0)

print(length(exampleList)())

/**
 * Scott Lists
 */

exampleScottList = end => s1 => s2("a")(s2 => s2("b")(end))

/**
 * Parigot Lists
 */

exampleParigotList = s1 => end1 => s1("a")(s2 => end2 => s2("b")(s3 => end3 => end3))

/**
 * Product types
 */

// data Friends = Friends { best :: String, friendly :: String, weird :: String }
Friends = best => friendly => weird => s => s(best)(friendly)(weird)
best = friends => friends(best => _ => _ => best)
friendly = friends => friends(_ => friendly => _ => friendly)
weird = friends => friends(_ => _ => weird => weird)

friends = Friends("Alice")("Bob")("Carol")
print(best(friends))

/**
 * Sum types, tagged unions
 */

// data Tree = Leaf Int | Node Tree Tree
Leaf = n => leaf => node => leaf(n)
Node = a => b => leaf => node => node(a)(b)
nodeLeft = node => node(_ => _)(a => b => a)
nodeRight = node => node(_ => _)(a => b => b)
leafValue = leaf => leaf(n => n)(_ => _)
isLeaf = tree => tree(n => tru)(a => b => fls)
isNode = tree => tree(n => fls)(a => b => tru)

exampleTree = Node(Leaf(1))(Node(Leaf(2))(Leaf(3)))
print(evalBool(isNode(exampleTree)))
print(evalBool(isLeaf(nodeLeft(exampleTree))))
print(leafValue(nodeLeft(exampleTree)))
print(leafValue(nodeRight(nodeRight(exampleTree))))

/**
 * Rose, Balanced, binary, finger, etc. trees
 */

// skipped, see bruijn std
// for example: AVL - can even be used for sets and hashmaps!

/**
 * Strings, chars
 */

// just a list of numbers/bits/etc.

/**
 * Church numerals
 */

churchZero = s => z => z
churchSucc = n => s => z => s(n(s)(z))
churchPred = n => f => x => n(g => h => h(g(f)))(u => x)(u => u)
churchIsZero = n => n(z => fls)(tru)

print(evalBool(churchIsZero(churchZero))) // true
print(evalBool(churchIsZero(churchSucc(churchZero)))) // false
print(evalBool(churchIsZero(churchPred(churchSucc(churchZero))))) // true

/**
 * Scott numerals
 */

scottZero = z => s => z
scottSucc = n => z => s => s(n)
scottPred = n => n(scottZero)(x => x)
scottIsZero = n => n(tru)(x => fls)

print(evalBool(scottIsZero(scottZero))) // true
print(evalBool(scottIsZero(scottSucc(scottZero)))) // false
print(evalBool(scottIsZero(scottPred(scottSucc(scottZero))))) // true

/**
 * Parigot numerals
 */

parigotZero = n => n
parigotSucc = n => a => b => b(n(a))
parigotPred = n => a => n(a(x => x))

/**
 * Wadsworth numerals
 */

wadsworthZero = n => n(u => k)
wadsworthSucc = n => a => b => n(c => a(b(c))(b))
wadsworthPred = n => a => n(b => k(a(b)))(x => x)
wadsworthIsZero = n => n(x => x)(k(k(ki)))

print(evalBool(wadsworthIsZero(wadsworthZero))) // true
print(evalBool(wadsworthIsZero(wadsworthSucc(wadsworthZero)))) // false
print(evalBool(wadsworthIsZero(wadsworthPred(wadsworthSucc(wadsworthZero))))) // true

/**
 * de Bruijn numerals
 */

bruijnZero = n => n
bruijnSucc = n => a => b => n(a)
bruijnPred = n => a => n(a)(a)
bruijnMult = a => b => a(b)

/**
 * n-ary numerals
 */

exampleMogensenBinary = end => b1 => b0 => b1(b1(end))
exampleMogensenTernary = end => tn => tp => t0 => t0(tp(end))

/**
 * rational, real, complex
 */

// skipped, see bruijn std

/**
 * Maybe monad
 */

Nothing = empty => full => empty
Just = v => empty => full => full(v)
isNothing = m => m(tru)(u => fls)
isJust = m => m(fls)(u => tru)
map => f => m => m(nothing)(v => just(f(v)))

pure = Just
bind = mx => f => mx(mx)(f)
//       ------------^^  ^-----------------
//       case Nothing     case Just (apply)

evalMaybe = maybe => maybe("Nothing")(v => "Just " + v)
print(evalMaybe(bind(Nothing)(n => pure(n + 1))))
print(evalMaybe(bind(Just(42))(n => pure(n + 1))))

/**
 * Either monad
 */

// data Either a b = Left a | Right b
Left = a => left => right => left(a)
Right = b => left => right => right(b)

// instance Monad
pure = Right
bind = mx => f => mx(Left)(f)
//          ---------^^^^  ^------------------
//          case Left       case Right (apply)

evalEither = either => either(a => "Left " + a)(b => "Right " + b)
print(evalEither(bind(Left(42))(n => pure(n + 1))))
print(evalEither(bind(Right(42))(n => pure(n + 1))))

/**
 * Mogensen-Scott meta
 */

// enc[x]      = sym => app => lam => sym(x)
// enc[f(x)]   = sym => app => lam => app(enc[f])(enc[x])
// enc[x => m] = sym => app => lam => lam(x => enc[m])

evalMeta = term => term
  (x => x)
  (f => x => eval(f)(eval(x)))
  (m => x => eval(m(x)))

/**
 * Bruijn-Church meta
 */

// enc[i]    = idx => app => lam => church[idx]
// enc[f(x)] = idx => app => lam => app(enc[f])(enc[x])
// enc[b]    = idx => app => lam => lam(enc[b])

/**
 * Lambda Screen
 */

// a screen is (s => s(tl)(tr)(bl)(br)), where tl,tr,bl,br in [screen, pixel]
//   pixel is (w => b => w) || (w => b => b)