"use strict";

var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");

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

var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));

var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass"));

var _get2 = _interopRequireDefault(require("@babel/runtime/helpers/get"));

var _inherits2 = _interopRequireDefault(require("@babel/runtime/helpers/inherits"));

var _possibleConstructorReturn2 = _interopRequireDefault(require("@babel/runtime/helpers/possibleConstructorReturn"));

var _getPrototypeOf2 = _interopRequireDefault(require("@babel/runtime/helpers/getPrototypeOf"));

var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));

var _tsFns = require("ts-fns");

function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = (0, _getPrototypeOf2.default)(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = (0, _getPrototypeOf2.default)(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return (0, _possibleConstructorReturn2.default)(this, result); }; }

function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); return true; } catch (e) { return false; } }

var EditorSymbol = Symbol('editof');

function edit(Constructor) {
  var Editor = /*#__PURE__*/function (_Constructor) {
    (0, _inherits2.default)(Editor, _Constructor);

    var _super = _createSuper(Editor);

    function Editor() {
      (0, _classCallCheck2.default)(this, Editor);
      return _super.apply(this, arguments);
    }

    (0, _createClass2.default)(Editor, [{
      key: "init",
      value: function init(data) {
        var _this = this;

        (0, _tsFns.define)(this, '$commits', {
          value: {},
          configurable: true
        });
        (0, _tsFns.define)(this, '$history', {
          value: [],
          configurable: true
        });
        (0, _tsFns.define)(this, '$cursor', {
          value: -1,
          writable: true,
          configurable: true
        });
        (0, _tsFns.define)(this, '$doing', {
          value: false,
          writable: true,
          configurable: true
        }); // receive (clone) another model

        if ((0, _tsFns.isInstanceOf)(data, Constructor)) {
          data = data.Chunk().toJSON();
          data = this.onEdit(data);
          (0, _get2.default)((0, _getPrototypeOf2.default)(Editor.prototype), "init", this).call(this);
          this.Chunk().fromJSON(data);
        } else {
          (0, _get2.default)((0, _getPrototypeOf2.default)(Editor.prototype), "init", this).call(this, data);
        } // record all changes in history


        this.watch('*', function (_ref) {
          var key = _ref.key,
              value = _ref.value;

          _this.$record({
            key: key,
            value: value
          });
        }, true);
      }
    }, {
      key: "restore",
      value: function restore(data) {
        (0, _get2.default)((0, _getPrototypeOf2.default)(Editor.prototype), "restore", this).call(this, data);
        this.clear(); // create a initialized mirror

        this.commit('$origin');
        return this;
      }
    }, {
      key: "undo",
      value: function undo() {
        if (!this.$store.editable) {
          return;
        }

        var cursor = this.$cursor - 1; // no history

        if (cursor < -1 || !this.$history.length) {
          return;
        } // from history to none


        if (cursor === -1) {
          var origin = this.$commits.$origin;
          var current = this.$history[0];
          var key = current.key,
              data = current.data;

          if (data) {
            this.$replay({
              data: origin
            });
          } else if (key) {
            var value = (0, _tsFns.parse)(origin, key);
            this.$replay({
              key: key,
              value: value
            });
          }
        } else {
          var history = this.$history[cursor];
          this.$replay(history);
        }

        this.$cursor = cursor;
        return this;
      }
    }, {
      key: "redo",
      value: function redo() {
        if (!this.$store.editable) {
          return;
        }

        var cursor = this.$cursor + 1;
        var max = this.$history.length - 1;

        if (cursor > max) {
          return;
        }

        var history = this.$history[cursor];
        this.$replay(history);
        this.$cursor = cursor;
        return this;
      }
    }, {
      key: "commit",
      value: function commit() {
        var tag = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '$origin';
        var data = this.Chunk().toJSON();
        this.$commits[tag] = data;
        return this;
      }
    }, {
      key: "rollback",
      value: function rollback() {
        var tag = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '$origin';

        if (!this.$store.editable) {
          return;
        }

        var data = this.$commits[tag];

        if (!data) {
          return;
        }

        this.$doing = true;
        this.Chunk().fromJSON(data);
        this.$doing = false;
        this.$record({
          tag: tag,
          data: data
        });
        return this;
      }
    }, {
      key: "$record",
      value: function $record(action) {
        if (this.$doing) {
          return;
        }

        var next = this.$cursor + 1;
        this.$history.length = next; // clear all items after cursor

        var time = Date.now();
        var tag = action.tag,
            data = action.data,
            key = action.key,
            value = action.value;

        if (tag) {
          this.$history.push({
            time: time,
            tag: tag,
            data: data
          });
        } else if (key) {
          this.$history.push({
            time: time,
            key: key,
            value: value
          });
        }

        this.$cursor = next; // move cursro to next (latest)
      }
    }, {
      key: "$replay",
      value: function $replay(history) {
        var key = history.key,
            value = history.value,
            data = history.data;
        this.$doing = true;

        if (data) {
          this.$store.update(data);
        } else {
          this.$store.set(key, value);
        }

        this.$doing = false;
      }
    }, {
      key: "clear",
      value: function clear() {
        this.$cursor = -1;
        this.$history.length = 0;
        this.$doing = false;
      }
    }, {
      key: "submit",
      value: function submit() {
        var editof = this.$$editof;
        var data = this.Chunk().toJSON();
        data = this.onSubmit(data);

        if (editof) {
          // here we should must ensure model is able to receive current editor's data
          editof.Chunk().fromJSON(data);
          return editof;
        }

        var _Editor = (0, _tsFns.getConstructorOf)(this);

        var Constructor = _Editor[EditorSymbol];

        if (Constructor) {
          return new Constructor(data);
        }

        throw new Error("[TySheMo]: without submit target");
      }
    }, {
      key: "onEdit",
      value: function onEdit(data) {
        return data;
      }
    }, {
      key: "onSubmit",
      value: function onSubmit(data) {
        return data;
      }
    }]);
    return Editor;
  }(Constructor);

  (0, _defineProperty2.default)(Editor, EditorSymbol, Constructor);
  return Editor;
}