import type { AssertionError } from "Test"
import { assertEquals, test, ErrorWithMessage } from "Test"
import { always, Loop, Done } from "Function"
import List from "List"
import type { Wish } from "./Wish"
import { after, bad, good, bichain, mapRej, parallel, parallelN, raceN } from "./Wish"


test("map should map over a value if it's a good Wish", () => pipe(
  map((x) => x + 1),
  chain(assertEquals($, 4))
)(good(3)))

test("map should be identity if it's a bad Wish", () => pipe(
  map((x) => x + 1),
  bichain(
    assertEquals($, 3),
    always(bad(ErrorWithMessage("Wish should be bad")))
  )
)(bad(3)))

test("mapRej should map over a value if it's a bad Wish", () => pipe(
  mapRej((x) => x + 1),
  bichain(
    assertEquals($, 4),
    always(bad(ErrorWithMessage("Wish should be bad")))
  )
)(bad(3)))

test("mapRej should be identity if it's a good Wish", () => pipe(
  mapRej((x) => x + 1),
  bichain(
    always(bad(ErrorWithMessage("Wish should be good"))),
    assertEquals($, 3)
  )
)(good(3)))

test("mapFirst should map over the rejected value", () => pipe(
  mapFirst((x) => x + 1),
  bichain(
    assertEquals($, 4),
    always(bad(ErrorWithMessage("Wish should be bad")))
  )
)(bad(3)))

test("mapSecond should map over the resolved value", () => pipe(
  mapSecond((x) => x + 1),
  bichain(
    always(bad(ErrorWithMessage("Wish should be bad"))),
    assertEquals($, 4)
  )
)(good(3)))

test("ap should apply a function wrapped in a good Wish to a good Wish", () => pipe(
  ap(good((x) => x + 1)),
  chain(assertEquals($, 4))
)(good(3)))

test("ap should not apply a function wrapped in a good Wish to a bad Wish", () => pipe(
  ap(good((x) => x + 1)),
  bichain(
    assertEquals($, 3),
    always(bad(ErrorWithMessage("Wish should be bad")))
  )
)(bad(3)))

test("bimap should map over the bad value if given a bad wish", () => pipe(
  bimap((x) => x + 1, (x) => x * 2),
  bichain(
    assertEquals($, 4),
    always(bad(ErrorWithMessage("wish should be bad")))
  )
)(bad(3)))

test("bimap should map over the good value if given a good wish", () => pipe(
  bimap((x) => x + 1, (x) => x * 2),
  bichain(
    always(bad(ErrorWithMessage("wish should be good"))),
    assertEquals($, 6)
  )
)(good(3)))

test("parallel should return the result of all given wishes in a list", () => pipe(
  parallel,
  chain(assertEquals($, [1, 2, 3]))
)([good(1), good(2), good(3)]))

test("parallel should return a bad Wish if one of the given wishes is bad", () => pipe(
  parallel,
  bichain(
    assertEquals($, 2),
    always(bad(ErrorWithMessage("Wish should be bad")))
  )
)([good(1), bad(2), good(3), bad(4)]))

test("parallel should handle an empty list", () => pipe(
  parallel,
  chain(assertEquals($, []))
)([]))

test("assoc should prefer the first successful wish", () => do {
  result <- assoc(good(3), good(4))
  return assertEquals(result, 3)
})

test("assoc should fall back to the second wish when the first fails", () => bichain(
  always(bad(ErrorWithMessage("Wish should be good"))),
  assertEquals($, 4),
)(assoc(bad(3), good(4))))

test("parallel - lots of items", () => pipe(
  map(of),
  parallelN(200),
  chain(
    assertEquals($, List.range(0, 10000)),
  )
)(List.range(0, 10000)))

test("raceN", () => pipe(
  raceN(2),
  bichain(always(bad(ErrorWithMessage("It should have resolved"))), assertEquals($, "YOU")),
)([
  after(1000, "NOPE"),
  after(300, "YOU"),
]))

test("raceN - first failed", () => pipe(
  raceN(2),
  bichain(always(bad(ErrorWithMessage("It should have resolved"))), assertEquals($, "YOU")),
)([
  chain(always(bad("OR NOT")), after(100, "MAYBE")),
  after(300, "YOU"),
]))

test("raceN - some failed", () => pipe(
  raceN(5),
  bichain(always(bad(ErrorWithMessage("It should have resolved"))), assertEquals($, "YOU")),
)([
  chain(always(bad("OR NOT")), after(100, "MAYBE")),
  chain(always(bad("OR NOT")), after(400, "MAYBE")),
  after(400, "AFTER"),
  after(300, "YOU"),
  chain(always(bad("OR NOT")), after(200, "MAYBE")),
]))

test("tailRecM", () => do {
  millionWish :: Integer -> Wish AssertionError Integer
  millionWish = (x) => {
    go = (value) =>
      value < 5000
        ? good(Loop(value + 1))
        : good(Done(value))

    return tailRecM(go, x)
  }

  million <- millionWish(0)

  return assertEquals(million, 5000)
})

test("tailRecM - immediate Done", () => do {
  finished <- tailRecM((x) => good(Done(x + 1)), 3)

  return assertEquals(finished, 4)
})
