//////////////////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2021, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////////////////

import * as keyboardShortcuts from 'sources/keyboard_shortcuts';
import {queryToolActions} from 'sources/sqleditor/query_tool_actions';
import gettext from 'sources/gettext';

describe('the keyboard shortcuts', () => {
  const F1_KEY = 112,
    F5_KEY = 116,
    F6_KEY = 117,
    F7_KEY = 118,
    F8_KEY = 119,
    PERIOD_KEY = 190,
    FWD_SLASH_KEY = 191,
    C1_KEY = 49;

  let sqlEditorControllerSpy, event, queryToolActionsSpy;
  beforeEach(() => {
    event = {
      shift: false,
      which: undefined,
      preventDefault: jasmine.createSpy('preventDefault'),
      cancelBubble: false,
      stopPropagation: jasmine.createSpy('stopPropagation'),
      stopImmediatePropagation: jasmine.createSpy('stopImmediatePropagation'),
    };

    let gridView = {
      query_tool_obj: {
        getSelection: jasmine.createSpy('getSelection'),
        getValue: jasmine.createSpy('getValue'),
      },
    };

    sqlEditorControllerSpy = jasmine.createSpyObj('SqlEditorController', [
      'isQueryRunning',
      'execute',
    ]);

    sqlEditorControllerSpy.gridView = gridView;

    sqlEditorControllerSpy.preferences = {
      execute_query: {
        alt: false,
        shift: false,
        control: false,
        key: {
          key_code: F5_KEY,
        },
      },
      explain_query: {
        alt: false,
        shift: false,
        control: false,
        key: {
          key_code: F7_KEY,
        },
      },
      explain_analyze_query: {
        alt: false,
        shift: true,
        control: false,
        key: {
          key_code: F7_KEY,
        },
      },
      download_results: {
        alt: false,
        shift: false,
        control: false,
        key: {
          key_code: F8_KEY,
        },
      },
      move_next: {
        alt: false,
        shift: false,
        control: false,
        key: {
          key_code: null,
        },
      },
      move_previous: {
        alt: false,
        shift: false,
        control: false,
        key: {
          key_code: null,
        },
      },
      commit_transaction: {
        alt: false,
        shift: true,
        control: true,
        key: {
          key_code: 'm',
        },
      },
      rollback_transaction: {
        alt: false,
        shift: true,
        control: true,
        key: {
          key_code: 'r',
        },
      },
      save_data: {
        alt : false,
        shift: false,
        control: false,
        key: {
          key_code: F6_KEY,
        },
      },
    };

    sqlEditorControllerSpy.macros = [
      {
        alt: false,
        control: true,
        id: 1,
        key: '1',
        key_code: C1_KEY,
        key_label: 'Ctrl + 1',
        name: 'C1',
        sql: 'Select 1;',
      },
    ];

    queryToolActionsSpy = jasmine.createSpyObj(queryToolActions, [
      'explainAnalyze',
      'explain',
      'download',
      'commentBlockCode',
      'commentLineCode',
      'uncommentLineCode',
      'executeQuery',
      'executeCommit',
      'executeRollback',
      'saveDataChanges',
      'executeMacro',
    ]);
  });

  describe('when the key is not handled by the function', function () {

    beforeEach(() => {
      event.which = F1_KEY;
      keyboardShortcuts.processEventQueryTool(
        sqlEditorControllerSpy, queryToolActionsSpy, event
      );
    });

    it('should allow event to propagate', () => {
      expect(event.preventDefault).not.toHaveBeenCalled();
    });
  });

  describe('F5', () => {
    describe('when there is no query already running', () => {
      beforeEach(() => {
        event.keyCode = F5_KEY;
        event.altKey = false;
        event.shiftKey = false;
        event.ctrlKey = false;
        keyboardShortcuts.processEventQueryTool(
          sqlEditorControllerSpy, queryToolActionsSpy, event
        );
      });

      it('should execute the query', () => {
        expect(queryToolActionsSpy.executeQuery).toHaveBeenCalledWith(sqlEditorControllerSpy);
      });

      it('should stop event propagation', () => {
        expect(event.preventDefault).toHaveBeenCalled();
      });
    });

    describe('when the query is already running', () => {
      it('does nothing', () => {
        event.keyCode = F5_KEY;
        event.altKey = false;
        event.shiftKey = false;
        event.ctrlKey = false;
        sqlEditorControllerSpy.isQueryRunning.and.returnValue(true);

        keyboardShortcuts.processEventQueryTool(
          sqlEditorControllerSpy, queryToolActionsSpy, event
        );

        expect(queryToolActionsSpy.executeQuery).not.toHaveBeenCalled();
      });
    });
  });

  describe('F6', () => {
    describe('when there is not a query already running', () => {
      beforeEach(() => {
        event.which = F6_KEY;
        event.altKey = false;
        event.shiftKey = false;
        event.ctrlKey = false;
        keyboardShortcuts.processEventQueryTool(
          sqlEditorControllerSpy, queryToolActionsSpy, event
        );
      });

      it('should save the changed data', () => {
        expect(queryToolActionsSpy.saveDataChanges).toHaveBeenCalledWith(sqlEditorControllerSpy);
      });

      expectEventPropagationToStop();
    });

    describe('when the query is already running', () => {
      it('does nothing', () => {
        event.keyCode = F6_KEY;
        event.altKey = false;
        event.shiftKey = false;
        event.ctrlKey = false;
        sqlEditorControllerSpy.isQueryRunning.and.returnValue(true);

        keyboardShortcuts.processEventQueryTool(
          sqlEditorControllerSpy, queryToolActionsSpy, event
        );

        expect(queryToolActionsSpy.saveDataChanges).not.toHaveBeenCalled();
      });
    });
  });

  describe('F7', () => {
    describe('when there is not a query already running', () => {
      beforeEach(() => {
        event.which = F7_KEY;
        event.altKey = false;
        event.shiftKey = false;
        event.ctrlKey = false;
        keyboardShortcuts.processEventQueryTool(
          sqlEditorControllerSpy, queryToolActionsSpy, event
        );
      });

      it('should explain the query plan', () => {
        expect(queryToolActionsSpy.explain).toHaveBeenCalledWith(sqlEditorControllerSpy);
      });

      expectEventPropagationToStop();
    });

    describe('when the query is already running', () => {
      it('does nothing', () => {
        event.keyCode = F7_KEY;
        event.altKey = false;
        event.shiftKey = false;
        event.ctrlKey = false;
        sqlEditorControllerSpy.isQueryRunning.and.returnValue(true);

        keyboardShortcuts.processEventQueryTool(
          sqlEditorControllerSpy, queryToolActionsSpy, event
        );

        expect(queryToolActionsSpy.explain).not.toHaveBeenCalled();
      });
    });
  });

  describe('Shift+F7', () => {
    describe('when there is not a query already running', () => {
      beforeEach(() => {
        event.shiftKey = true;
        event.which = F7_KEY;
        event.altKey = false;
        event.ctrlKey = false;
        keyboardShortcuts.processEventQueryTool(
          sqlEditorControllerSpy, queryToolActionsSpy, event
        );
      });

      it('should analyze explain the query plan', () => {
        expect(queryToolActionsSpy.explainAnalyze).toHaveBeenCalledWith(sqlEditorControllerSpy);
      });

      expectEventPropagationToStop();
    });

    describe('when the query is already running', () => {
      it('does nothing', () => {
        event.shiftKey = true;
        event.which = F7_KEY;
        event.altKey = false;
        event.ctrlKey = false;

        sqlEditorControllerSpy.isQueryRunning.and.returnValue(true);

        keyboardShortcuts.processEventQueryTool(
          sqlEditorControllerSpy, queryToolActionsSpy, event
        );

        expect(queryToolActionsSpy.explainAnalyze).not.toHaveBeenCalled();
      });
    });
  });

  describe('F8', () => {
    describe('when there is not a query already running', () => {
      beforeEach(() => {
        event.which = F8_KEY;
        event.altKey = false;
        event.shiftKey = false;
        event.ctrlKey = false;

        keyboardShortcuts.processEventQueryTool(
          sqlEditorControllerSpy, queryToolActionsSpy, event
        );
      });

      it('should download the query results as a CSV', () => {
        expect(queryToolActionsSpy.download).toHaveBeenCalled();
      });

      it('should stop event propagation', () => {
        expect(event.preventDefault).toHaveBeenCalled();
      });
    });

    describe('when the query is already running', () => {
      it('does nothing', () => {
        event.keyCode = F8_KEY;
        event.altKey = false;
        event.shiftKey = false;
        event.ctrlKey = false;

        sqlEditorControllerSpy.isQueryRunning.and.returnValue(true);

        keyboardShortcuts.processEventQueryTool(
          sqlEditorControllerSpy, queryToolActionsSpy, event
        );

        expect(queryToolActionsSpy.download).not.toHaveBeenCalled();
      });
    });
  });

  describe('inlineComment', () => {
    describe('when there is not a query already running', () => {
      describe('and the system is a Mac', () => {
        beforeEach(() => {
          macKeysSetup();
          event.which = FWD_SLASH_KEY;
          keyboardShortcuts.processEventQueryTool(
            sqlEditorControllerSpy, queryToolActionsSpy, event
          );
        });

        it('should comment the line', () => {
          expect(queryToolActionsSpy.commentLineCode).toHaveBeenCalledWith(sqlEditorControllerSpy);
        });

        expectEventPropagationToStop();
      });

      describe('and the system is Windows', () => {
        beforeEach(() => {
          windowsKeysSetup();
          event.which = FWD_SLASH_KEY;
          keyboardShortcuts.processEventQueryTool(
            sqlEditorControllerSpy, queryToolActionsSpy, event
          );
        });

        it('should comment the line', () => {
          expect(queryToolActionsSpy.commentLineCode).toHaveBeenCalledWith(sqlEditorControllerSpy);
        });

        expectEventPropagationToStop();
      });
    });

    describe('when the query is already running', () => {
      beforeEach(() => {
        sqlEditorControllerSpy.isQueryRunning.and.returnValue(true);
      });
      describe('and the system is a Mac', () => {
        beforeEach(() => {
          macKeysSetup();
          event.which = FWD_SLASH_KEY;
        });

        it('does nothing', () => {
          keyboardShortcuts.processEventQueryTool(
            sqlEditorControllerSpy, queryToolActionsSpy, event
          );

          expect(queryToolActionsSpy.commentLineCode).not.toHaveBeenCalled();
        });
      });

      describe('and the system is a Windows', () => {
        beforeEach(() => {
          windowsKeysSetup();
          event.which = FWD_SLASH_KEY;
        });

        it('does nothing', () => {
          keyboardShortcuts.processEventQueryTool(
            sqlEditorControllerSpy, queryToolActionsSpy, event
          );

          expect(queryToolActionsSpy.commentLineCode).not.toHaveBeenCalled();
        });
      });
    });
  });

  describe('inlineUncomment', () => {
    describe('when there is not a query already running', () => {
      describe('and the system is a mac', () => {
        beforeEach(() => {
          macKeysSetup();
          event.which = PERIOD_KEY;
          keyboardShortcuts.processEventQueryTool(
            sqlEditorControllerSpy, queryToolActionsSpy, event
          );
        });

        it('should uncomment the line', () => {
          expect(queryToolActionsSpy.uncommentLineCode).toHaveBeenCalledWith(sqlEditorControllerSpy);
        });

        expectEventPropagationToStop();
      });
      describe('and the system is a windows', () => {
        beforeEach(() => {
          windowsKeysSetup();
          event.which = PERIOD_KEY;
          keyboardShortcuts.processEventQueryTool(
            sqlEditorControllerSpy, queryToolActionsSpy, event
          );
        });

        it('should uncomment the line', () => {
          expect(queryToolActionsSpy.uncommentLineCode).toHaveBeenCalledWith(sqlEditorControllerSpy);
        });

        expectEventPropagationToStop();
      });
    });

    describe('when the query is already running', () => {
      beforeEach(() => {
        sqlEditorControllerSpy.isQueryRunning.and.returnValue(true);
      });
      describe('and the system is a Mac', () => {
        beforeEach(() => {
          macKeysSetup();
          event.which = PERIOD_KEY;
        });

        it('does nothing', () => {
          keyboardShortcuts.processEventQueryTool(
            sqlEditorControllerSpy, queryToolActionsSpy, event
          );
          expect(queryToolActionsSpy.uncommentLineCode).not.toHaveBeenCalled();
        });
      });
      describe('and the system is a Windows', () => {
        beforeEach(() => {
          windowsKeysSetup();
          event.which = PERIOD_KEY;
        });

        it('does nothing', () => {
          keyboardShortcuts.processEventQueryTool(
            sqlEditorControllerSpy, queryToolActionsSpy, event
          );

          expect(queryToolActionsSpy.uncommentLineCode).not.toHaveBeenCalled();
        });
      });
    });
  });

  describe('blockComment', () => {
    describe('when there is not a query already running', () => {
      describe('and the system is a Mac', () => {
        beforeEach(() => {
          macKeysSetup();
          event.which = FWD_SLASH_KEY;
          event.shiftKey = true;
          keyboardShortcuts.processEventQueryTool(
            sqlEditorControllerSpy, queryToolActionsSpy, event
          );
        });

        it('should comment out the block selection', () => {
          expect(queryToolActionsSpy.commentBlockCode).toHaveBeenCalledWith(sqlEditorControllerSpy);
        });

        expectEventPropagationToStop();
      });
    });

    describe('when there is not a query already running', () => {
      describe('and the system is a Windows', () => {
        beforeEach(() => {
          windowsKeysSetup();
          event.which = FWD_SLASH_KEY;
          event.shiftKey = true;
          keyboardShortcuts.processEventQueryTool(
            sqlEditorControllerSpy, queryToolActionsSpy, event
          );
        });

        it('should comment out the block selection', () => {
          expect(queryToolActionsSpy.commentBlockCode).toHaveBeenCalledWith(sqlEditorControllerSpy);
        });

        expectEventPropagationToStop();
      });
    });

    describe('when there is a query already running', () => {
      beforeEach(() => {
        sqlEditorControllerSpy.isQueryRunning.and.returnValue(true);
      });
      describe('and the system is a Mac', () => {
        beforeEach(() => {
          macKeysSetup();
          event.which = FWD_SLASH_KEY;
          event.shiftKey = true;
          keyboardShortcuts.processEventQueryTool(
            sqlEditorControllerSpy, queryToolActionsSpy, event
          );
        });
        it('does nothing', () => {
          expect(queryToolActionsSpy.commentBlockCode).not.toHaveBeenCalled();
        });
      });
      describe('and the system is a Windows', () => {
        beforeEach(() => {
          windowsKeysSetup();
          event.which = FWD_SLASH_KEY;
          event.shiftKey = true;
          keyboardShortcuts.processEventQueryTool(
            sqlEditorControllerSpy, queryToolActionsSpy, event
          );
        });
        it('does nothing', () => {
          expect(queryToolActionsSpy.commentBlockCode).not.toHaveBeenCalled();
        });
      });
    });
  });

  describe('shortcut to text converters', ()=> {
    var shortcut = {
      alt: false,
      shift: false,
      control: false,
      key: {
        char: 'a',
        key_code: 65,
      },
    };

    it('shortcut_key',()=>{
      expect(keyboardShortcuts.shortcut_key(shortcut)).toEqual('A');
    });

    it('shortcut_accesskey_title',()=>{
      expect(keyboardShortcuts.shortcut_accesskey_title(
        'Title', shortcut)).toEqual(gettext('Title (accesskey + A)'));
    });

    it('shortcut_title',()=>{
      shortcut.alt = true;
      shortcut.shift = false;
      shortcut.control = false;
      expect(keyboardShortcuts.shortcut_title(
        'Title', shortcut)).toEqual(gettext('Title (Alt+A)'));

      shortcut.alt = false;
      shortcut.shift = true;
      shortcut.control = false;
      expect(keyboardShortcuts.shortcut_title(
        'Title', shortcut)).toEqual(gettext('Title (Shift+A)'));

      shortcut.alt = false;
      shortcut.shift = false;
      shortcut.control = true;
      expect(keyboardShortcuts.shortcut_title(
        'Title', shortcut)).toEqual(gettext('Title (Ctrl+A)'));

      shortcut.alt = true;
      shortcut.shift = true;
      shortcut.control = true;
      expect(keyboardShortcuts.shortcut_title(
        'Title', shortcut)).toEqual(gettext('Title (Alt+Shift+Ctrl+A)'));
    });
  });

  describe('Shift+Ctrl+C', () => {
    describe('when there is not a query already running', () => {
      beforeEach(() => {
        event.shiftKey = true;
        event.which = 'm';
        event.altKey = false;
        event.ctrlKey = true;
        keyboardShortcuts.processEventQueryTool(
          sqlEditorControllerSpy, queryToolActionsSpy, event
        );
      });

      it('should commit the transaction', () => {
        expect(queryToolActionsSpy.executeCommit).toHaveBeenCalledWith(sqlEditorControllerSpy);
      });

      expectEventPropagationToStop();
    });

    describe('when the query is already running', () => {
      it('does nothing', () => {
        event.shiftKey = true;
        event.which = 'm';
        event.altKey = false;
        event.ctrlKey = true;

        sqlEditorControllerSpy.isQueryRunning.and.returnValue(true);

        keyboardShortcuts.processEventQueryTool(
          sqlEditorControllerSpy, queryToolActionsSpy, event
        );

        expect(queryToolActionsSpy.executeCommit).not.toHaveBeenCalled();
      });
    });
  });

  describe('Shift+Ctrl+R', () => {
    describe('when there is not a query already running', () => {
      beforeEach(() => {
        event.shiftKey = true;
        event.which = 'r';
        event.altKey = false;
        event.ctrlKey = true;
        keyboardShortcuts.processEventQueryTool(
          sqlEditorControllerSpy, queryToolActionsSpy, event
        );
      });

      it('should rollback the transaction', () => {
        expect(queryToolActionsSpy.executeRollback).toHaveBeenCalledWith(sqlEditorControllerSpy);
      });

      expectEventPropagationToStop();
    });

    describe('when the query is already running', () => {
      it('does nothing', () => {
        event.shiftKey = true;
        event.which = 'r';
        event.altKey = false;
        event.ctrlKey = true;

        sqlEditorControllerSpy.isQueryRunning.and.returnValue(true);

        keyboardShortcuts.processEventQueryTool(
          sqlEditorControllerSpy, queryToolActionsSpy, event
        );

        expect(queryToolActionsSpy.executeRollback).not.toHaveBeenCalled();
      });
    });
  });

  describe('Macro Ctrl + 1', () => {
    describe('when there is not a query already running', () => {
      beforeEach(() => {
        event.which = C1_KEY;
        event.altKey = false;
        event.shiftKey = false;
        event.ctrlKey = true;

        keyboardShortcuts.processEventQueryTool(
          sqlEditorControllerSpy, queryToolActionsSpy, event
        );
      });

      it('should execute the macro', () => {
        expect(queryToolActionsSpy.executeMacro).toHaveBeenCalledWith(sqlEditorControllerSpy,
          sqlEditorControllerSpy.macros[0].id);
      });

      it('should stop event propagation', () => {
        expect(event.preventDefault).toHaveBeenCalled();
      });
    });

    describe('when the query is already running', () => {
      it('does nothing', () => {
        event.keyCode = C1_KEY;
        event.altKey = false;
        event.shiftKey = false;
        event.ctrlKey = true;

        sqlEditorControllerSpy.isQueryRunning.and.returnValue(true);

        keyboardShortcuts.processEventQueryTool(
          sqlEditorControllerSpy, queryToolActionsSpy, event
        );

        expect(queryToolActionsSpy.executeMacro).not.toHaveBeenCalled();
      });
    });
  });

  function expectEventPropagationToStop() {
    describe('stops all event propogation', () => {

      it('should cancel the bubble', () => {
        expect(event.cancelBubble).toEqual(true);
      });

      it('should prevent the default behavior', () => {
        expect(event.preventDefault).toHaveBeenCalled();
      });

      it('should stop event propagation', () => {
        expect(event.stopPropagation).toHaveBeenCalled();
        expect(event.stopImmediatePropagation).toHaveBeenCalled();
      });
    });
  }

  function windowsKeysSetup() {
    spyOn(keyboardShortcuts, 'isMac').and.returnValue(false);
    event.ctrlKey = true;
    event.metaKey = false;
  }

  function macKeysSetup() {
    spyOn(keyboardShortcuts, 'isMac').and.returnValue(true);
    event.ctrlKey = false;
    event.metaKey = true;
  }
});
