include :assert

add_results setup name: "examples test" {
  test "factorial" {
    assert_equal 720 { fact = {x | true? x == 0, 1, { x * fact(x - 1)}};  fact 6 }
  }

  test "fibonacci" {
    assert_equal 55 { fibonacci = { x| true? x == 0, 0 { true? x == 1, 1 { fibonacci(x - 1) + fibonacci(x - 2)}}; }; fibonacci 10 }
  }

  test "array_map" {
    assert_equal [2,3,4] { array.my_first = { my[0] }; array.my_rest = { my[1,-1] }; array.my_map = {f| true? my.length <= 0 {[]} { true? my.length == 1, {[f my.my_first]}, {[f my.my_first] + my.my_rest.my_map ->f} }}; [1,2,3].my_map({x| x + 1}) }
  }

  test "how_are_you_easy" {
    assert_equal "How are you?" { how = {x| "How are you?" }; are = {x|}; you? = null; how are you? }
  }

  test "how_are_you_map" {
    assert_equal ["How", " are ","you?"] { array.my_first = { my[0] }; array.my_rest = { my[1,-1] }; array.my_map = {f| true? my.length <= 0 {[]} { true? my.length == 1 {[f my.my_first]} {[f my.my_first] + my.my_rest.my_map ->f} }}; how = {x| x.my_map {y| y }}; are = {x| x}; you? = ["How", " are ", "you?"]; how are you? }
  }

  test "how_are_you_auto_reply" {
    assert_equal "I am fine, thank you!" { How = {x| p "How", x[0], x[1] }; are = {x| [" are "] + x }; you? = ["you?"]; I = {x| "I" + x[0] + x[1] + x[2] + x[3] }; am = {x, y| [" am"] + x + y }; fine = [" fine, "]; thank = {x| ["thank"] + x }; you! = [" you!"]; How are you?;  I am fine, thank you! }
  }

  test "greet" {
    assert_equal "oh, hi john doe" { greet = {first, last| "oh, hi " + first + " " + last }; greet "john", "doe" }
  }

  test "my" {
    assert_equal "hi" { test = object.new; test.y = { my.z = { "hi" } };  test.y; test.z }
  }

  test "add_not" {
    assert_false { number.! = {x| not x }; a = [1,2,3]; a[1] ! true }
  }

  test "null?" {
    assert_equal "a is null" { a = null; false? { null? a }, { "a is not null" }, { "a is null" } }
  }

  test "tak" {
    assert_equal 7 { tak = { x, y, z | false? y < x, { z } , { tak tak(x - 1, y, z), tak(y - 1, z, x), tak(z - 1, x, y) } }; tak 18, 12, 6 }
  }

  test "hideous " {
    assert_equal "object[-!_+~%~+_!-, <------||==@, @==||------>, parent]" { a_!?-*+^&@1~\\\\><$ = object.new; a_!?-*+^&@1~\\\\><$.-!_+~%~+_!- = {d0~!@><?&&<>\\/+-*^&% | d0~!@><?&&<>\\/+-*^&%};                a_!?-*+^&@1~\\\\><$.@==||------> = { a_!?-*+^&@1~\\\\><$ };                a_!?-*+^&@1~\\\\><$.<------||==@ = { my->@==||------> };               (@==||------>a_!?-*+^&@1~\\\\><$ -!_+~%~+_!- a_!?-*+^&@1~\\\\><$.<------||==@).to_s }
  }

  test "ackermann" {
    assert_equal 125 {
      ackermann = { m, n |
        when { m == 0 } { n + 1 }
          { m > 0 && n == 0 } { ackermann(m - 1, 1) }
          { m > 0 && n > 0 } { ackermann(m - 1, ackermann(m, n - 1)) }
      }

      ackermann 3 4
    }
  }

  test "accumulator factory" {
    assert_equal 8.3 { 
      accumulator = { sum |
        { n | sum = sum + n }
      }

      x = accumulator 1
      x 5
      accumulator 3 #Does not affect x
      x 2.3
    }
  }

  test "array to hash" {
    assert_equal [1 : :a, 2 : :b, 3 : :c] {
      zip = { keys, values |
        h = [:]
          keys.each_with_index { key, index |
            h[key] = values[index]
          }

        h
      }

      zip [1,2,3] [:a :b :c]
    }
  }

  test "exponentiation" {
    assert_equal 32 {
      exp = { base, exp |
        1.to(exp).reduce 1, { m, n | m = m * base }
      }

      exp 2 5
    }
  } 

  test "filter" {
    assert_equal [2, 4, 6, 8, 10] {
      1.to(10).select { x | x % 2 == 0 }
    }
  }

  test "flatten" {
    assert_equal 1.to(8) {
      array.flatten = {
        true? my.empty?
        { [] }
        { true? my.first.array?
          { my.first.flatten + my.rest.flatten }
          { [my.first] + my.rest.flatten }
        }
      }

      [[1], 2, [[3,4], 5], [[[]]], [[[6]]], 7, 8, []].flatten
    }
  }

  test "function composition" {
    assert_equal 4 {
      compose = { f, g | { x | f g x } }
      add1 = { x | x + 1 }
      double = { x | x * 2 }
      b = compose(->double ->add1)
        b 1
    }
  }

  test "hailstone" {
    assert {
      hailstone = { num |
        sequence = [num]
          while { num != 1 }
        { true? num % 2 == 0
          { num = num / 2 }
          { num = num * 3 + 1 }
          sequence << num
        }

        sequence
      }

      seq = hailstone 27
        seq[0,3] == [27 82 41 124] && seq[-1, -4] == [8 4 2 1]
    }
  }

  test "happy numbers" {
  
    assert_equal [1 7 10 13 19 23 28 31] {
      set = import :set :set

      happiness = set.new 1
      sadness = set.new
  
      sum_of_squares_of_digits = { num |
        num.to_s.dice.reduce 0 { sum, n | sum = sum + n.to_i ^ 2 }
      }
  
      happy? = { n, seen = set.new |
        when {true? happiness.include? n } { happiness.merge seen << n; true }
            { true? sadness.include? n } { sadness.merge seen; false }
            { true? seen.include? n } { sadness.merge seen; false }
            { true } { seen << n; happy? sum_of_squares_of_digits(n), seen }
      }

      num = 1
      happies = []

      while { happies.length < 8 } {
        true? happy?(num)
          { happies << num }

        num = num + 1
      }

      happies
    }
  }

  test "higher order functions" {
    assert_equal 3 {
      add = { a, b | a + b }

      doit = { f, a, b | f a, b }

      doit ->add 1 2
    }
  }

  test "increment integer string" {
    assert_equal "101" {
      ("100".to_i + 1).to_s
    }
  } 

  test "mean" {
    assert_equal 5.5 {
      mean = { list |
        true? list.empty?, 0, { list.reduce(0, :+) / list.length }
      }

      mean 1.to 10
    }
  }

  test "mutual recursion" {
    skip "Mutual recursion is currently broken due to function lifting"

    female = null #yes, this is necessary

      male = { n |
        true? n == 0
        { 0 }
        { n - female male(n - 1) }
      }

    female = { n |
      true? n == 0
      { 1 }
      { n - male female(n - 1 ) }
    }

    assert_equal [1, 1, 2, 2, 3, 3, 4, 5, 5, 6, 6, 7, 8, 8, 9, 9, 10, 11, 11, 12, 13] {
      0.to(20).map! { n | female n }
    }

    assert_equal [0, 0, 1, 2, 2, 3, 4, 4, 5, 6, 6, 7, 7, 8, 9, 9, 10, 11, 11, 12, 12] {
      0.to(20).map! { n | male n }
    }
  }

  test "palindrome" {
    palindrome? = { str | 
      str = str.downcase.sub(/\s+/, "")
      str == str.reverse 
    }

    assert { palindrome? "In girum imus nocte et consumimur igni" }
    assert { palindrome? "A man a plan a canal Panama" }
    assert { palindrome? "racecar" }
    assert_false { palindrome? "No way this is a palindrome" }
  }

  test "pangram" {
    pangram? = { sentence |
      sentence.downcase.dice.unique.select(:alpha?).length == 26
    }

    assert { pangram? 'The quick brown fox jumps over the lazy dog.' }
    assert_false { pangram? 'Probably not a pangram.' }
  }

  test "sum of squares" {
    assert_equal 385 { 1.to(10).reduce 0 { res, n | res = res + n ^ 2 } } 
  }

  test "unknown method" {
    assert_equal "not_real" {
      example = object.new

      example.no_method = { meth_name, *args |
        meth_name
      }

      example.not_real "at all"
    }
  }
}