import {
  stateReducer,
  SelectionMode,
  EmptySelectionAction,
  HoverState,
} from './HudCanvasState';

const initialState = {
  width: 5000,
  emptySelectionAction: EmptySelectionAction.SelectNothing,
  hoverX: 0,
  selection: { start: 0, end: 0 },
  origSelection: { start: 0, end: 0 },
  mousedownX: 0,
  mode: SelectionMode.Normal,
  prevMode: SelectionMode.Normal,
  cursorClass: 'cursor-auto',
  hoverState: HoverState.Normal,
  shouldPublish: false,
};

describe('stateReducer', () => {
  describe('setselection', () => {
    it('sets the selection', () => {
      const state = stateReducer(
        { ...initialState },
        { type: 'setselection', x: 0, selection: { start: 100, end: 200 } }
      );
      expect(state.selection).toEqual({ start: 100, end: 200 });
    });
  });

  describe('mousedown', () => {
    describe('when hovering over the selection start', () => {
      it('updates the state', () => {
        const state = stateReducer(
          { ...initialState, selection: { start: 1000, end: 2000 } },
          { type: 'mousedown', x: 995 }
        );
        expect(state.mode).toEqual(SelectionMode.ResizingStart);
        expect(state.selection).toEqual({ start: 1000, end: 2000 });
        expect(state.mousedownX).toEqual(995);
      });
    });
    describe('when hovering over the selection end', () => {
      it('updates the state', () => {
        const state = stateReducer(
          { ...initialState, selection: { start: 1000, end: 2000 } },
          { type: 'mousedown', x: 2003 }
        );
        expect(state.mode).toEqual(SelectionMode.ResizingEnd);
        expect(state.selection).toEqual({ start: 1000, end: 2000 });
        expect(state.mousedownX).toEqual(2003);
      });
    });
    describe('when hovering over the selection', () => {
      it('updates the state', () => {
        const state = stateReducer(
          { ...initialState, selection: { start: 1000, end: 2000 } },
          { type: 'mousedown', x: 1500 }
        );
        expect(state.mode).toEqual(SelectionMode.Dragging);
        expect(state.selection).toEqual({ start: 1000, end: 2000 });
        expect(state.mousedownX).toEqual(1500);
      });
    });
    describe('when not hovering over the selection', () => {
      it('updates the state', () => {
        const state = stateReducer(
          { ...initialState, selection: { start: 1000, end: 2000 } },
          { type: 'mousedown', x: 3000 }
        );
        expect(state.mode).toEqual(SelectionMode.Selecting);
        expect(state.selection).toEqual({ start: 3000, end: 3000 });
        expect(state.mousedownX).toEqual(3000);
      });
    });
  });

  describe('mouseup', () => {
    describe('when re-entering normal mode', () => {
      it('updates the state', () => {
        const state = stateReducer(
          {
            ...initialState,
            selection: { start: 1000, end: 2000 },
            mode: SelectionMode.Selecting,
            mousedownX: 1200,
          },
          { type: 'mouseup', x: 0 }
        );
        expect(state.mode).toEqual(SelectionMode.Normal);
        expect(state.selection).toEqual({ start: 1000, end: 2000 });
        expect(state.mousedownX).toEqual(1200);
      });
    });

    describe('nothing is selected and emptySelectionAction is SelectNothing', () => {
      it('clears the selection at the mouse x coord', () => {
        const state = stateReducer(
          {
            ...initialState,
            selection: { start: 1000, end: 1000 },
            mode: SelectionMode.Selecting,
            emptySelectionAction: EmptySelectionAction.SelectNothing,
          },
          { type: 'mouseup', x: 500 }
        );
        expect(state.mode).toEqual(SelectionMode.Normal);
        expect(state.selection).toEqual({ start: 500, end: 500 });
      });
    });

    describe('nothing is selected and emptySelectionAction is SelectPrevious', () => {
      it('reverts to the previous selection', () => {
        const state = stateReducer(
          {
            ...initialState,
            selection: { start: 1000, end: 2000 },
            mode: SelectionMode.Selecting,
            emptySelectionAction: EmptySelectionAction.SelectNothing,
          },
          { type: 'mouseup', x: 0 }
        );
        expect(state.mode).toEqual(SelectionMode.Normal);
        expect(state.selection).toEqual({ start: 1000, end: 2000 });
      });
    });
  });

  describe('mouseup', () => {
    it('sets the state', () => {
      const state = stateReducer(
        {
          ...initialState,
          selection: { start: 2000, end: 3000 },
          mode: SelectionMode.Dragging,
          mousedownX: 475,
        },
        { type: 'mouseleave', x: 500 }
      );
      expect(state.mode).toEqual(SelectionMode.Dragging);
      expect(state.selection).toEqual({ start: 2000, end: 3000 });
      expect(state.mousedownX).toEqual(475);
    });
  });

  describe('mousemove', () => {
    describe('in normal mode', () => {
      describe('when hovering over the selection start', () => {
        it('updates the state', () => {
          const state = stateReducer(
            {
              ...initialState,
              selection: { start: 1000, end: 3000 },
              mode: SelectionMode.Normal,
            },
            { type: 'mousemove', x: 997 }
          );
          expect(state.mode).toEqual(SelectionMode.Normal);
          expect(state.selection).toEqual({ start: 1000, end: 3000 });
          expect(state.hoverState).toEqual(HoverState.OverSelectionStart);
        });
      });

      describe('when hovering over the selection end', () => {
        it('updates the state', () => {
          const state = stateReducer(
            {
              ...initialState,
              selection: { start: 1000, end: 3000 },
              mode: SelectionMode.Normal,
            },
            { type: 'mousemove', x: 3009 }
          );
          expect(state.mode).toEqual(SelectionMode.Normal);
          expect(state.selection).toEqual({ start: 1000, end: 3000 });
          expect(state.hoverState).toEqual(HoverState.OverSelectionEnd);
        });
      });

      describe('when hovering over the selection', () => {
        it('updates the state', () => {
          const state = stateReducer(
            {
              ...initialState,
              selection: { start: 1000, end: 3000 },
              mode: SelectionMode.Normal,
            },
            { type: 'mousemove', x: 1200 }
          );
          expect(state.mode).toEqual(SelectionMode.Normal);
          expect(state.selection).toEqual({ start: 1000, end: 3000 });
          expect(state.hoverState).toEqual(HoverState.OverSelection);
        });
      });

      describe('when hovering elsewhere', () => {
        it('updates the state', () => {
          const state = stateReducer(
            {
              ...initialState,
              selection: { start: 1000, end: 3000 },
              mode: SelectionMode.Normal,
            },
            { type: 'mousemove', x: 10 }
          );
          expect(state.mode).toEqual(SelectionMode.Normal);
          expect(state.selection).toEqual({ start: 1000, end: 3000 });
          expect(state.hoverState).toEqual(HoverState.Normal);
        });
      });
    });

    describe('in selecting mode', () => {
      describe('a normal selection', () => {
        it('updates the state', () => {
          const state = stateReducer(
            {
              ...initialState,
              selection: { start: 2000, end: 3000 },
              mode: SelectionMode.Selecting,
              mousedownX: 2000,
            },
            { type: 'mousemove', x: 3005 }
          );
          expect(state.mode).toEqual(SelectionMode.Selecting);
          expect(state.selection).toEqual({ start: 2000, end: 3005 });
        });
      });

      describe('when crossing over', () => {
        it('updates the state', () => {
          const state = stateReducer(
            {
              ...initialState,
              selection: { start: 2000, end: 2002 },
              mode: SelectionMode.Selecting,
              mousedownX: 2000,
            },
            { type: 'mousemove', x: 1995 }
          );
          expect(state.mode).toEqual(SelectionMode.Selecting);
          expect(state.selection).toEqual({ start: 1995, end: 2000 });
        });
      });
    });

    describe('in dragging mode', () => {
      describe('in the middle of the canvas', () => {
        it('updates the state', () => {
          const state = stateReducer(
            {
              ...initialState,
              selection: { start: 1000, end: 1500 },
              origSelection: { start: 1000, end: 1500 },
              mode: SelectionMode.Dragging,
              mousedownX: 1200,
            },
            { type: 'mousemove', x: 1220 }
          );
          expect(state.mode).toEqual(SelectionMode.Dragging);
          expect(state.selection).toEqual({ start: 1020, end: 1520 });
        });
      });

      describe('at the start of the canvas', () => {
        it('constrains the movement and updates the state', () => {
          const state = stateReducer(
            {
              ...initialState,
              selection: { start: 10, end: 210 },
              origSelection: { start: 10, end: 210 },
              mode: SelectionMode.Dragging,
              mousedownX: 50,
            },
            { type: 'mousemove', x: 30 }
          );
          expect(state.mode).toEqual(SelectionMode.Dragging);
          expect(state.selection).toEqual({ start: 0, end: 200 });
        });
      });

      describe('at the end of the canvas', () => {
        it('constrains the movement and updates the state', () => {
          const state = stateReducer(
            {
              ...initialState,
              width: 3000,
              selection: { start: 2800, end: 2900 },
              origSelection: { start: 2800, end: 2900 },
              mode: SelectionMode.Dragging,
              mousedownX: 1200,
            },
            { type: 'mousemove', x: 1350 }
          );
          expect(state.mode).toEqual(SelectionMode.Dragging);
          expect(state.selection).toEqual({ start: 2900, end: 3000 });
        });
      });
    });

    describe('in resizing start mode', () => {
      describe('a normal resize', () => {
        it('updates the state', () => {
          const state = stateReducer(
            {
              ...initialState,
              selection: { start: 2000, end: 3000 },
              origSelection: { start: 2000, end: 3000 },
              mode: SelectionMode.ResizingStart,
            },
            { type: 'mousemove', x: 2020 }
          );
          expect(state.mode).toEqual(SelectionMode.ResizingStart);
          expect(state.selection).toEqual({ start: 2020, end: 3000 });
        });
      });

      describe('when crossing over', () => {
        it('updates the state', () => {
          const state = stateReducer(
            {
              ...initialState,
              selection: { start: 2000, end: 2002 },
              origSelection: { start: 2000, end: 2002 },
              mode: SelectionMode.ResizingStart,
            },
            { type: 'mousemove', x: 2010 }
          );
          expect(state.mode).toEqual(SelectionMode.ResizingStart);
          expect(state.selection).toEqual({ start: 2002, end: 2010 });
        });
      });
    });

    describe('in resizing end mode', () => {
      describe('a normal resize', () => {
        it('updates the state', () => {
          const state = stateReducer(
            {
              ...initialState,
              selection: { start: 1000, end: 2000 },
              origSelection: { start: 1000, end: 2000 },
              mode: SelectionMode.ResizingEnd,
            },
            { type: 'mousemove', x: 2007 }
          );
          expect(state.mode).toEqual(SelectionMode.ResizingEnd);
          expect(state.selection).toEqual({ start: 1000, end: 2007 });
        });
      });

      describe('when crossing over', () => {
        it('updates the state', () => {
          const state = stateReducer(
            {
              ...initialState,
              selection: { start: 2000, end: 2002 },
              origSelection: { start: 2000, end: 2002 },
              mode: SelectionMode.ResizingEnd,
            },
            { type: 'mousemove', x: 1995 }
          );
          expect(state.mode).toEqual(SelectionMode.ResizingEnd);
          expect(state.selection).toEqual({ start: 1995, end: 2000 });
        });
      });
    });
  });
});
