"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.EditorContentManager = void 0;

var _Validation = require("./Validation");

function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; }

function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; }

function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }

function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }

/**
 * The EditorContentManager facilitates listening to local content changes and
 * the playback of remote content changes into the editor.
 */
var EditorContentManager =
/*#__PURE__*/
function () {
  /**
   * Option defaults.
   *
   * @internal
   */

  /**
   * The options that configure the EditorContentManager.
   * @internal
   */

  /**
   * A flag denoting if outgoing events should be suppressed.
   * @internal
   */

  /**
   * Constructs a new EditorContentManager using the supplied options.
   *
   * @param options
   *   The options that configure the EditorContentManager.
   */
  function EditorContentManager(options) {
    var _this = this;

    _classCallCheck(this, EditorContentManager);

    this._onBeforeChange = function (editor, changeObj) {
      if (_this._suppress) {
        return;
      }

      var from = editor.indexFromPos(changeObj.from);
      var to = editor.indexFromPos(changeObj.to);
      var deleted = null;
      var inserted = null; // Code the from and to are the same for in inserts because the
      // insert happens "at" a specific index. The delete on the other
      // hand covers a non empty range.

      if (from !== to) {
        deleted = editor.getRange(changeObj.from, changeObj.to);
      } // If you insert a new line you will get ["", ""] in the 'text' property
      // so if the first element in the text array is non-empty or if you have
      // more than one element, you know you have an insert.


      if (changeObj.text[0] !== "" || changeObj.text.length > 1) {
        inserted = changeObj.text.join("\n");
      }

      _this._operationQueue.push({
        from: from,
        to: to,
        inserted: inserted,
        deleted: deleted
      });
    };

    this._onChanges = function (_, changes) {
      if (_this._suppress) {
        return;
      }

      changes.forEach(function (changeObj) {
        console.log("change(".concat(_this._options.id, ")"), changeObj);

        var _this$_operationQueue = _this._operationQueue.shift(),
            from = _this$_operationQueue.from,
            to = _this$_operationQueue.to,
            inserted = _this$_operationQueue.inserted,
            deleted = _this$_operationQueue.deleted;

        if (inserted !== null && deleted === null) {
          _this._options.onInsert(from, inserted);
        } else if (inserted !== null && deleted !== null) {
          _this._options.onReplace(from, to - from, inserted);
        } else if (inserted === null && deleted !== null) {
          _this._options.onDelete(from, to - from);
        } else {
          throw new Error("Unexpected change: " + JSON.stringify(changeObj));
        }
      });
    };

    this._options = _objectSpread({}, EditorContentManager._DEFAULTS, {}, options);

    _Validation.Validation.assertDefined(this._options, "options");

    _Validation.Validation.assertDefined(this._options.editor, "options.editor");

    _Validation.Validation.assertFunction(this._options.onInsert, "options.onInsert");

    _Validation.Validation.assertFunction(this._options.onReplace, "options.onReplace");

    _Validation.Validation.assertFunction(this._options.onDelete, "options.onDelete");

    this._options.editor.on("beforeChange", this._onBeforeChange);

    this._options.editor.on("changes", this._onChanges);

    this._operationQueue = [];
  }
  /**
   * Inserts text into the editor.
   *
   * @param index
   *   The index to insert text at.
   * @param text
   *   The text to insert.
   */


  _createClass(EditorContentManager, [{
    key: "insert",
    value: function insert(index, text) {
      this._suppress = true;

      var from = this._options.editor.posFromIndex(index);

      this._options.editor.replaceRange(text, from, undefined, this._options.remoteOrigin);

      this._suppress = false;
    }
    /**
     * Replaces text in the editor.
     *
     * @param index
     *   The start index of the range to replace.
     * @param length
     *   The length of the  range to replace.
     * @param text
     *   The text to insert.
     */

  }, {
    key: "replace",
    value: function replace(index, length, text) {
      this._suppress = true;

      var from = this._options.editor.posFromIndex(index);

      var to = this._options.editor.posFromIndex(index + length);

      this._options.editor.replaceRange(text, from, to, this._options.remoteOrigin);

      this._suppress = false;
    }
    /**
     * Deletes text in the editor.
     *
     * @param index
     *   The start index of the range to remove.
     * @param length
     *   The length of the  range to remove.
     */

  }, {
    key: "delete",
    value: function _delete(index, length) {
      this._suppress = true;

      var from = this._options.editor.posFromIndex(index);

      var to = this._options.editor.posFromIndex(index + length);

      this._options.editor.replaceRange("", from, to, this._options.remoteOrigin);

      this._suppress = false;
    }
    /**
     * Disposes of the content manager, freeing any resources.
     */

  }, {
    key: "dispose",
    value: function dispose() {
      this._options.editor.off("beforeChange", this._onBeforeChange);

      this._options.editor.off("changes", this._onChanges);
    }
    /**
     * A helper method to process local changes from CodeMirror. Before change
     * is used because the from and to positions from code mirror are relative
     * to the state of the document before the change in the "changes" event,
     * but the document has already been changed. So we use before changes to
     * calculate the from and to index before the document is changed. This
     * method then pushes the change data into a queue that will then be
     * picked up by the "change" event.
     *
     * @param editor
     *   The editor that the change originated from.
     * @param changeObj
     *   The object that specifies the change.
     *
     * @internal
     */

  }]);

  return EditorContentManager;
}();
/**
 * @internal
 */


exports.EditorContentManager = EditorContentManager;
EditorContentManager._DEFAULTS = {
  onInsert: function onInsert() {// no-op
  },
  onReplace: function onReplace() {// no-op
  },
  onDelete: function onDelete() {// no-op
  },
  remoteOrigin: "remote"
};