/**
 * @license
 * Copyright (C) 2015 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import '../../../test/common-test-setup-karma.js';
import './gr-change-list.js';
import {afterNextRender} from '@polymer/polymer/lib/utils/render-status.js';
import {GerritNav} from '../../core/gr-navigation/gr-navigation.js';
import {TestKeyboardShortcutBinder} from '../../../test/test-utils.js';
import {Shortcut} from '../../../mixins/keyboard-shortcut-mixin/keyboard-shortcut-mixin.js';
import {YOUR_TURN} from '../../core/gr-navigation/gr-navigation.js';

const basicFixture = fixtureFromElement('gr-change-list');

suite('gr-change-list basic tests', () => {
  let element;

  suiteSetup(() => {
    const kb = TestKeyboardShortcutBinder.push();
    kb.bindShortcut(Shortcut.CURSOR_NEXT_CHANGE, 'j');
    kb.bindShortcut(Shortcut.CURSOR_PREV_CHANGE, 'k');
    kb.bindShortcut(Shortcut.OPEN_CHANGE, 'o');
    kb.bindShortcut(Shortcut.REFRESH_CHANGE_LIST, 'shift+r');
    kb.bindShortcut(Shortcut.TOGGLE_CHANGE_REVIEWED, 'r');
    kb.bindShortcut(Shortcut.TOGGLE_CHANGE_STAR, 's');
    kb.bindShortcut(Shortcut.NEXT_PAGE, 'n');
    kb.bindShortcut(Shortcut.NEXT_PAGE, 'p');
  });

  suiteTeardown(() => {
    TestKeyboardShortcutBinder.pop();
  });

  setup(() => {
    element = basicFixture.instantiate();
  });

  suite('test show change number not logged in', () => {
    setup(() => {
      element = basicFixture.instantiate();
      element.account = undefined;
      element.preferences = undefined;
      element._config = {};
    });

    test('show number disabled', () => {
      assert.isFalse(element.showNumber);
    });
  });

  suite('test show change number preference enabled', () => {
    setup(() => {
      element = basicFixture.instantiate();
      element.preferences = {
        legacycid_in_change_table: true,
        time_format: 'HHMM_12',
        change_table: [],
      };
      element.account = {_account_id: 1001};
      element._config = {};
      flush();
    });

    test('show number enabled', () => {
      assert.isTrue(element.showNumber);
    });
  });

  suite('test show change number preference disabled', () => {
    setup(() => {
      element = basicFixture.instantiate();
      // legacycid_in_change_table is not set when false.
      element.preferences = {
        time_format: 'HHMM_12',
        change_table: [],
      };
      element.account = {_account_id: 1001};
      element._config = {};
      flush();
    });

    test('show number disabled', () => {
      assert.isFalse(element.showNumber);
    });
  });

  test('computed fields', () => {
    assert.equal(element._computeLabelNames(
        [{results: [{_number: 0, labels: {}}]}]).length, 0);
    assert.equal(element._computeLabelNames([
      {results: [
        {_number: 0, labels: {Verified: {approved: {}}}},
        {
          _number: 1,
          labels: {
            'Verified': {approved: {}},
            'Code-Review': {approved: {}},
          },
        },
        {
          _number: 2,
          labels: {
            'Verified': {approved: {}},
            'Library-Compliance': {approved: {}},
          },
        },
      ]},
    ]).length, 3);

    assert.equal(element._computeLabelShortcut('Code-Review'), 'CR');
    assert.equal(element._computeLabelShortcut('Verified'), 'V');
    assert.equal(element._computeLabelShortcut('Library-Compliance'), 'LC');
    assert.equal(element._computeLabelShortcut('PolyGerrit-Review'), 'PR');
    assert.equal(element._computeLabelShortcut('polygerrit-review'), 'PR');
    assert.equal(element._computeLabelShortcut(
        'Invalid-Prolog-Rules-Label-Name--Verified'), 'V');
    assert.equal(element._computeLabelShortcut(
        'Some-Special-Label-7'), 'SSL7');
    assert.equal(element._computeLabelShortcut('--Too----many----dashes---'),
        'TMD');
    assert.equal(element._computeLabelShortcut(
        'Really-rather-entirely-too-long-of-a-label-name'), 'RRETL');
  });

  test('colspans', () => {
    element.sections = [
      {results: [{}]},
    ];
    flush();
    const tdItemCount = element.root.querySelectorAll(
        'td').length;

    const changeTableColumns = [];
    const labelNames = [];
    assert.equal(tdItemCount, element._computeColspan(
        {}, changeTableColumns, labelNames));
  });

  test('keyboard shortcuts', done => {
    sinon.stub(element, '_computeLabelNames');
    element.sections = [
      {results: new Array(1)},
      {results: new Array(2)},
    ];
    element.selectedIndex = 0;
    element.changes = [
      {_number: 0},
      {_number: 1},
      {_number: 2},
    ];
    flush();
    afterNextRender(element, () => {
      const elementItems = element.root.querySelectorAll(
          'gr-change-list-item');
      assert.equal(elementItems.length, 3);

      assert.isTrue(elementItems[0].hasAttribute('selected'));
      MockInteractions.pressAndReleaseKeyOn(element, 74, null, 'j');
      assert.equal(element.selectedIndex, 1);
      assert.isTrue(elementItems[1].hasAttribute('selected'));
      MockInteractions.pressAndReleaseKeyOn(element, 74, null, 'j');
      assert.equal(element.selectedIndex, 2);
      assert.isTrue(elementItems[2].hasAttribute('selected'));

      const navStub = sinon.stub(GerritNav, 'navigateToChange');
      assert.equal(element.selectedIndex, 2);
      MockInteractions.pressAndReleaseKeyOn(element, 13, null, 'enter');
      assert.deepEqual(navStub.lastCall.args[0], {_number: 2},
          'Should navigate to /c/2/');

      MockInteractions.pressAndReleaseKeyOn(element, 75, null, 'k');
      assert.equal(element.selectedIndex, 1);
      MockInteractions.pressAndReleaseKeyOn(element, 13, null, 'enter');
      assert.deepEqual(navStub.lastCall.args[0], {_number: 1},
          'Should navigate to /c/1/');

      MockInteractions.pressAndReleaseKeyOn(element, 75, null, 'k');
      MockInteractions.pressAndReleaseKeyOn(element, 75, null, 'k');
      MockInteractions.pressAndReleaseKeyOn(element, 75, null, 'k');
      assert.equal(element.selectedIndex, 0);

      const reloadStub = sinon.stub(element, '_reloadWindow');
      MockInteractions.pressAndReleaseKeyOn(element, 82, 'shift', 'r');
      assert.isTrue(reloadStub.called);

      done();
    });
  });

  test('changes needing review', () => {
    element.changes = [
      {
        _number: 0,
        status: 'NEW',
        reviewed: true,
        owner: {_account_id: 0},
      },
      {
        _number: 1,
        status: 'NEW',
        owner: {_account_id: 0},
      },
      {
        _number: 2,
        status: 'MERGED',
        owner: {_account_id: 0},
      },
      {
        _number: 3,
        status: 'ABANDONED',
        owner: {_account_id: 0},
      },
      {
        _number: 4,
        status: 'NEW',
        work_in_progress: true,
        owner: {_account_id: 0},
      },
    ];
    flush();
    let elementItems = element.root.querySelectorAll(
        'gr-change-list-item');
    assert.equal(elementItems.length, 5);
    for (let i = 0; i < elementItems.length; i++) {
      assert.isFalse(elementItems[i].hasAttribute('needs-review'));
    }

    element.showReviewedState = true;
    elementItems = element.root.querySelectorAll(
        'gr-change-list-item');
    assert.equal(elementItems.length, 5);
    assert.isFalse(elementItems[0].hasAttribute('needs-review'));
    assert.isTrue(elementItems[1].hasAttribute('needs-review'));
    assert.isFalse(elementItems[2].hasAttribute('needs-review'));
    assert.isFalse(elementItems[3].hasAttribute('needs-review'));
    assert.isFalse(elementItems[4].hasAttribute('needs-review'));

    element.account = {_account_id: 42};
    elementItems = element.root.querySelectorAll(
        'gr-change-list-item');
    assert.equal(elementItems.length, 5);
    assert.isFalse(elementItems[0].hasAttribute('needs-review'));
    assert.isTrue(elementItems[1].hasAttribute('needs-review'));
    assert.isFalse(elementItems[2].hasAttribute('needs-review'));
    assert.isFalse(elementItems[3].hasAttribute('needs-review'));
    assert.isFalse(elementItems[4].hasAttribute('needs-review'));

    element._config = {
      change: {enable_attention_set: true},
    };
    elementItems = element.root.querySelectorAll(
        'gr-change-list-item');
    for (let i = 0; i < elementItems.length; i++) {
      assert.isFalse(elementItems[i].hasAttribute('needs-review'));
    }
  });

  test('no changes', () => {
    element.changes = [];
    flush();
    const listItems = element.root.querySelectorAll(
        'gr-change-list-item');
    assert.equal(listItems.length, 0);
    const noChangesMsg =
        element.root.querySelector('.noChanges');
    assert.ok(noChangesMsg);
  });

  test('empty sections', () => {
    element.sections = [{results: []}, {results: []}];
    flush();
    const listItems = element.root.querySelectorAll(
        'gr-change-list-item');
    assert.equal(listItems.length, 0);
    const noChangesMsg = element.root.querySelectorAll(
        '.noChanges');
    assert.equal(noChangesMsg.length, 2);
  });

  suite('empty section', () => {
    test('not shown on empty non-outgoing sections', () => {
      const section = {results: []};
      assert.isTrue(element._isEmpty(section));
      assert.equal(element._getSpecialEmptySlot(section), '');
    });

    test('shown on empty outgoing sections', () => {
      const section = {results: [], isOutgoing: true};
      assert.isTrue(element._isEmpty(section));
      assert.equal(element._getSpecialEmptySlot(section), 'empty-outgoing');
    });

    test('shown on empty outgoing sections', () => {
      const section = {results: [], name: YOUR_TURN.name};
      assert.isTrue(element._isEmpty(section));
      assert.equal(element._getSpecialEmptySlot(section), 'empty-your-turn');
    });

    test('not shown on non-empty outgoing sections', () => {
      const section = {isOutgoing: true, results: [
        {_number: 0, labels: {Verified: {approved: {}}}}]};
      assert.isFalse(element._isEmpty(section));
    });
  });

  suite('empty column preference', () => {
    let element;

    setup(() => {
      element = basicFixture.instantiate();
      element.sections = [
        {results: [{}]},
      ];
      element.account = {_account_id: 1001};
      element.preferences = {
        legacycid_in_change_table: true,
        time_format: 'HHMM_12',
        change_table: [],
      };
      element._config = {};
      flush();
    });

    test('show number enabled', () => {
      assert.isTrue(element.showNumber);
    });

    test('all columns visible', () => {
      for (const column of element.columnNames) {
        const elementClass = '.' + element._lowerCase(column);
        assert.isFalse(element.shadowRoot
            .querySelector(elementClass).hidden);
      }
    });
  });

  suite('full column preference', () => {
    let element;

    setup(() => {
      element = basicFixture.instantiate();
      element.sections = [
        {results: [{}]},
      ];
      element.account = {_account_id: 1001};
      element.preferences = {
        legacycid_in_change_table: true,
        time_format: 'HHMM_12',
        change_table: [
          'Subject',
          'Status',
          'Owner',
          'Assignee',
          'Reviewers',
          'Comments',
          'Repo',
          'Branch',
          'Updated',
          'Size',
        ],
      };
      element._config = {};
      flush();
    });

    test('all columns visible', () => {
      for (const column of element.changeTableColumns) {
        const elementClass = '.' + element._lowerCase(column);
        assert.isFalse(element.shadowRoot
            .querySelector(elementClass).hidden);
      }
    });
  });

  suite('partial column preference', () => {
    let element;

    setup(() => {
      element = basicFixture.instantiate();
      element.sections = [
        {results: [{}]},
      ];
      element.account = {_account_id: 1001};
      element.preferences = {
        legacycid_in_change_table: true,
        time_format: 'HHMM_12',
        change_table: [
          'Subject',
          'Status',
          'Owner',
          'Assignee',
          'Reviewers',
          'Comments',
          'Branch',
          'Updated',
          'Size',
        ],
      };
      element._config = {};
      flush();
    });

    test('all columns except repo visible', () => {
      for (const column of element.changeTableColumns) {
        const elementClass = '.' + column.toLowerCase();
        if (column === 'Repo') {
          assert.isNotOk(element.shadowRoot.querySelector(elementClass));
        } else {
          assert.isOk(element.shadowRoot.querySelector(elementClass));
        }
      }
    });
  });

  suite('random column does not exist', () => {
    let element;

    /* This would only exist if somebody manually updated the config
    file. */
    setup(() => {
      element = basicFixture.instantiate();
      element.account = {_account_id: 1001};
      element.preferences = {
        legacycid_in_change_table: true,
        time_format: 'HHMM_12',
        change_table: [
          'Bad',
        ],
      };
      flush();
    });

    test('bad column does not exist', () => {
      const elementClass = '.bad';
      assert.isNotOk(element.shadowRoot
          .querySelector(elementClass));
    });
  });

  suite('dashboard queries', () => {
    let element;

    setup(() => {
      element = basicFixture.instantiate();
    });

    teardown(() => { sinon.restore(); });

    test('query without age and limit unchanged', () => {
      const query = 'status:closed owner:me';
      assert.deepEqual(element._processQuery(query), query);
    });

    test('query with age and limit', () => {
      const query = 'status:closed age:1week limit:10 owner:me';
      const expectedQuery = 'status:closed owner:me';
      assert.deepEqual(element._processQuery(query), expectedQuery);
    });

    test('query with age', () => {
      const query = 'status:closed age:1week owner:me';
      const expectedQuery = 'status:closed owner:me';
      assert.deepEqual(element._processQuery(query), expectedQuery);
    });

    test('query with limit', () => {
      const query = 'status:closed limit:10 owner:me';
      const expectedQuery = 'status:closed owner:me';
      assert.deepEqual(element._processQuery(query), expectedQuery);
    });

    test('query with age as value and not key', () => {
      const query = 'status:closed random:age';
      const expectedQuery = 'status:closed random:age';
      assert.deepEqual(element._processQuery(query), expectedQuery);
    });

    test('query with limit as value and not key', () => {
      const query = 'status:closed random:limit';
      const expectedQuery = 'status:closed random:limit';
      assert.deepEqual(element._processQuery(query), expectedQuery);
    });

    test('query with -age key', () => {
      const query = 'status:closed -age:1week';
      const expectedQuery = 'status:closed';
      assert.deepEqual(element._processQuery(query), expectedQuery);
    });
  });

  suite('gr-change-list sections', () => {
    let element;

    setup(() => {
      element = basicFixture.instantiate();
    });

    test('keyboard shortcuts', done => {
      element.selectedIndex = 0;
      element.sections = [
        {
          results: [
            {_number: 0},
            {_number: 1},
            {_number: 2},
          ],
        },
        {
          results: [
            {_number: 3},
            {_number: 4},
            {_number: 5},
          ],
        },
        {
          results: [
            {_number: 6},
            {_number: 7},
            {_number: 8},
          ],
        },
      ];
      flush();
      afterNextRender(element, () => {
        const elementItems = element.root.querySelectorAll(
            'gr-change-list-item');
        assert.equal(elementItems.length, 9);

        MockInteractions.pressAndReleaseKeyOn(element, 74); // 'j'
        assert.equal(element.selectedIndex, 1);
        MockInteractions.pressAndReleaseKeyOn(element, 74); // 'j'

        const navStub = sinon.stub(GerritNav, 'navigateToChange');
        assert.equal(element.selectedIndex, 2);

        MockInteractions.pressAndReleaseKeyOn(element, 13); // 'enter'
        assert.deepEqual(navStub.lastCall.args[0], {_number: 2},
            'Should navigate to /c/2/');

        MockInteractions.pressAndReleaseKeyOn(element, 75); // 'k'
        assert.equal(element.selectedIndex, 1);
        MockInteractions.pressAndReleaseKeyOn(element, 13); // 'enter'
        assert.deepEqual(navStub.lastCall.args[0], {_number: 1},
            'Should navigate to /c/1/');

        MockInteractions.pressAndReleaseKeyOn(element, 74); // 'j'
        MockInteractions.pressAndReleaseKeyOn(element, 74); // 'j'
        MockInteractions.pressAndReleaseKeyOn(element, 74); // 'j'
        assert.equal(element.selectedIndex, 4);
        MockInteractions.pressAndReleaseKeyOn(element, 13); // 'enter'
        assert.deepEqual(navStub.lastCall.args[0], {_number: 4},
            'Should navigate to /c/4/');

        MockInteractions.pressAndReleaseKeyOn(element, 82); // 'r'
        const change = element._changeForIndex(element.selectedIndex);
        assert.equal(change.reviewed, true,
            'Should mark change as reviewed');
        MockInteractions.pressAndReleaseKeyOn(element, 82); // 'r'
        assert.equal(change.reviewed, false,
            'Should mark change as unreviewed');
        done();
      });
    });

    test('highlight attribute is updated correctly', () => {
      element.changes = [
        {
          _number: 0,
          status: 'NEW',
          owner: {_account_id: 0},
        },
        {
          _number: 1,
          status: 'ABANDONED',
          owner: {_account_id: 0},
        },
      ];
      element.account = {_account_id: 42};
      flush();
      let items = element._getListItems();
      assert.equal(items.length, 2);
      assert.isFalse(items[0].hasAttribute('highlight'));
      assert.isFalse(items[1].hasAttribute('highlight'));

      // Assign all issues to the user, but only the first one is highlighted
      // because the second one is abandoned.
      element.set(['changes', 0, 'assignee'], {_account_id: 12});
      element.set(['changes', 1, 'assignee'], {_account_id: 12});
      element.account = {_account_id: 12};
      flush();
      items = element._getListItems();
      assert.isTrue(items[0].hasAttribute('highlight'));
      assert.isFalse(items[1].hasAttribute('highlight'));
    });

    test('_computeItemHighlight gives false for null account', () => {
      assert.isFalse(
          element._computeItemHighlight(null, {assignee: {_account_id: 42}}));
    });

    test('_computeItemAbsoluteIndex', () => {
      sinon.stub(element, '_computeLabelNames');
      element.sections = [
        {results: new Array(1)},
        {results: new Array(2)},
        {results: new Array(3)},
      ];

      assert.equal(element._computeItemAbsoluteIndex(0, 0), 0);
      // Out of range but no matter.
      assert.equal(element._computeItemAbsoluteIndex(0, 1), 1);

      assert.equal(element._computeItemAbsoluteIndex(1, 0), 1);
      assert.equal(element._computeItemAbsoluteIndex(1, 1), 2);
      assert.equal(element._computeItemAbsoluteIndex(1, 2), 3);
      assert.equal(element._computeItemAbsoluteIndex(2, 0), 3);
      assert.equal(element._computeItemAbsoluteIndex(3, 0), 6);
    });
  });
});

