// Copyright (C) 2017 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';

  const PARENT = 'PARENT';

  Polymer({
    is: 'gr-comment-api',

    properties: {
      /** @type {number} */
      _changeNum: Number,
      /** @type {!Object|undefined} */
      _comments: Object,
      /** @type {!Object|undefined} */
      _drafts: Object,
      /** @type {!Object|undefined} */
      _robotComments: Object,
    },

    behaviors: [
      Gerrit.PatchSetBehavior,
    ],

    /**
     * Load all comments (with drafts and robot comments) for the given change
     * number. The returned promise resolves when the comments have loaded, but
     * does not yield the comment data.
     *
     * @param {number} changeNum
     * @return {!Promise}
     */
    loadAll(changeNum) {
      this._changeNum = changeNum;

      // Reset comment arrays.
      this._comments = undefined;
      this._drafts = undefined;
      this._robotComments = undefined;

      const promises = [];
      promises.push(this.$.restAPI.getDiffComments(changeNum)
          .then(comments => { this._comments = comments; }));
      promises.push(this.$.restAPI.getDiffRobotComments(changeNum)
          .then(robotComments => { this._robotComments = robotComments; }));
      promises.push(this.$.restAPI.getDiffDrafts(changeNum)
          .then(drafts => { this._drafts = drafts; }));

      return Promise.all(promises);
    },

    /**
     * Get an object mapping file paths to a boolean representing whether that
     * path contains diff comments in the given patch set (including drafts and
     * robot comments).
     *
     * Paths with comments are mapped to true, whereas paths without comments
     * are not mapped.
     *
     * @param {!Object} patchRange The patch-range object containing patchNum
     *     and basePatchNum properties to represent the range.
     * @return {Object}
     */
    getPaths(patchRange) {
      const responses = [this._comments, this._drafts, this._robotComments];
      const commentMap = {};
      for (const response of responses) {
        for (const path in response) {
          if (response.hasOwnProperty(path) &&
              response[path].some(c => this._isInPatchRange(c, patchRange))) {
            commentMap[path] = true;
          }
        }
      }
      return commentMap;
    },

    /**
     * Get the comments (with drafts and robot comments) for a path and
     * patch-range. Returns an object with left and right properties mapping to
     * arrays of comments in on either side of the patch range for that path.
     *
     * @param {!string} path
     * @param {!Object} patchRange The patch-range object containing patchNum
     *     and basePatchNum properties to represent the range.
     * @param {Object=} opt_projectConfig Optional project config object to
     *     include in the meta sub-object.
     * @return {Object}
     */
    getCommentsForPath(path, patchRange, opt_projectConfig) {
      const comments = this._comments[path] || [];
      const drafts = this._drafts[path] || [];
      const robotComments = this._robotComments[path] || [];

      drafts.forEach(d => { d.__draft = true; });

      const all = comments.concat(drafts).concat(robotComments);

      const baseComments = all.filter(c =>
          this._isInBaseOfPatchRange(c, patchRange));
      const revisionComments = all.filter(c =>
          this._isInRevisionOfPatchRange(c, patchRange));

      return {
        meta: {
          changeNum: this._changeNum,
          path,
          patchRange,
          projectConfig: opt_projectConfig,
        },
        left: baseComments,
        right: revisionComments,
      };
    },

    /**
     * Whether the given comment should be included in the base side of the
     * given patch range.
     * @param {!Object} comment
     * @param {!Object} range
     * @return {boolean}
     */
    _isInBaseOfPatchRange(comment, range) {
      // If the base of the range is the parent of the patch:
      if (range.basePatchNum === PARENT &&
          comment.side === PARENT &&
          this.patchNumEquals(comment.patch_set, range.patchNum)) {
        return true;
      }
      // If the base of the range is not the parent of the patch:
      if (range.basePatchNum !== PARENT &&
          comment.side !== PARENT &&
          this.patchNumEquals(comment.patch_set, range.basePatchNum)) {
        return true;
      }
      return false;
    },

    /**
     * Whether the given comment should be included in the revision side of the
     * given patch range.
     * @param {!Object} comment
     * @param {!Object} range
     * @return {boolean}
     */
    _isInRevisionOfPatchRange(comment, range) {
      return comment.side !== PARENT &&
          this.patchNumEquals(comment.patch_set, range.patchNum);
    },

    /**
     * Whether the given comment should be included in the given patch range.
     * @param {!Object} comment
     * @param {!Object} range
     * @return {boolean|undefined}
     */
    _isInPatchRange(comment, range) {
      return this._isInBaseOfPatchRange(comment, range) ||
          this._isInRevisionOfPatchRange(comment, range);
    },
  });
})();