// @flow
/* eslint-env jest */

import {
    bottom,
    collides,
    compact,
    moveElement,
    sortLayoutItemsByRowCol,
    validateLayout
  } from "../../src/helpers/utils";
  
  describe("bottom", () => {
    it("Handles an empty layout as input", () => {
      expect(bottom([])).toEqual(0);
    });
  
    it("Returns the bottom coordinate of the layout", () => {
      expect(
        bottom([
          { i: "1", x: 0, y: 1, w: 1, h: 1 },
          { i: "2", x: 1, y: 2, w: 1, h: 1 }
        ])
      ).toEqual(3);
    });
  });
  
  describe("sortLayoutItemsByRowCol", () => {
    it("should sort by top to bottom right", () => {
      const layout = [
        { x: 1, y: 1, w: 1, h: 1, i: "2" },
        { x: 1, y: 0, w: 1, h: 1, i: "1" },
        { x: 0, y: 1, w: 2, h: 2, i: "3" }
      ];
      expect(sortLayoutItemsByRowCol(layout)).toEqual([
        { x: 1, y: 0, w: 1, h: 1, i: "1" },
        { x: 0, y: 1, w: 2, h: 2, i: "3" },
        { x: 1, y: 1, w: 1, h: 1, i: "2" }
      ]);
    });
  });

  describe("collides", () => {
    it("Returns whether the layout items collide", () => {
      expect(
        collides(
          { i: "1", x: 0, y: 1, w: 1, h: 1 },
          { i: "2", x: 1, y: 2, w: 1, h: 1 }
        )
      ).toEqual(false);
      expect(
        collides(
          { i: "1", x: 0, y: 1, w: 1, h: 1 },
          { i: "2", x: 0, y: 1, w: 1, h: 1 }
        )
      ).toEqual(true);
    });
  });
  
  describe("validateLayout", () => {
    it("Validates an empty layout", () => {
      validateLayout([]);
    });
    it("Validates a populated layout", () => {
      validateLayout([
        { i: "1", x: 0, y: 1, w: 1, h: 1 },
        { i: "2", x: 1, y: 2, w: 1, h: 1 }
      ]);
    });
    it("Throws errors on invalid input", () => {
      expect(() => {
        validateLayout([
          { i: "1", x: 0, y: 1, w: 1, h: 1 },
          { i: "2", x: 1, y: 2, w: 1 }
        ]);
      }).toThrowError(/layout\[1\]\.h must be a number!/i);
    });
  });
  
  describe("moveElement", () => {
    function compactAndMove(
      layout,
      layoutItem,
      x,
      y,
      isUserAction,
      preventCollision
    ) {
      return compact(
        moveElement(
          layout,
          layoutItem,
          x,
          y,
          isUserAction,
          preventCollision
        )
      );
    }
  
    it("Does not change layout when colliding on no rearrangement mode", () => {
      const layout = [
        { i: "1", x: 0, y: 1, w: 1, h: 1, moved: false },
        { i: "2", x: 1, y: 2, w: 1, h: 1, moved: false }
      ];
      const layoutItem = layout[0];
      expect(
        moveElement(
          layout,
          layoutItem,
          1,
          2, // x, y
          true,
          true // isUserAction, preventCollision
        )
      ).toEqual([
        { i: "1", x: 0, y: 1, w: 1, h: 1, moved: false },
        { i: "2", x: 1, y: 2, w: 1, h: 1, moved: false }
      ]);
    });
  
    it("Does change layout when colliding in rearrangement mode", () => {
      const layout = [
        { i: "1", x: 0, y: 0, w: 1, h: 1, moved: false },
        { i: "2", x: 1, y: 0, w: 1, h: 1, moved: false }
      ];
      const layoutItem = layout[0];
      expect(
        moveElement(
          layout,
          layoutItem,
          1,
          0, // x, y
          true,
          false // isUserAction, preventCollision
        )
      ).toEqual([
        { i: "1", x: 1, y: 0, w: 1, h: 1, moved: true },
        { i: "2", x: 1, y: 1, w: 1, h: 1, moved: true }
      ]);
    });
  
    it("Moves elements out of the way without causing panel jumps when compaction is vertical", () => {
      const layout = [
        { x: 0, y: 0, w: 1, h: 10, i: "A" },
        { x: 0, y: 10, w: 1, h: 1, i: "B" },
        { x: 0, y: 11, w: 1, h: 1, i: "C" }
      ];
      // move A down slightly so it collides with C; can cause C to jump above B.
      // We instead want B to jump above A (it has the room)
      const itemA = layout[0];
      expect(
        compactAndMove(
          layout,
          itemA,
          0,
          1, // x, y
          true,
          false // isUserAction, preventCollision
        )
      ).toEqual([
        expect.objectContaining({ x: 0, y: 1, w: 1, h: 10, i: "A" }),
        expect.objectContaining({ x: 0, y: 0, w: 1, h: 1, i: "B" }),
        expect.objectContaining({ x: 0, y: 11, w: 1, h: 1, i: "C" })
      ]);
    });
  
    it("Calculates the correct collision when moving large object far", () => {
      const layout = [
        { x: 0, y: 0, w: 1, h: 10, i: "A" },
        { x: 0, y: 10, w: 1, h: 1, i: "B" },
        { x: 0, y: 11, w: 1, h: 1, i: "C" }
      ];
      // Move A down by 2. This should move B above, but since we don't compact in between,
      // C should move below.
      const itemA = layout[0];
      expect(
        moveElement(
          layout,
          itemA,
          0,
          2, // x, y
          true,
          false // isUserAction, preventCollision
        )
      ).toEqual([
        expect.objectContaining({ x: 0, y: 2, w: 1, h: 10, i: "A" }),
        expect.objectContaining({ x: 0, y: 1, w: 1, h: 1, i: "B" }),
        expect.objectContaining({ x: 0, y: 12, w: 1, h: 1, i: "C" })
      ]);
    });
  
    it("Moves elements out of the way without causing panel jumps when compaction is vertical", () => {
      const layout = [
        { x: 0, y: 0, w: 1, h: 1, i: "A" },
        { x: 1, y: 0, w: 1, h: 1, i: "B" },
        { x: 0, y: 1, w: 2, h: 2, i: "C" }
      ];
      // move A over slightly so it collides with B; can cause C to jump above B
      // this test will check that that does not happen
      const itemA = layout[0];
      expect(
        moveElement(
          layout,
          itemA,
          1,
          0, // x, y
          true,
          false // isUserAction, preventCollision
        )
      ).toEqual([
        { x: 1, y: 0, w: 1, h: 1, i: "A", moved: true },
        { x: 1, y: 1, w: 1, h: 1, i: "B", moved: true },
        { x: 0, y: 2, w: 2, h: 2, i: "C", moved: true }
      ]);
    });
  
    it("Moves one element to another should cause moving down panels, vert compact", () => {
      // | A | B |
      // |C|  D  |
      const layout = [
        { x: 0, y: 0, w: 2, h: 1, i: "A" },
        { x: 2, y: 0, w: 2, h: 1, i: "B" },
        { x: 0, y: 1, w: 1, h: 1, i: "C" },
        { x: 1, y: 1, w: 3, h: 1, i: "D" }
      ];
      // move B left slightly so it collides with A; can cause C to jump above A
      // this test will check that that does not happen
      const itemB = layout[1];
      expect(
        compactAndMove(
          layout,
          itemB,
          1,
          0, // x, y
          true,
          false // isUserAction, preventCollision
        )
      ).toEqual([
        expect.objectContaining({ x: 0, y: 1, w: 2, h: 1, i: "A" }),
        expect.objectContaining({ x: 1, y: 0, w: 2, h: 1, i: "B" }),
        expect.objectContaining({ x: 0, y: 2, w: 1, h: 1, i: "C" }),
        expect.objectContaining({ x: 1, y: 2, w: 3, h: 1, i: "D" })
      ]);
    });
  
    it("Moves one element to another should cause moving down panels, vert compact", () => {
      // | A |
      // |B|C|
      //   | |
      //
      // Moving C above A should not move B above A
      const layout = [
        { x: 0, y: 0, w: 2, h: 1, i: "A" },
        { x: 0, y: 1, w: 1, h: 1, i: "B" },
        { x: 1, y: 1, w: 1, h: 2, i: "C" }
      ];
      // Move C up.
      const itemB = layout[2];
      expect(
        compactAndMove(
          layout,
          itemB,
          1,
          0, // x, y
          true,
          false // isUserAction, preventCollision
        )
      ).toEqual([
        expect.objectContaining({ x: 0, y: 2, w: 2, h: 1, i: "A" }),
        expect.objectContaining({ x: 0, y: 3, w: 1, h: 1, i: "B" }),
        expect.objectContaining({ x: 1, y: 0, w: 1, h: 2, i: "C" })
      ]);
    });
  });
  
  describe("compact vertical", () => {
    it("Removes empty vertical space above item", () => {
      const layout = [{ i: "1", x: 0, y: 1, w: 1, h: 1 }];
      expect(compact(layout, true)).toEqual([
        { i: "1", x: 0, y: 0, w: 1, h: 1, moved: false }
      ]);
    });
  
    it("Resolve collision by moving item further down in array", () => {
      const layout = [
        { x: 0, y: 0, w: 1, h: 5, i: "1" },
        { x: 0, y: 1, w: 1, h: 1, i: "2" }
      ];
      expect(compact(layout, true)).toEqual([
        { x: 0, y: 0, w: 1, h: 5, i: "1", moved: false },
        { x: 0, y: 5, w: 1, h: 1, i: "2", moved: false }
      ]);
    });
  
    it("Handles recursive collision by moving new collisions out of the way before moving item down", () => {
      const layout = [
        { x: 0, y: 0, w: 2, h: 5, i: "1" },
        { x: 0, y: 0, w: 10, h: 1, i: "2" },
        { x: 5, y: 1, w: 1, h: 1, i: "3" },
        { x: 5, y: 2, w: 1, h: 1, i: "4" },
        { x: 5, y: 3, w: 1, h: 1, i: "5", static: true }
      ];
  
      expect(compact(layout, true)).toEqual([
        { x: 0, y: 0, w: 2, h: 5, i: "1", moved: false },
        { x: 0, y: 5, w: 10, h: 1, i: "2", moved: false },
        { x: 5, y: 0, w: 1, h: 1, i: "3", moved: false },
        { x: 5, y: 1, w: 1, h: 1, i: "4", moved: false },
        { x: 5, y: 3, w: 1, h: 1, i: "5", moved: false, static: true }
      ]);
    });
  });
