import { describe, test, expect } from '@jest/globals';
import { LinkedList } from '#js/util/LinkedList.js';

function inReverse(a: number, b: number) {
  return a > b;
}

describe('LinkedList functionality', () => {
  test('Empty list is empty array', () => {
    const list = new LinkedList();
    expect(Array.from(list)).toEqual([]);
  });

  test('Adding one item', () => {
    const list = new LinkedList();
    const item = list.push('x');
    expect(Array.from(list)).toEqual(['x']);
    expect(item).toBe(list);
  });

  test('Removing an item from list of 1 item', () => {
    const list = new LinkedList('x');
    const item = list.pop();
    expect(Array.from(list)).toEqual([]);
    expect(item).toBe('x');
  });

  test('Removing from empty list', () => {
    const list = new LinkedList();
    const item1 = list.pop();
    expect(Array.from(list)).toEqual([]);
    expect(item1).toBe(null);
    const item2 = list.shift();
    expect(Array.from(list)).toEqual([]);
    expect(item2).toBe(null);
  });

  test('Removing items', () => {
    expect(Array.from(new LinkedList(1, 2, 3, 4).remove())).toEqual([
      1, 2, 3, 4,
    ]); // remove nothing
    expect(Array.from(new LinkedList(1, 2, 3, 4).remove(1))).toEqual([2, 3, 4]); // at start
    expect(Array.from(new LinkedList(1, 2, 3, 4).remove(4))).toEqual([1, 2, 3]); // at end
    expect(Array.from(new LinkedList(1, 2, 3, 4).remove(2))).toEqual([1, 3, 4]); // in midle
    expect(Array.from(new LinkedList(1, 2, 3, 4).remove(1, 3, 4))).toEqual([2]); // multiple items removed
    expect(Array.from(new LinkedList(1, 2, 3, 4).remove(6))).toEqual([
      1, 2, 3, 4,
    ]); // item not in list
    expect(Array.from(new LinkedList(1, 2, 3, 4).remove(2, 6))).toEqual([
      1, 3, 4,
    ]); // some items not in list
    expect(Array.from(new LinkedList(1, 2, 3, 4).remove(1, 2, 3, 4))).toEqual(
      []
    ); // all items removed
  });

  test('Adding nothing', () => {
    expect(Array.from(new LinkedList().push())).toEqual([]);
    expect(Array.from(new LinkedList().unshift())).toEqual([]);
    expect(Array.from(new LinkedList(1, 2).push())).toEqual([1, 2]);
    expect(Array.from(new LinkedList(1, 2).unshift())).toEqual([1, 2]);
  });

  test('unshift to empty array', () => {
    const list = new LinkedList();
    expect(Array.from(list.unshift('x'))).toEqual(['x']);
    const item = list.shift();
    expect(Array.from(list)).toEqual([]);
    expect(item).toBe('x');
  });

  test('Pushing several items', () => {
    const list = new LinkedList();
    list.push(2, 3, 4);
    expect(Array.from(list)).toEqual([2, 3, 4]);
    list.push(5, 6);
    expect(Array.from(list)).toEqual([2, 3, 4, 5, 6]);
    const item = list.unshift(0, 1);
    expect(Array.from(list)).toEqual([0, 1, 2, 3, 4, 5, 6]);
    expect(item).toBe(list);
  });

  test('Iterator', () => {
    const list = new LinkedList(0, 1, 2, 3, 4, 5, 6);
    let j = 0;
    for (const item of list) {
      if (item !== j) break;
      j++;
    }
    expect(j).toBe(7);
  });

  test('Reversed iterator', () => {
    const list = new LinkedList(0, 1, 2, 3, 4, 5, 6);
    let j = 6;
    for (const item of list.reversed()) {
      if (item !== j) break;
      j--;
    }
    expect(j).toBe(-1);
  });

  test('Removing items by shift', () => {
    const list = new LinkedList(0, 1, 2, 3);
    expect(list.shift()).toBe(0);
    expect(Array.from(list)).toEqual([1, 2, 3]);
    expect(list.pop()).toBe(3);
    expect(Array.from(list)).toEqual([1, 2]);
  });

  test('Clearing of list', () => {
    const list = new LinkedList(1, 2, 3);
    expect(Array.from(list.clear())).toEqual([]);
    expect(list.clear()).toBe(list); // clear() returns list
  });

  test('Sorting of list', () => {
    const list = new LinkedList();
    expect(list.sort()).toBe(list); // sort() returns list
    expect(Array.from(list.sort())).toEqual([]); // empty list
    expect(Array.from(new LinkedList(5, 1, 3, 6, 4, 0, 2).sort())).toEqual([
      0, 1, 2, 3, 4, 5, 6,
    ]);
    expect(
      Array.from(new LinkedList(5, 1, 3, 6, 4, 0, 2).sort(inReverse))
    ).toEqual([6, 5, 4, 3, 2, 1, 0]);
  });

  test('Inserting item in sorted list', () => {
    const list = new LinkedList();
    expect(list.insert(3)).toBe(list); // insert() returns list
    expect(Array.from(new LinkedList().insert(3))).toEqual([3]); // with empty list
    expect(Array.from(new LinkedList(5, 3, 4, 1).sort().insert(0))).toEqual([
      0, 1, 3, 4, 5,
    ]); // at start
    expect(Array.from(new LinkedList(5, 3, 4, 1).sort().insert(6))).toEqual([
      1, 3, 4, 5, 6,
    ]); // at end
    expect(Array.from(new LinkedList(5, 3, 4, 1).sort().insert(2))).toEqual([
      1, 2, 3, 4, 5,
    ]); // in middle
  });

  test('Inserting with sort function', () => {
    expect(Array.from(new LinkedList().insert(3, inReverse))).toEqual([3]); // with empty list
    expect(Array.from(new LinkedList(3, 2, 1).insert(4, inReverse))).toEqual([
      4, 3, 2, 1,
    ]); // at start
    expect(Array.from(new LinkedList(3, 2, 1).insert(0, inReverse))).toEqual([
      3, 2, 1, 0,
    ]); // at end
    expect(Array.from(new LinkedList(4, 3, 1).insert(2, inReverse))).toEqual([
      4, 3, 2, 1,
    ]); // in middle
  });

  test('Merging of lists', () => {
    const list = new LinkedList(1, 3);
    expect(list.merge(new LinkedList(2, 4))).toBe(list); // merge() return list
    expect(Array.from(new LinkedList().merge(new LinkedList()))).toEqual([]); // with two empty lists
    expect(Array.from(new LinkedList().merge(new LinkedList(1, 3, 5)))).toEqual(
      [1, 3, 5]
    ); // with 1st list empty
    expect(Array.from(new LinkedList(1, 3, 5).merge(new LinkedList()))).toEqual(
      [1, 3, 5]
    ); // with 2nd list empty
    expect(
      Array.from(
        new LinkedList(1, 3, 4, 7, 8, 9).merge(new LinkedList(2, 5, 6, 10, 11))
      )
    ).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]); // intermized lists
    expect(
      Array.from(new LinkedList(1, 3, 4).merge(new LinkedList(1, 2, 4, 5)))
    ).toEqual([1, 1, 2, 3, 4, 4, 5]); // merge with repeats
    expect(
      Array.from(new LinkedList(1, 2, 3, 4).merge(new LinkedList(5, 6, 7, 8)))
    ).toEqual([1, 2, 3, 4, 5, 6, 7, 8]); // at end
    expect(
      Array.from(new LinkedList(5, 6, 7, 8).merge(new LinkedList(1, 2, 3, 4)))
    ).toEqual([1, 2, 3, 4, 5, 6, 7, 8]); // at start
    expect(
      Array.from(new LinkedList(1, 2, 7, 8).merge(new LinkedList(3, 4, 5, 6)))
    ).toEqual([1, 2, 3, 4, 5, 6, 7, 8]); // in the middle
    expect(
      Array.from(
        new LinkedList(9, 8, 7, 4, 3, 1).merge(
          new LinkedList(11, 10, 6, 5, 2, 0),
          inReverse
        )
      )
    ).toEqual([11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0]); // with sort function
  });
});
