import {
  top,
  bottom,
  left,
  right,
  near,
  snap,
  overlapY,
  overlapX,
  snapWithin,
  applySnap,
  snapDiff,
  boundingBox,
  applyMultipleDiffs,
} from "./snapUtils";

describe("side functions", () => {
  const box = { x: 10, y: 15, width: 50, height: 100 };
  it("can find the top of a box", () => {
    const actual = top(box);
    const expected = 15;
    expect(actual).toEqual(expected);
  });
  it("can find the bottom of a box", () => {
    const actual = bottom(box);
    const expected = 115;
    expect(actual).toEqual(expected);
  });
  it("can find the left of a box", () => {
    const actual = left(box);
    const expected = 10;
    expect(actual).toEqual(expected);
  });
  it("can find the right of a box", () => {
    const actual = right(box);
    const expected = 60;
    expect(actual).toEqual(expected);
  });
});

describe("near function", () => {
  it("can tell if A is near B", () => {
    const actual = near(10, 20);
    const expected = true;
    expect(actual).toEqual(expected);
  });
  it("can tell if A is near B", () => {
    const actual = near(10, 30);
    const expected = false;
    expect(actual).toEqual(expected);
  });
});

describe("overlap functions", () => {
  it("overlapY detects when the boxes overlap in the Y axis", () => {
    const a = { y: 10, height: 50, width: 100, x: 0 };
    const b = { y: 40, height: 50, width: 100, x: 0 };
    const actual = overlapY(a, b);
    const expected = true;
    expect(actual).toEqual(expected);
  });
  it("overlapY detects when the boxes are within SNAP_DISTANCE on the Y axis", () => {
    const a = { y: 10, height: 50, width: 100, x: 0 };
    const b = { y: 70, height: 50, width: 100, x: 0 };
    const actual = overlapY(a, b);
    const expected = true;
    expect(actual).toEqual(expected);
  });
  it("overlapY detects when the boxes do not overlap in the Y axis", () => {
    const a = { y: 10, height: 50, width: 100, x: 0 };
    const b = { y: 90, height: 50, width: 100, x: 0 };
    const actual = overlapY(a, b);
    const expected = false;
    expect(actual).toEqual(expected);
  });
  it("overlapX detects when the boxes overlap in the X axis", () => {
    const a = { x: 10, width: 50, height: 100, y: 0 };
    const b = { x: 40, width: 50, height: 100, y: 0 };
    const actual = overlapX(a, b);
    const expected = true;
    expect(actual).toEqual(expected);
  });
  it("overlapX detects when the boxes are within SNAP_DISTANCE on the X axis", () => {
    const a = { x: 10, width: 50, height: 100, y: 0 };
    const b = { x: 70, width: 50, height: 100, y: 0 };
    const actual = overlapX(a, b);
    const expected = true;
    expect(actual).toEqual(expected);
  });
  it("overlapX detects when the boxes do not overlap in the X axis", () => {
    const a = { x: 10, width: 50, height: 100, y: 0 };
    const b = { x: 90, width: 50, height: 100, y: 0 };
    const actual = overlapX(a, b);
    const expected = false;
    expect(actual).toEqual(expected);
  });
});

describe("snap function", () => {
  it("does not snap if A and B are obviously far apart", () => {
    const a = { x: 10, y: 10, width: 100, height: 100 };
    const b = { x: 200, y: 200, width: 100, height: 100 };
    const actual = snap(a, b);
    const expected = {};
    expect(actual).toEqual(expected);
  });
  it("snaps the left of A to the right of B", () => {
    const a = { x: 120, y: 30, width: 100, height: 100 };
    const b = { x: 10, y: 10, width: 100, height: 100 };
    const actual = snap(a, b);
    const expected = { x: 110 };
    expect(actual).toEqual(expected);
  });
  it("snaps the right of A to the left of B", () => {
    const a = { x: 10, y: 30, width: 100, height: 100 };
    const b = { x: 120, y: 10, width: 100, height: 100 };
    const actual = snap(a, b);
    const expected = { x: 20 };
    expect(actual).toEqual(expected);
  });
  it("snaps the top of A to the bottom of B", () => {
    const a = { x: 30, y: 10, width: 100, height: 100 };
    const b = { x: 10, y: 120, width: 100, height: 100 };
    const actual = snap(a, b);
    const expected = { y: 20 };
    expect(actual).toEqual(expected);
  });
  it("snaps the bottom of A to the top of B", () => {
    const a = { x: 30, y: 120, width: 100, height: 100 };
    const b = { x: 10, y: 10, width: 100, height: 100 };
    const actual = snap(a, b);
    const expected = { y: 110 };
    expect(actual).toEqual(expected);
  });
  it("does not snap to the X axis if A is below B", () => {
    const a = { x: 10, y: 10, width: 100, height: 100 };
    const b = { x: 110, y: 150, width: 100, height: 100 };
    const actual = snap(a, b);
    const expected = {};
    expect(actual).toEqual(expected);
  });
  it("snaps in both axis if the corners are within SNAP_DISTANCE", () => {
    const a = { x: 10, y: 10, width: 100, height: 100 };
    const b = { x: 120, y: 120, width: 100, height: 100 };
    const actual = snap(a, b);
    const expected = { x: 20, y: 20 };
    expect(actual).toEqual(expected);
  });
  it("snaps the left of A to the left of B", () => {
    const a = { x: 10, y: 10, width: 100, height: 100 };
    const b = { x: 15, y: 110, width: 100, height: 100 };
    const actual = snap(a, b);
    const expected = { x: 15, y: 10 };
    expect(actual).toEqual(expected);
  });
  it("snaps the top of A to the bottom of B", () => {
    const a = { x: 10, y: 10, width: 100, height: 100 };
    const b = { x: 110, y: 15, width: 100, height: 100 };
    const actual = snap(a, b);
    const expected = { x: 10, y: 15 };
    expect(actual).toEqual(expected);
  });
  it("snaps the top left corner of A to the bottom right corner of B", () => {
    const a = { x: 110, y: 110, width: 100, height: 100 };
    const b = { x: 0, y: 0, width: 100, height: 100 };
    const actual = snap(a, b);
    const expected = { x: 100, y: 100 };
    expect(actual).toEqual(expected);
  });
  it("snaps the top of A down to the bottom of B, if A slighterly overlaps B", () => {
    const a = { x: 0, y: 90, width: 100, height: 100 };
    const b = { x: 0, y: 0, width: 100, height: 100 };
    const actual = snap(a, b);
    const expected = { x: 0, y: 100 };
    expect(actual).toEqual(expected);
  });
  it("snaps the top of A down to the bottom of B, if A slighterly overlaps B", () => {
    const a = { x: 0, y: 90, width: 100, height: 100 };
    const b = { x: 0, y: 0, width: 100, height: 100 };
    const actual = snap(a, b);
    const expected = { x: 0, y: 100 };
    expect(actual).toEqual(expected);
  });
});

describe("snapWithin function", () => {
  it("snaps the inner box to the top of the outer box", () => {
    const inner = { x: 120, y: 10, width: 100, height: 100 };
    const outer = { width: 1000, height: 1000 };
    const actual = snapWithin(inner, outer);
    const expected = { y: 0 };
    expect(actual).toEqual(expected);
  });
  it("snaps the inner box to the bottom of the outer box", () => {
    const inner = { x: 120, y: 910, width: 100, height: 100 };
    const outer = { width: 1000, height: 1000 };
    const actual = snapWithin(inner, outer);
    const expected = { y: 900 };
    expect(actual).toEqual(expected);
  });
  it("snaps the inner box to the right of the outer box", () => {
    const inner = { x: 910, y: 120, width: 100, height: 100 };
    const outer = { width: 1000, height: 1000 };
    const actual = snapWithin(inner, outer);
    const expected = { x: 900 };
    expect(actual).toEqual(expected);
  });
  it("snaps the inner box to the left of the outer box", () => {
    const inner = { x: 10, y: 120, width: 100, height: 100 };
    const outer = { width: 1000, height: 1000 };
    const actual = snapWithin(inner, outer);
    const expected = { x: 0 };
    expect(actual).toEqual(expected);
  });
  it("snaps the inner box to the top-left of the outer box", () => {
    const inner = { x: 10, y: 10, width: 100, height: 100 };
    const outer = { width: 1000, height: 1000 };
    const actual = snapWithin(inner, outer);
    const expected = { x: 0, y: 0 };
    expect(actual).toEqual(expected);
  });
});

describe("applySnap function", () => {
  it("does not apply undefined values", () => {
    const original = { x: 10, y: 10 };
    const snapped = { x: undefined, y: undefined };
    const actual = applySnap(original, snapped);
    const expected = original;
    expect(actual).toEqual(expected);
  });
  it("preserves other values on original", () => {
    const original = { x: 10, y: 10, foo: "bar" };
    const snapped = { x: 50, y: 50 };
    const actual = applySnap(original, snapped);
    const expected = { x: 50, y: 50, foo: "bar" };
    expect(actual).toEqual(expected);
  });
  it('will clobber original with falsy "0" values', () => {
    const original = { x: 10, y: 10 };
    const snapped = { x: 0, y: 0 };
    const actual = applySnap(original, snapped);
    expect(actual).toEqual(snapped);
  });
  it('will clobber original with falsy "0" values', () => {
    const original = { x: 10, y: 10 };
    const snapped = { x: 0, y: 0 };
    const actual = applySnap(original, snapped);
    expect(actual).toEqual(snapped);
  });
  it("can apply multiple snaps", () => {
    const original = { x: 10, y: 10 };
    const snapped1 = { x: 0 };
    const snapped2 = { y: 0 };
    const actual = applySnap(original, snapped1, snapped2);
    const expected = { x: 0, y: 0 };
    expect(actual).toEqual(expected);
  });
  it("the last snap wins", () => {
    const original = { x: 10, y: 10 };
    const snapped1 = { x: 50, y: 20 };
    const snapped2 = { x: 60, y: 60 };
    const actual = applySnap(original, snapped1, snapped2);
    const expected = { x: 60, y: 60 };
    expect(actual).toEqual(expected);
  });
});

describe("snap diff", () => {
  it("does not snap if A and B are obviously far apart", () => {
    const a = { x: 10, y: 10, width: 100, height: 100 };
    const b = { x: 200, y: 200, width: 100, height: 100 };
    const actual = snapDiff(a, b);
    const expected = { x: 0, y: 0 };
    expect(actual).toEqual(expected);
  });
  it("snaps the left of A to the right of B", () => {
    const a = { x: 120, y: 30, width: 100, height: 100 };
    const b = { x: 10, y: 10, width: 100, height: 100 };
    const actual = snapDiff(a, b);
    const expected = { x: -10, y: 0 };
    expect(actual).toEqual(expected);
  });
  it("snaps the right of A to the left of B", () => {
    const a = { x: 10, y: 30, width: 100, height: 100 };
    const b = { x: 120, y: 10, width: 100, height: 100 };
    const actual = snapDiff(a, b);
    const expected = { x: 10, y: 0 };
    expect(actual).toEqual(expected);
  });
  it("snaps the top of A to the bottom of B", () => {
    const a = { x: 30, y: 10, width: 100, height: 100 };
    const b = { x: 10, y: 120, width: 100, height: 100 };
    const actual = snapDiff(a, b);
    const expected = { x: 0, y: 10 };
    expect(actual).toEqual(expected);
  });
  it("snaps the bottom of A to the top of B", () => {
    const a = { x: 30, y: 120, width: 100, height: 100 };
    const b = { x: 10, y: 10, width: 100, height: 100 };
    const actual = snapDiff(a, b);
    const expected = { x: 0, y: -10 };
    expect(actual).toEqual(expected);
  });
  it("does not snap to the X axis if A is below B", () => {
    const a = { x: 10, y: 10, width: 100, height: 100 };
    const b = { x: 110, y: 150, width: 100, height: 100 };
    const actual = snapDiff(a, b);
    const expected = { x: 0, y: 0 };
    expect(actual).toEqual(expected);
  });
  it("snaps in both axis if the corners are within SNAP_DISTANCE", () => {
    const a = { x: 10, y: 10, width: 100, height: 100 };
    const b = { x: 120, y: 120, width: 100, height: 100 };
    const actual = snapDiff(a, b);
    const expected = { x: 10, y: 10 };
    expect(actual).toEqual(expected);
  });
  it("snaps the left of A to the left of B", () => {
    const a = { x: 10, y: 10, width: 100, height: 100 };
    const b = { x: 15, y: 110, width: 100, height: 100 };
    const actual = snapDiff(a, b);
    const expected = { x: 5, y: 0 };
    expect(actual).toEqual(expected);
  });
  it("snaps the top of A to the bottom of B", () => {
    const a = { x: 10, y: 10, width: 100, height: 100 };
    const b = { x: 110, y: 15, width: 100, height: 100 };
    const actual = snapDiff(a, b);
    const expected = { x: 0, y: 5 };
    expect(actual).toEqual(expected);
  });
  it("snaps the top left corner of A to the bottom right corner of B", () => {
    const a = { x: 110, y: 110, width: 100, height: 100 };
    const b = { x: 0, y: 0, width: 100, height: 100 };
    const actual = snapDiff(a, b);
    const expected = { x: -10, y: -10 };
    expect(actual).toEqual(expected);
  });
});

describe("snapWithin function", () => {
  it("snaps the inner box to the top of the outer box", () => {
    const inner = { x: 120, y: 10, width: 100, height: 100 };
    const outer = { width: 1000, height: 1000 };
    const actual = snapWithin(inner, outer);
    const expected = { y: 0 };
    expect(actual).toEqual(expected);
  });
  it("snaps the inner box to the bottom of the outer box", () => {
    const inner = { x: 120, y: 910, width: 100, height: 100 };
    const outer = { width: 1000, height: 1000 };
    const actual = snapWithin(inner, outer);
    const expected = { y: 900 };
    expect(actual).toEqual(expected);
  });
  it("snaps the inner box to the right of the outer box", () => {
    const inner = { x: 910, y: 120, width: 100, height: 100 };
    const outer = { width: 1000, height: 1000 };
    const actual = snapWithin(inner, outer);
    const expected = { x: 900 };
    expect(actual).toEqual(expected);
  });
  it("snaps the inner box to the left of the outer box", () => {
    const inner = { x: 10, y: 120, width: 100, height: 100 };
    const outer = { width: 1000, height: 1000 };
    const actual = snapWithin(inner, outer);
    const expected = { x: 0 };
    expect(actual).toEqual(expected);
  });
  it("snaps the inner box to the top-left of the outer box", () => {
    const inner = { x: 10, y: 10, width: 100, height: 100 };
    const outer = { width: 1000, height: 1000 };
    const actual = snapWithin(inner, outer);
    const expected = { x: 0, y: 0 };
    expect(actual).toEqual(expected);
  });
});

describe("boundingBox function", () => {
  it("does not mutate the given array", () => {
    const box = { x: 10, y: 10, width: 100, height: 100 };
    const boxesA = [box];
    const boxesB = [box];
    boundingBox(boxesA);
    expect(boxesA).toEqual(boxesB);
  });
  it("returns iteself when called on a single box", () => {
    const box = { x: 10, y: 10, width: 100, height: 100 };
    expect(boundingBox([box])).toEqual(box);
  });
  it("returns the larger box when one fits entire within another", () => {
    const boxes = [
      { x: 10, y: 10, width: 100, height: 100 },
      { x: 20, y: 20, width: 80, height: 80 },
    ];
    expect(boundingBox(boxes)).toEqual(boxes[0]);
  });
  it("returns countaing box of two boxes", () => {
    const boxes = [
      { x: 10, y: 10, width: 100, height: 100 },
      { x: 120, y: 220, width: 100, height: 100 },
    ];
    expect(boundingBox(boxes)).toEqual({
      x: 10,
      y: 10,
      width: 210,
      height: 310,
    });
  });
  it("returns countaing box of three boxes", () => {
    const boxes = [
      { x: 10, y: 10, width: 100, height: 100 },
      { x: 120, y: 220, width: 100, height: 100 },
      { x: 3, y: 220, width: 100, height: 100 },
    ];
    expect(boundingBox(boxes)).toEqual({
      x: 3,
      y: 10,
      width: 217,
      height: 310,
    });
  });
});

describe("applySnap function", () => {
  it("does not apply undefined values", () => {
    const original = { x: 10, y: 10 };
    const snapped = { x: undefined, y: undefined };
    const actual = applySnap(original, snapped);
    const expected = original;
    expect(actual).toEqual(expected);
  });
  it("preserves other values on original", () => {
    const original = { x: 10, y: 10, foo: "bar" };
    const snapped = { x: 50, y: 50 };
    const actual = applySnap(original, snapped);
    const expected = { x: 50, y: 50, foo: "bar" };
    expect(actual).toEqual(expected);
  });
  it('will clobber original with falsy "0" values', () => {
    const original = { x: 10, y: 10 };
    const snapped = { x: 0, y: 0 };
    const actual = applySnap(original, snapped);
    expect(actual).toEqual(snapped);
  });
  it('will clobber original with falsy "0" values', () => {
    const original = { x: 10, y: 10 };
    const snapped = { x: 0, y: 0 };
    const actual = applySnap(original, snapped);
    expect(actual).toEqual(snapped);
  });
  it("can apply multiple snaps", () => {
    const original = { x: 10, y: 10 };
    const snapped1 = { x: 0 };
    const snapped2 = { y: 0 };
    const actual = applySnap(original, snapped1, snapped2);
    const expected = { x: 0, y: 0 };
    expect(actual).toEqual(expected);
  });
  it("the last snap wins", () => {
    const original = { x: 10, y: 10 };
    const snapped1 = { x: 50, y: 20 };
    const snapped2 = { x: 60, y: 60 };
    const actual = applySnap(original, snapped1, snapped2);
    const expected = { x: 60, y: 60 };
    expect(actual).toEqual(expected);
  });

  describe("applyMultipleDiffs", () => {
    it("uses the smallest non-zero value for each axis", () => {
      expect(
        applyMultipleDiffs({ x: 31, y: -8 }, { x: 0, y: 8 }, { x: 0, y: 0 })
      ).toEqual({ x: 31, y: 0 });
    });
  });
});
