/**
 * @license
 * Copyright (C) 2016 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 {
  _testOnly_computeWipForPatchSets, computeAllPatchSets,
  fetchChangeUpdates, findEditParentPatchNum, findEditParentRevision,
  getParentIndex, getRevisionByPatchNum,
  isMergeParent,
  sortRevisions,
} from './patch-set-util.js';

suite('gr-patch-set-util tests', () => {
  test('getRevisionByPatchNum', () => {
    const revisions = [
      {_number: 0},
      {_number: 1},
      {_number: 2},
    ];
    assert.deepEqual(getRevisionByPatchNum(revisions, 1), revisions[1]);
    assert.deepEqual(getRevisionByPatchNum(revisions, 2), revisions[2]);
    assert.equal(getRevisionByPatchNum(revisions, 3), undefined);
  });

  test('fetchChangeUpdates on latest', done => {
    const knownChange = {
      revisions: {
        sha1: {description: 'patch 1', _number: 1},
        sha2: {description: 'patch 2', _number: 2},
      },
      status: 'NEW',
      messages: [],
    };
    const mockRestApi = {
      getChangeDetail() {
        return Promise.resolve(knownChange);
      },
    };
    fetchChangeUpdates(knownChange, mockRestApi)
        .then(result => {
          assert.isTrue(result.isLatest);
          assert.isNotOk(result.newStatus);
          assert.isNotOk(result.newMessages);
          done();
        });
  });

  test('fetchChangeUpdates not on latest', done => {
    const knownChange = {
      revisions: {
        sha1: {description: 'patch 1', _number: 1},
        sha2: {description: 'patch 2', _number: 2},
      },
      status: 'NEW',
      messages: [],
    };
    const actualChange = {
      revisions: {
        sha1: {description: 'patch 1', _number: 1},
        sha2: {description: 'patch 2', _number: 2},
        sha3: {description: 'patch 3', _number: 3},
      },
      status: 'NEW',
      messages: [],
    };
    const mockRestApi = {
      getChangeDetail() {
        return Promise.resolve(actualChange);
      },
    };
    fetchChangeUpdates(knownChange, mockRestApi)
        .then(result => {
          assert.isFalse(result.isLatest);
          assert.isNotOk(result.newStatus);
          assert.isNotOk(result.newMessages);
          done();
        });
  });

  test('fetchChangeUpdates new status', done => {
    const knownChange = {
      revisions: {
        sha1: {description: 'patch 1', _number: 1},
        sha2: {description: 'patch 2', _number: 2},
      },
      status: 'NEW',
      messages: [],
    };
    const actualChange = {
      revisions: {
        sha1: {description: 'patch 1', _number: 1},
        sha2: {description: 'patch 2', _number: 2},
      },
      status: 'MERGED',
      messages: [],
    };
    const mockRestApi = {
      getChangeDetail() {
        return Promise.resolve(actualChange);
      },
    };
    fetchChangeUpdates(knownChange, mockRestApi)
        .then(result => {
          assert.isTrue(result.isLatest);
          assert.equal(result.newStatus, 'MERGED');
          assert.isNotOk(result.newMessages);
          done();
        });
  });

  test('fetchChangeUpdates new messages', done => {
    const knownChange = {
      revisions: {
        sha1: {description: 'patch 1', _number: 1},
        sha2: {description: 'patch 2', _number: 2},
      },
      status: 'NEW',
      messages: [],
    };
    const actualChange = {
      revisions: {
        sha1: {description: 'patch 1', _number: 1},
        sha2: {description: 'patch 2', _number: 2},
      },
      status: 'NEW',
      messages: [{message: 'blah blah'}],
    };
    const mockRestApi = {
      getChangeDetail() {
        return Promise.resolve(actualChange);
      },
    };
    fetchChangeUpdates(knownChange, mockRestApi)
        .then(result => {
          assert.isTrue(result.isLatest);
          assert.isNotOk(result.newStatus);
          assert.deepEqual(result.newMessages, {message: 'blah blah'});
          done();
        });
  });

  test('_computeWipForPatchSets', () => {
    // Compute patch sets for a given timeline on a change. The initial WIP
    // property of the change can be true or false. The map of tags by
    // revision is keyed by patch set number. Each value is a list of change
    // message tags in the order that they occurred in the timeline. These
    // indicate actions that modify the WIP property of the change and/or
    // create new patch sets.
    //
    // Returns the actual results with an assertWip method that can be used
    // to compare against an expected value for a particular patch set.
    const compute = (initialWip, tagsByRevision) => {
      const change = {
        messages: [],
        work_in_progress: initialWip,
      };
      const revs = Object.keys(tagsByRevision).sort((a, b) => a - b);
      for (const rev of revs) {
        for (const tag of tagsByRevision[rev]) {
          change.messages.push({
            tag,
            _revision_number: rev,
          });
        }
      }
      let patchNums = revs.map(rev => { return {num: rev}; });
      patchNums = _testOnly_computeWipForPatchSets(
          change, patchNums);
      const actualWipsByRevision = {};
      for (const patchNum of patchNums) {
        actualWipsByRevision[patchNum.num] = patchNum.wip;
      }
      const verifier = {
        assertWip(revision, expectedWip) {
          const patchNum = patchNums.find(patchNum => patchNum.num == revision);
          if (!patchNum) {
            assert.fail('revision ' + revision + ' not found');
          }
          assert.equal(patchNum.wip, expectedWip,
              'wip state for ' + revision + ' is ' +
            patchNum.wip + '; expected ' + expectedWip);
          return verifier;
        },
      };
      return verifier;
    };

    compute(false, {1: ['upload']}).assertWip(1, false);
    compute(true, {1: ['upload']}).assertWip(1, true);

    const setWip = 'autogenerated:gerrit:setWorkInProgress';
    const uploadInWip = 'autogenerated:gerrit:newWipPatchSet';
    const clearWip = 'autogenerated:gerrit:setReadyForReview';

    compute(false, {
      1: ['upload', setWip],
      2: ['upload'],
      3: ['upload', clearWip],
      4: ['upload', setWip],
    }).assertWip(1, false) // Change was created with PS1 ready for review
        .assertWip(2, true) // PS2 was uploaded during WIP
        .assertWip(3, false) // PS3 was marked ready for review after upload
        .assertWip(4, false); // PS4 was uploaded ready for review

    compute(false, {
      1: [uploadInWip, null, 'addReviewer'],
      2: ['upload'],
      3: ['upload', clearWip, setWip],
      4: ['upload'],
      5: ['upload', clearWip],
      6: [uploadInWip],
    }).assertWip(1, true) // Change was created in WIP
        .assertWip(2, true) // PS2 was uploaded during WIP
        .assertWip(3, false) // PS3 was marked ready for review
        .assertWip(4, true) // PS4 was uploaded during WIP
        .assertWip(5, false) // PS5 was marked ready for review
        .assertWip(6, true); // PS6 was uploaded with WIP option
  });

  test('isMergeParent', () => {
    assert.isFalse(isMergeParent(1));
    assert.isFalse(isMergeParent(4321));
    assert.isFalse(isMergeParent('52'));
    assert.isFalse(isMergeParent('edit'));
    assert.isFalse(isMergeParent('PARENT'));
    assert.isFalse(isMergeParent(0));

    assert.isTrue(isMergeParent(-23));
    assert.isTrue(isMergeParent(-1));
    assert.isTrue(isMergeParent('-42'));
  });

  test('findEditParentRevision', () => {
    let revisions = [
      {_number: 0},
      {_number: 1},
      {_number: 2},
    ];
    assert.strictEqual(findEditParentRevision(revisions), null);

    revisions = [...revisions, {_number: 'edit', basePatchNum: 3}];
    assert.strictEqual(findEditParentRevision(revisions), null);

    revisions = [...revisions, {_number: 3}];
    assert.deepEqual(findEditParentRevision(revisions), {_number: 3});
  });

  test('findEditParentPatchNum', () => {
    let revisions = [
      {_number: 0},
      {_number: 1},
      {_number: 2},
    ];
    assert.equal(findEditParentPatchNum(revisions), -1);

    revisions =
        [...revisions, {_number: 'edit', basePatchNum: 3}, {_number: 3}];
    assert.deepEqual(findEditParentPatchNum(revisions), 3);
  });

  test('sortRevisions', () => {
    const revisions = [
      {_number: 0},
      {_number: 2},
      {_number: 1},
    ];
    const sorted = [
      {_number: 2},
      {_number: 1},
      {_number: 0},
    ];

    assert.deepEqual(sortRevisions(revisions), sorted);

    // Edit patchset should follow directly after its basePatchNum.
    revisions.push({_number: 'edit', basePatchNum: 2});
    sorted.unshift({_number: 'edit', basePatchNum: 2});
    assert.deepEqual(sortRevisions(revisions), sorted);

    revisions[0].basePatchNum = 0;
    const edit = sorted.shift();
    edit.basePatchNum = 0;
    // Edit patchset should be at index 2.
    sorted.splice(2, 0, edit);
    assert.deepEqual(sortRevisions(revisions), sorted);
  });

  test('getParentIndex', () => {
    assert.equal(getParentIndex('-13'), 13);
    assert.equal(getParentIndex(-4), 4);
  });

  test('computeAllPatchSets', () => {
    const expected = [
      {num: 4, desc: 'test', sha: 'rev4'},
      {num: 3, desc: 'test', sha: 'rev3'},
      {num: 2, desc: 'test', sha: 'rev2'},
      {num: 1, desc: 'test', sha: 'rev1'},
    ];
    const patchNums = computeAllPatchSets({
      revisions: {
        rev3: {_number: 3, description: 'test', date: 3},
        rev1: {_number: 1, description: 'test', date: 1},
        rev4: {_number: 4, description: 'test', date: 4},
        rev2: {_number: 2, description: 'test', date: 2},
      },
    });
    assert.equal(patchNums.length, expected.length);
    for (let i = 0; i < expected.length; i++) {
      assert.deepEqual(patchNums[i], expected[i]);
    }
  });
});

