/**
 * @license
 * Copyright 2020 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */

import * as Blockly from '../../build/src/core/blockly.js';
import {createKeyDownEvent} from './test_helpers/user_input.js';
import {defineStackBlock} from './test_helpers/block_definitions.js';
import {
  sharedTestSetup,
  sharedTestTeardown,
} from './test_helpers/setup_teardown.js';

suite('Key Down', function () {
  setup(function () {
    sharedTestSetup.call(this);
    this.workspace = Blockly.inject('blocklyDiv', {});
  });
  teardown(function () {
    sharedTestTeardown.call(this);
  });

  /**
   * Creates a block and sets it as Blockly.selected.
   * @param {Blockly.Workspace} workspace The workspace to create a new block on.
   * @return {Blockly.Block} The block being selected.
   */
  function setSelectedBlock(workspace) {
    defineStackBlock();
    const block = workspace.newBlock('stack_block');
    Blockly.common.setSelected(block);
    return block;
  }

  /**
   * Creates a test for not running keyDown events when the workspace is in read only mode.
   * @param {Object} keyEvent Mocked key down event. Use createKeyDownEvent.
   * @param {string=} opt_name An optional name for the test case.
   */
  function runReadOnlyTest(keyEvent, opt_name) {
    const name = opt_name ? opt_name : 'Not called when readOnly is true';
    test(name, function () {
      this.workspace.options.readOnly = true;
      document.dispatchEvent(keyEvent);
      sinon.assert.notCalled(this.hideChaffSpy);
    });
  }

  suite('Escape', function () {
    setup(function () {
      this.event = createKeyDownEvent(Blockly.utils.KeyCodes.ESC);
      this.hideChaffSpy = sinon.spy(
        Blockly.WorkspaceSvg.prototype,
        'hideChaff',
      );
    });
    test('Simple', function () {
      document.dispatchEvent(this.event);
      sinon.assert.calledOnce(this.hideChaffSpy);
    });
    runReadOnlyTest(createKeyDownEvent(Blockly.utils.KeyCodes.ESC));
    test('Not called when focus is on an HTML input', function () {
      const event = createKeyDownEvent(Blockly.utils.KeyCodes.ESC);
      const input = document.createElement('textarea');
      input.dispatchEvent(event);
      sinon.assert.notCalled(this.hideChaffSpy);
    });
    test('Not called on hidden workspaces', function () {
      this.workspace.isVisible_ = false;
      document.dispatchEvent(this.event);
      sinon.assert.notCalled(this.hideChaffSpy);
    });
  });

  suite('Delete Block', function () {
    setup(function () {
      this.hideChaffSpy = sinon.spy(
        Blockly.WorkspaceSvg.prototype,
        'hideChaff',
      );
      setSelectedBlock(this.workspace);
      this.deleteSpy = sinon.spy(Blockly.common.getSelected(), 'dispose');
    });
    const testCases = [
      ['Delete', createKeyDownEvent(Blockly.utils.KeyCodes.DELETE)],
      ['Backspace', createKeyDownEvent(Blockly.utils.KeyCodes.BACKSPACE)],
    ];
    // Delete a block.
    suite('Simple', function () {
      testCases.forEach(function (testCase) {
        const testCaseName = testCase[0];
        const keyEvent = testCase[1];
        test(testCaseName, function () {
          document.dispatchEvent(keyEvent);
          sinon.assert.calledOnce(this.hideChaffSpy);
          sinon.assert.calledOnce(this.deleteSpy);
        });
      });
    });
    // Do not delete a block if workspace is in readOnly mode.
    suite('Not called when readOnly is true', function () {
      testCases.forEach(function (testCase) {
        const testCaseName = testCase[0];
        const keyEvent = testCase[1];
        runReadOnlyTest(keyEvent, testCaseName);
      });
    });
  });

  suite('Copy', function () {
    setup(function () {
      this.block = setSelectedBlock(this.workspace);
      this.copySpy = sinon.spy(this.block, 'toCopyData');
      this.hideChaffSpy = sinon.spy(
        Blockly.WorkspaceSvg.prototype,
        'hideChaff',
      );
    });
    const testCases = [
      [
        'Control C',
        createKeyDownEvent(Blockly.utils.KeyCodes.C, [
          Blockly.utils.KeyCodes.CTRL,
        ]),
      ],
      [
        'Meta C',
        createKeyDownEvent(Blockly.utils.KeyCodes.C, [
          Blockly.utils.KeyCodes.META,
        ]),
      ],
      [
        'Alt C',
        createKeyDownEvent(Blockly.utils.KeyCodes.C, [
          Blockly.utils.KeyCodes.ALT,
        ]),
      ],
    ];
    // Copy a block.
    suite('Simple', function () {
      testCases.forEach(function (testCase) {
        const testCaseName = testCase[0];
        const keyEvent = testCase[1];
        test(testCaseName, function () {
          document.dispatchEvent(keyEvent);
          sinon.assert.calledOnce(this.copySpy);
          sinon.assert.calledOnce(this.hideChaffSpy);
        });
      });
    });
    // Do not copy a block if a workspace is in readonly mode.
    suite('Not called when readOnly is true', function () {
      testCases.forEach(function (testCase) {
        const testCaseName = testCase[0];
        const keyEvent = testCase[1];
        runReadOnlyTest(keyEvent, testCaseName);
      });
    });
    // Do not copy a block if a gesture is in progress.
    suite('Gesture in progress', function () {
      testCases.forEach(function (testCase) {
        const testCaseName = testCase[0];
        const keyEvent = testCase[1];
        test(testCaseName, function () {
          sinon.stub(Blockly.Gesture, 'inProgress').returns(true);
          document.dispatchEvent(keyEvent);
          sinon.assert.notCalled(this.copySpy);
          sinon.assert.notCalled(this.hideChaffSpy);
        });
      });
    });
    // Do not copy a block if is is not deletable.
    suite('Block is not deletable', function () {
      testCases.forEach(function (testCase) {
        const testCaseName = testCase[0];
        const keyEvent = testCase[1];
        test(testCaseName, function () {
          sinon
            .stub(Blockly.common.getSelected(), 'isDeletable')
            .returns(false);
          document.dispatchEvent(keyEvent);
          sinon.assert.notCalled(this.copySpy);
          sinon.assert.notCalled(this.hideChaffSpy);
        });
      });
    });
    // Do not copy a block if it is not movable.
    suite('Block is not movable', function () {
      testCases.forEach(function (testCase) {
        const testCaseName = testCase[0];
        const keyEvent = testCase[1];
        test(testCaseName, function () {
          sinon.stub(Blockly.common.getSelected(), 'isMovable').returns(false);
          document.dispatchEvent(keyEvent);
          sinon.assert.notCalled(this.copySpy);
          sinon.assert.notCalled(this.hideChaffSpy);
        });
      });
    });
  });

  suite('Undo', function () {
    setup(function () {
      this.undoSpy = sinon.spy(this.workspace, 'undo');
      this.hideChaffSpy = sinon.spy(
        Blockly.WorkspaceSvg.prototype,
        'hideChaff',
      );
    });
    const testCases = [
      [
        'Control Z',
        createKeyDownEvent(Blockly.utils.KeyCodes.Z, [
          Blockly.utils.KeyCodes.CTRL,
        ]),
      ],
      [
        'Meta Z',
        createKeyDownEvent(Blockly.utils.KeyCodes.Z, [
          Blockly.utils.KeyCodes.META,
        ]),
      ],
      [
        'Alt Z',
        createKeyDownEvent(Blockly.utils.KeyCodes.Z, [
          Blockly.utils.KeyCodes.ALT,
        ]),
      ],
    ];
    // Undo.
    suite('Simple', function () {
      testCases.forEach(function (testCase) {
        const testCaseName = testCase[0];
        const keyEvent = testCase[1];
        test(testCaseName, function () {
          document.dispatchEvent(keyEvent);
          sinon.assert.calledOnce(this.undoSpy);
          sinon.assert.calledWith(this.undoSpy, false);
          sinon.assert.calledOnce(this.hideChaffSpy);
        });
      });
    });
    // Do not undo if a gesture is in progress.
    suite('Gesture in progress', function () {
      testCases.forEach(function (testCase) {
        const testCaseName = testCase[0];
        const keyEvent = testCase[1];
        test(testCaseName, function () {
          sinon.stub(Blockly.Gesture, 'inProgress').returns(true);
          document.dispatchEvent(keyEvent);
          sinon.assert.notCalled(this.undoSpy);
          sinon.assert.notCalled(this.hideChaffSpy);
        });
      });
    });
    // Do not undo if the workspace is in readOnly mode.
    suite('Not called when readOnly is true', function () {
      testCases.forEach(function (testCase) {
        const testCaseName = testCase[0];
        const keyEvent = testCase[1];
        runReadOnlyTest(keyEvent, testCaseName);
      });
    });
  });

  suite('Redo', function () {
    setup(function () {
      this.redoSpy = sinon.spy(this.workspace, 'undo');
      this.hideChaffSpy = sinon.spy(
        Blockly.WorkspaceSvg.prototype,
        'hideChaff',
      );
    });
    const testCases = [
      [
        'Control Shift Z',
        createKeyDownEvent(Blockly.utils.KeyCodes.Z, [
          Blockly.utils.KeyCodes.CTRL,
          Blockly.utils.KeyCodes.SHIFT,
        ]),
      ],
      [
        'Meta Shift Z',
        createKeyDownEvent(Blockly.utils.KeyCodes.Z, [
          Blockly.utils.KeyCodes.META,
          Blockly.utils.KeyCodes.SHIFT,
        ]),
      ],
      [
        'Alt Shift Z',
        createKeyDownEvent(Blockly.utils.KeyCodes.Z, [
          Blockly.utils.KeyCodes.ALT,
          Blockly.utils.KeyCodes.SHIFT,
        ]),
      ],
    ];
    // Undo.
    suite('Simple', function () {
      testCases.forEach(function (testCase) {
        const testCaseName = testCase[0];
        const keyEvent = testCase[1];
        test(testCaseName, function () {
          document.dispatchEvent(keyEvent);
          sinon.assert.calledOnce(this.redoSpy);
          sinon.assert.calledWith(this.redoSpy, true);
          sinon.assert.calledOnce(this.hideChaffSpy);
        });
      });
    });
    // Do not undo if a gesture is in progress.
    suite('Gesture in progress', function () {
      testCases.forEach(function (testCase) {
        const testCaseName = testCase[0];
        const keyEvent = testCase[1];
        test(testCaseName, function () {
          sinon.stub(Blockly.Gesture, 'inProgress').returns(true);
          document.dispatchEvent(keyEvent);
          sinon.assert.notCalled(this.redoSpy);
          sinon.assert.notCalled(this.hideChaffSpy);
        });
      });
    });
    // Do not undo if the workspace is in readOnly mode.
    suite('Not called when readOnly is true', function () {
      testCases.forEach(function (testCase) {
        const testCaseName = testCase[0];
        const keyEvent = testCase[1];
        runReadOnlyTest(keyEvent, testCaseName);
      });
    });
  });

  suite('UndoWindows', function () {
    setup(function () {
      this.ctrlYEvent = createKeyDownEvent(Blockly.utils.KeyCodes.Y, [
        Blockly.utils.KeyCodes.CTRL,
      ]);
      this.undoSpy = sinon.spy(this.workspace, 'undo');
      this.hideChaffSpy = sinon.spy(
        Blockly.WorkspaceSvg.prototype,
        'hideChaff',
      );
    });
    test('Simple', function () {
      document.dispatchEvent(this.ctrlYEvent);
      sinon.assert.calledOnce(this.undoSpy);
      sinon.assert.calledWith(this.undoSpy, true);
      sinon.assert.calledOnce(this.hideChaffSpy);
    });
    test('Not called when a gesture is in progress', function () {
      sinon.stub(Blockly.Gesture, 'inProgress').returns(true);
      document.dispatchEvent(this.ctrlYEvent);
      sinon.assert.notCalled(this.undoSpy);
      sinon.assert.notCalled(this.hideChaffSpy);
    });
    runReadOnlyTest(
      createKeyDownEvent(Blockly.utils.KeyCodes.Y, [
        Blockly.utils.KeyCodes.CTRL,
      ]),
    );
  });
});
