/**
 * @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.
 */
(function() {
  'use strict';

  // Maximum length for patch set descriptions.
  const PATCH_DESC_MAX_LENGTH = 500;

  /**
   * Fired when the patch range changes
   *
   * @event patch-range-change
   *
   * @property {string} patchNum
   * @property {string} basePatchNum
   */

  Polymer({
    is: 'gr-patch-range-select',

    properties: {
      availablePatches: Array,
      _baseDropdownContent: {
        type: Object,
        computed: '_computeBaseDropdownContent(availablePatches, patchNum,' +
            '_sortedRevisions, changeComments, revisionInfo)',
      },
      _patchDropdownContent: {
        type: Object,
        computed: '_computePatchDropdownContent(availablePatches,' +
            'basePatchNum, _sortedRevisions, changeComments)',
      },
      changeNum: String,
      changeComments: Object,
      /** @type {{ meta_a: !Array, meta_b: !Array}} */
      filesWeblinks: Object,
      patchNum: String,
      basePatchNum: String,
      revisions: Object,
      revisionInfo: Object,
      _sortedRevisions: Array,
    },

    observers: [
      '_updateSortedRevisions(revisions.*)',
    ],

    behaviors: [Gerrit.PatchSetBehavior],

    _computeBaseDropdownContent(availablePatches, patchNum, _sortedRevisions,
        changeComments, revisionInfo) {
      const parentCounts = revisionInfo.getParentCountMap();
      const currentParentCount = parentCounts.hasOwnProperty(patchNum) ?
          parentCounts[patchNum] : 1;
      const maxParents = revisionInfo.getMaxParents();
      const isMerge = currentParentCount > 1;

      const dropdownContent = [];
      for (const basePatch of availablePatches) {
        const basePatchNum = basePatch.num;
        const entry = this._createDropdownEntry(basePatchNum, 'Patchset ',
            _sortedRevisions, changeComments);
        dropdownContent.push(Object.assign({}, entry, {
          disabled: this._computeLeftDisabled(
              basePatch.num, patchNum, _sortedRevisions),
        }));
      }

      dropdownContent.push({
        text: isMerge ? 'Auto Merge' : 'Base',
        value: 'PARENT',
      });

      for (let idx = 0; isMerge && idx < maxParents; idx++) {
        dropdownContent.push({
          disabled: idx >= currentParentCount,
          triggerText: `Parent ${idx + 1}`,
          text: `Parent ${idx + 1}`,
          mobileText: `Parent ${idx + 1}`,
          value: -(idx + 1),
        });
      }

      return dropdownContent;
    },

    _computeMobileText(patchNum, changeComments, revisions) {
      return `${patchNum}` +
          `${this._computePatchSetCommentsString(changeComments, patchNum)}` +
          `${this._computePatchSetDescription(revisions, patchNum, true)}`;
    },

    _computePatchDropdownContent(availablePatches, basePatchNum,
        _sortedRevisions, changeComments) {
      const dropdownContent = [];
      for (const patch of availablePatches) {
        const patchNum = patch.num;
        const entry = this._createDropdownEntry(
            patchNum, patchNum === 'edit' ? '' : 'Patchset ', _sortedRevisions,
            changeComments);
        dropdownContent.push(Object.assign({}, entry, {
          disabled: this._computeRightDisabled(basePatchNum, patchNum,
              _sortedRevisions),
        }));
      }
      return dropdownContent;
    },

    _createDropdownEntry(patchNum, prefix, sortedRevisions, changeComments) {
      const entry = {
        triggerText: `${prefix}${patchNum}`,
        text: `${prefix}${patchNum}` +
            `${this._computePatchSetCommentsString(
                changeComments, patchNum)}`,
        mobileText: this._computeMobileText(patchNum, changeComments,
            sortedRevisions),
        bottomText: `${this._computePatchSetDescription(
            sortedRevisions, patchNum)}`,
        value: patchNum,
      };
      const date = this._computePatchSetDate(sortedRevisions, patchNum);
      if (date) {
        entry['date'] = date;
      }
      return entry;
    },

    _updateSortedRevisions(revisionsRecord) {
      const revisions = revisionsRecord.base;
      this._sortedRevisions = this.sortRevisions(Object.values(revisions));
    },

    /**
     * The basePatchNum should always be <= patchNum -- because sortedRevisions
     * is sorted in reverse order (higher patchset nums first), invalid base
     * patch nums have an index greater than the index of patchNum.
     * @param {number|string} basePatchNum The possible base patch num.
     * @param {number|string} patchNum The current selected patch num.
     * @param {!Array} sortedRevisions
     */
    _computeLeftDisabled(basePatchNum, patchNum, sortedRevisions) {
      return this.findSortedIndex(basePatchNum, sortedRevisions) <=
          this.findSortedIndex(patchNum, sortedRevisions);
    },

    /**
     * The basePatchNum should always be <= patchNum -- because sortedRevisions
     * is sorted in reverse order (higher patchset nums first), invalid patch
     * nums have an index greater than the index of basePatchNum.
     *
     * In addition, if the current basePatchNum is 'PARENT', all patchNums are
     * valid.
     *
     * If the curent basePatchNum is a parent index, then only patches that have
     * at least that many parents are valid.
     *
     * @param {number|string} basePatchNum The current selected base patch num.
     * @param {number|string} patchNum The possible patch num.
     * @param {!Array} sortedRevisions
     * @return {boolean}
     */
    _computeRightDisabled(basePatchNum, patchNum, sortedRevisions) {
      if (this.patchNumEquals(basePatchNum, 'PARENT')) { return false; }

      if (this.isMergeParent(basePatchNum)) {
        // Note: parent indices use 1-offset.
        return this.revisionInfo.getParentCount(patchNum) <
            this.getParentIndex(basePatchNum);
      }

      return this.findSortedIndex(basePatchNum, sortedRevisions) <=
          this.findSortedIndex(patchNum, sortedRevisions);
    },


    _computePatchSetCommentsString(changeComments, patchNum) {
      if (!changeComments) { return; }

      const commentCount = changeComments.computeCommentCount(patchNum);
      const commentString = GrCountStringFormatter.computePluralString(
          commentCount, 'comment');

      const unresolvedCount = changeComments.computeUnresolvedNum(patchNum);
      const unresolvedString = GrCountStringFormatter.computeString(
          unresolvedCount, 'unresolved');

      if (!commentString.length && !unresolvedString.length) {
        return '';
      }

      return ` (${commentString}` +
          // Add a comma + space if both comments and unresolved
          (commentString && unresolvedString ? ', ' : '') +
          `${unresolvedString})`;
    },

    /**
     * @param {!Array} revisions
     * @param {number|string} patchNum
     * @param {boolean=} opt_addFrontSpace
     */
    _computePatchSetDescription(revisions, patchNum, opt_addFrontSpace) {
      const rev = this.getRevisionByPatchNum(revisions, patchNum);
      return (rev && rev.description) ?
          (opt_addFrontSpace ? ' ' : '') +
          rev.description.substring(0, PATCH_DESC_MAX_LENGTH) : '';
    },

    /**
     * @param {!Array} revisions
     * @param {number|string} patchNum
     */
    _computePatchSetDate(revisions, patchNum) {
      const rev = this.getRevisionByPatchNum(revisions, patchNum);
      return rev ? rev.created : undefined;
    },

    /**
     * Catches value-change events from the patchset dropdowns and determines
     * whether or not a patch change event should be fired.
     */
    _handlePatchChange(e) {
      const detail = {patchNum: this.patchNum, basePatchNum: this.basePatchNum};
      const target = Polymer.dom(e).localTarget;

      if (target === this.$.patchNumDropdown) {
        detail.patchNum = e.detail.value;
      } else {
        detail.basePatchNum = e.detail.value;
      }

      this.dispatchEvent(
          new CustomEvent('patch-range-change', {detail, bubbles: false}));
    },
  });
})();