// 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';

  var STORAGE_DEBOUNCE_INTERVAL = 400;

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

    /**
     * Fired when the Reply action is triggered.
     *
     * @event reply
     */

    /**
     * Fired when the Done action is triggered.
     *
     * @event done
     */

    /**
     * Fired when this comment is discarded.
     *
     * @event comment-discard
     */

    /**
     * Fired when this comment is saved.
     *
     * @event comment-save
     */

    /**
     * Fired when this comment is updated.
     *
     * @event comment-update
     */

    /**
     * @event comment-mouse-over
     */

    /**
     * @event comment-mouse-out
     */

    properties: {
      changeNum: String,
      comment: {
        type: Object,
        notify: true,
        observer: '_commentChanged',
      },
      disabled: {
        type: Boolean,
        value: false,
        reflectToAttribute: true,
      },
      draft: {
        type: Boolean,
        value: false,
        observer: '_draftChanged',
      },
      editing: {
        type: Boolean,
        value: false,
        observer: '_editingChanged',
      },
      patchNum: String,
      showActions: Boolean,
      collapsed: {
        type: Boolean,
        value: true,
        observer: '_toggleCollapseClass',
      },
      projectConfig: Object,

      _xhrPromise: Object,  // Used for testing.
      _messageText: {
        type: String,
        value: '',
        observer: '_messageTextChanged',
      },
    },

    observers: [
      '_commentMessageChanged(comment.message)',
      '_loadLocalDraft(changeNum, patchNum, comment)',
    ],

    attached: function() {
      if (this.editing) {
        this.collapsed = false;
      }
    },

    detached: function() {
      this.cancelDebouncer('fire-update');
    },

    _computeShowHideText: function(collapsed) {
      return collapsed ? '◀' : '▼';
    },

    save: function() {
      this.comment.message = this._messageText;
      this.disabled = true;

      this.$.storage.eraseDraftComment({
        changeNum: this.changeNum,
        patchNum: this.patchNum,
        path: this.comment.path,
        line: this.comment.line,
      });

      this._xhrPromise = this._saveDraft(this.comment).then(function(response) {
        this.disabled = false;
        if (!response.ok) { return response; }

        return this.$.restAPI.getResponseObject(response).then(function(obj) {
          var comment = obj;
          comment.__draft = true;
          // Maintain the ephemeral draft ID for identification by other
          // elements.
          if (this.comment.__draftID) {
            comment.__draftID = this.comment.__draftID;
          }
          this.comment = comment;
          this.editing = false;
          this._fireSave();
          return obj;
        }.bind(this));
      }.bind(this)).catch(function(err) {
        this.disabled = false;
        throw err;
      }.bind(this));
    },

    _commentChanged: function(comment) {
      this.editing = !!comment.__editing;
      if (this.editing) { // It's a new draft/reply, notify.
        this._fireUpdate();
      }
    },

    _getEventPayload: function(opt_mixin) {
      var payload = {
        comment: this.comment,
        patchNum: this.patchNum,
      };
      for (var k in opt_mixin) {
        payload[k] = opt_mixin[k];
      }
      return payload;
    },

    _fireSave: function() {
      this.fire('comment-save', this._getEventPayload());
    },

    _fireUpdate: function() {
      this.debounce('fire-update', function() {
        this.fire('comment-update', this._getEventPayload());
      });
    },

    _draftChanged: function(draft) {
      this.$.container.classList.toggle('draft', draft);
    },

    _editingChanged: function(editing, previousValue) {
      this.$.container.classList.toggle('editing', editing);
      if (editing) {
        var textarea = this.$.editTextarea.textarea;
        // Put the cursor at the end always.
        textarea.selectionStart = textarea.value.length;
        textarea.selectionEnd = textarea.selectionStart;
        this.async(function() {
          textarea.focus();
        }.bind(this));
      }
      if (this.comment && this.comment.id) {
        this.$$('.cancel').hidden = !editing;
      }
      if (this.comment) {
        this.comment.__editing = this.editing;
      }
      if (editing != !!previousValue) {
        // To prevent event firing on comment creation.
        this._fireUpdate();
      }
    },

    _computeLinkToComment: function(comment) {
      return '#' + comment.line;
    },

    _computeSaveDisabled: function(draft) {
      return draft == null || draft.trim() == '';
    },

    _handleTextareaKeydown: function(e) {
      switch (e.keyCode) {
        case 27: // 'esc'
          if (this._messageText.length === 0) {
            this._handleCancel(e);
          }
          break;
        case 83: // 's'
          if (e.ctrlKey) {
            this._handleSave(e);
          }
          break;
      }
    },

    _handleToggleCollapsed: function() {
      this.collapsed = !this.collapsed;
    },

    _toggleCollapseClass: function(collapsed) {
      if (collapsed) {
        this.$.container.classList.add('collapsed');
      } else {
        this.$.container.classList.remove('collapsed');
      }
    },

    _commentMessageChanged: function(message) {
      this._messageText = message || '';
    },

    _messageTextChanged: function(newValue, oldValue) {
      if (!this.comment || (this.comment && this.comment.id)) { return; }

      this.debounce('store', function() {
        var message = this._messageText;

        var commentLocation = {
          changeNum: this.changeNum,
          patchNum: this.patchNum,
          path: this.comment.path,
          line: this.comment.line,
        };

        if ((!this._messageText || !this._messageText.length) && oldValue) {
          // If the draft has been modified to be empty, then erase the storage
          // entry.
          this.$.storage.eraseDraftComment(commentLocation);
        } else {
          this.$.storage.setDraftComment(commentLocation, message);
        }
        this._fireUpdate();
      }, STORAGE_DEBOUNCE_INTERVAL);
    },

    _handleLinkTap: function(e) {
      e.preventDefault();
      var hash = this._computeLinkToComment(this.comment);
      // Don't add the hash to the window history if it's already there.
      // Otherwise you mess up expected back button behavior.
      if (window.location.hash == hash) { return; }
      // Change the URL but don’t trigger a nav event. Otherwise it will
      // reload the page.
      page.show(window.location.pathname + hash, null, false);
    },

    _handleReply: function(e) {
      this._preventDefaultAndBlur(e);
      this.fire('reply', this._getEventPayload(), {bubbles: false});
    },

    _handleQuote: function(e) {
      this._preventDefaultAndBlur(e);
      this.fire(
          'reply', this._getEventPayload({quote: true}), {bubbles: false});
    },

    _handleDone: function(e) {
      this._preventDefaultAndBlur(e);
      this.fire('done', this._getEventPayload(), {bubbles: false});
    },

    _handleEdit: function(e) {
      this._preventDefaultAndBlur(e);
      this._messageText = this.comment.message;
      this.editing = true;
    },

    _handleSave: function(e) {
      this._preventDefaultAndBlur(e);
      this.save();
    },

    _handleCancel: function(e) {
      this._preventDefaultAndBlur(e);
      if (this.comment.message == null || this.comment.message.length == 0) {
        this._fireDiscard();
        return;
      }
      this._messageText = this.comment.message;
      this.editing = false;
    },

    _fireDiscard: function() {
      this.cancelDebouncer('fire-update');
      this.fire('comment-discard', this._getEventPayload());
    },

    _handleDiscard: function(e) {
      this._preventDefaultAndBlur(e);
      if (!this.comment.__draft) {
        throw Error('Cannot discard a non-draft comment.');
      }
      this.editing = false;
      this.disabled = true;
      if (!this.comment.id) {
        this.disabled = false;
        this._fireDiscard();
        return;
      }

      this._xhrPromise = this._deleteDraft(this.comment).then(
          function(response) {
            this.disabled = false;
            if (!response.ok) { return response; }

            this._fireDiscard();
          }.bind(this)).catch(function(err) {
            this.disabled = false;
            throw err;
          }.bind(this));
    },

    _preventDefaultAndBlur: function(e) {
      e.preventDefault();
      Polymer.dom(e).rootTarget.blur();
    },

    _saveDraft: function(draft) {
      return this.$.restAPI.saveDiffDraft(this.changeNum, this.patchNum, draft);
    },

    _deleteDraft: function(draft) {
      return this.$.restAPI.deleteDiffDraft(this.changeNum, this.patchNum,
          draft);
    },

    _loadLocalDraft: function(changeNum, patchNum, comment) {
      // Only apply local drafts to comments that haven't been saved
      // remotely, and haven't been given a default message already.
      if (!comment || comment.id || comment.message) {
        return;
      }

      var draft = this.$.storage.getDraftComment({
        changeNum: changeNum,
        patchNum: patchNum,
        path: comment.path,
        line: comment.line,
      });

      if (draft) {
        this.set('comment.message', draft.message);
      }
    },

    _handleMouseEnter: function(e) {
      this.fire('comment-mouse-over', this._getEventPayload());
    },

    _handleMouseLeave: function(e) {
      this.fire('comment-mouse-out', this._getEventPayload());
    },
  });
})();