discard """
  matrix: "--mm:refc; --mm:orc"
  targets: "c js"
"""

import std/[lists, sequtils]
import std/assertions

const
  data = [1, 2, 3, 4, 5, 6]


template main =
  block SinglyLinkedListTest1:
    var L: SinglyLinkedList[int]
    for d in items(data): L.prepend(d)
    for d in items(data): L.add(d)
    doAssert($L == "[6, 5, 4, 3, 2, 1, 1, 2, 3, 4, 5, 6]")

    doAssert(4 in L)

  block SinglyLinkedListTest2:
    var L: SinglyLinkedList[string]
    for d in items(data): L.prepend($d)
    doAssert($L == """["6", "5", "4", "3", "2", "1"]""")

    doAssert("4" in L)


  block DoublyLinkedListTest1:
    var L: DoublyLinkedList[int]
    for d in items(data): L.prepend(d)
    for d in items(data): L.add(d)
    L.remove(L.find(1))
    doAssert($L == "[6, 5, 4, 3, 2, 1, 2, 3, 4, 5, 6]")

    doAssert(4 in L)

  block SinglyLinkedRingTest1:
    var L: SinglyLinkedRing[int]
    L.prepend(4)
    doAssert($L == "[4]")
    L.prepend(4)

    doAssert($L == "[4, 4]")
    doAssert(4 in L)


  block DoublyLinkedRingTest1:
    var L: DoublyLinkedRing[int]
    L.prepend(4)
    doAssert($L == "[4]")
    L.prepend(4)

    doAssert($L == "[4, 4]")
    doAssert(4 in L)

    L.add(3)
    L.add(5)
    doAssert($L == "[4, 4, 3, 5]")

    L.remove(L.find(3))
    L.remove(L.find(5))
    L.remove(L.find(4))
    L.remove(L.find(4))
    doAssert($L == "[]")
    doAssert(4 notin L)

  block tlistsToString:
    block:
      var l = initDoublyLinkedList[int]()
      l.add(1)
      l.add(2)
      l.add(3)
      doAssert $l == "[1, 2, 3]"
    block:
      var l = initDoublyLinkedList[string]()
      l.add("1")
      l.add("2")
      l.add("3")
      doAssert $l == """["1", "2", "3"]"""
    block:
      var l = initDoublyLinkedList[char]()
      l.add('1')
      l.add('2')
      l.add('3')
      doAssert $l == """['1', '2', '3']"""

  # Copied here until it is merged into sequtils
  template take(a: untyped, max: int): untyped =
    type T = typeof(block: (for ai in a: ai))
    var ret: seq[T]
    var i = 0
    if max > 0:
      for ai in a:
        ret.add ai
        i.inc
        if i >= max: break
    ret

  template testCommon(initList, toList) =

    block: # toSinglyLinkedList, toDoublyLinkedList
      let l = seq[int].default
      doAssert l.toList.toSeq == []
      doAssert [1].toList.toSeq == [1]
      doAssert [1, 2, 3].toList.toSeq == [1, 2, 3]

    block copy:
      doAssert array[0, int].default.toList.copy.toSeq == []
      doAssert [1].toList.copy.toSeq == [1]
      doAssert [1, 2].toList.copy.toSeq == [1, 2]
      doAssert [1, 2, 3].toList.copy.toSeq == [1, 2, 3]
      type Foo = ref object
        x: int
      var f0 = Foo(x: 0)
      let f1 = Foo(x: 1)
      var a = [f0].toList
      var b = a.copy
      b.add f1
      doAssert a.toSeq == [f0]
      doAssert b.toSeq == [f0, f1]
      f0.x = 42
      doAssert a.head.value.x == 42
      doAssert b.head.value.x == 42

    block: # add, addMoved
      block:
        var
          l0 = initList[int]()
          l1 = [1].toList
          l2 = [2, 3].toList
          l3 = [4, 5, 6].toList
        l0.add l3
        l1.add l3
        l2.addMoved l3
        doAssert l0.toSeq == [4, 5, 6]
        doAssert l1.toSeq == [1, 4, 5, 6]
        doAssert l2.toSeq == [2, 3, 4, 5, 6]
        doAssert l3.toSeq == []
        l2.add l3 # re-adding l3 that was destroyed is now a no-op
        doAssert l2.toSeq == [2, 3, 4, 5, 6]
        doAssert l3.toSeq == []
      block:
        var
          l0 = initList[int]()
          l1 = [1].toList
          l2 = [2, 3].toList
          l3 = [4, 5, 6].toList
        l3.addMoved l0
        l2.addMoved l1
        doAssert l3.toSeq == [4, 5, 6]
        doAssert l2.toSeq == [2, 3, 1]
        l3.add l0
        doAssert l3.toSeq == [4, 5, 6]
      block:
        var c = [0, 1].toList
        c.addMoved c
        doAssert c.take(6) == [0, 1, 0, 1, 0, 1]

    block: # prepend, prependMoved
      block:
        var
          l0 = initList[int]()
          l1 = [1].toList
          l2 = [2, 3].toList
          l3 = [4, 5, 6].toList
        l0.prepend l3
        l1.prepend l3
        doAssert l3.toSeq == [4, 5, 6]
        l2.prependMoved l3
        doAssert l0.toSeq == [4, 5, 6]
        doAssert l1.toSeq == [4, 5, 6, 1]
        doAssert l2.toSeq == [4, 5, 6, 2, 3]
        doAssert l3.toSeq == []
        l2.prepend l3 # re-prepending l3 that was destroyed is now a no-op
        doAssert l2.toSeq == [4, 5, 6, 2, 3]
        doAssert l3.toSeq == []
      block:
        var
          l0 = initList[int]()
          l1 = [1].toList
          l2 = [2, 3].toList
          l3 = [4, 5, 6].toList
        l3.prependMoved l0
        l2.prependMoved l1
        doAssert l3.toSeq == [4, 5, 6]
        doAssert l2.toSeq == [1, 2, 3]
        l3.prepend l0
        doAssert l3.toSeq == [4, 5, 6]
      block:
        var c = [0, 1].toList
        c.prependMoved c
        doAssert c.take(6) == [0, 1, 0, 1, 0, 1]

    block remove:
      var l = [0, 1, 2, 3].toList
      let
        l0 = l.head
        l1 = l0.next
        l2 = l1.next
        l3 = l2.next
      l.remove l0
      doAssert l.toSeq == [1, 2, 3]
      l.remove l2
      doAssert l.toSeq == [1, 3]
      l.remove l2
      doAssert l.toSeq == [1, 3]
      l.remove l3
      doAssert l.toSeq == [1]
      l.remove l1
      doAssert l.toSeq == []
      # Cycle preservation
      var a = [10, 11, 12].toList
      a.addMoved a
      doAssert a.take(6) == @[10, 11, 12, 10, 11, 12]
      a.remove a.head.next
      doAssert a.take(6) == @[10, 12, 10, 12, 10, 12]
      a.remove a.head
      doAssert a.take(6) == @[12, 12, 12, 12, 12, 12]

  testCommon initSinglyLinkedList, toSinglyLinkedList
  testCommon initDoublyLinkedList, toDoublyLinkedList

  block remove: # return value check
    var l = [0, 1, 2, 3].toSinglyLinkedList
    let n = l.head.next.next
    doAssert l.remove(n) == true
    doAssert l.toSeq == [0, 1, 3]
    doAssert l.remove(n) == false
    doAssert l.toSeq == [0, 1, 3]
    doAssert l.remove(l.head) == true
    doAssert l.toSeq == [1, 3]
    doAssert l.remove(l.head.next) == true
    doAssert l.toSeq == [1]
    doAssert l.remove(l.head) == true
    doAssert l.toSeq == []
  
  block issue19297: # add (appends a shallow copy)
    var a: SinglyLinkedList[int]
    var b: SinglyLinkedList[int]

    doAssert a.toSeq == @[]
    a.add(1)
    doAssert a.toSeq == @[1]
    a.add(b)
    doAssert a.toSeq == @[1]
    a.add(2)
    doAssert a.toSeq == @[1, 2]
  
  block issue19314: # add (appends a shallow copy)
    var a: DoublyLinkedList[int]
    var b: DoublyLinkedList[int]

    doAssert a.toSeq == @[]
    a.add(1)
    doAssert a.toSeq == @[1]
    a.add(b)
    doAssert a.toSeq == @[1]
    a.add(2)
    doAssert a.toSeq == @[1, 2]

  block RemoveLastNodeFromSinglyLinkedList:
    var list = initSinglyLinkedList[string]()
    let n1 = newSinglyLinkedNode("sonic")
    let n2 = newSinglyLinkedNode("the")
    let n3 = newSinglyLinkedNode("tiger")
    let n4 = newSinglyLinkedNode("hedgehog")
    list.add(n1)
    list.add(n2)
    list.add(n3)
    list.remove(n3)
    list.add(n4)
    doAssert list.toSeq == @["sonic", "the", "hedgehog"]

static: main()
main()
