text: define([
    'jquery',
    'summernote/base/core/func',
    'summernote/base/core/list',
    'summernote/base/core/dom'
  ], function ($, func, list, dom) {
    /**
     * @param {jQuery} $note
     * @param {Object} options
     * @return {Context}
     */
    var Context = function ($note, options) {
      var self = this;
  
      var ui = $.summernote.ui;
      this.memos = {};
      this.modules = {};
      this.layoutInfo = {};
      this.options = options;
  
      /**
       * create layout and initialize modules and other resources
       */
      this.initialize = function () {
        this.layoutInfo = ui.createLayout($note, options);
        this._initialize();
        $note.hide();
        return this;
      };
  
      /**
       * destroy modules and other resources and remove layout
       */
      this.destroy = function () {
        this._destroy();
        $note.removeData('summernote');
        ui.removeLayout($note, this.layoutInfo);
      };
  
      /**
       * destory modules and other resources and initialize it again
       */
      this.reset = function () {
        var disabled = self.isDisabled();
        this.code(dom.emptyPara);
        this._destroy();
        this._initialize();
  
        if (disabled) {
          self.disable();
        }
      };
  
      this._initialize = function () {
        // add optional buttons
        var buttons = $.extend({}, this.options.buttons);
        Object.keys(buttons).forEach(function (key) {
          self.memo('button.' + key, buttons[key]);
        });
  
        var modules = $.extend({}, this.options.modules, $.summernote.plugins || {});
  
        // add and initialize modules
        Object.keys(modules).forEach(function (key) {
          self.module(key, modules[key], true);
        });
  
        Object.keys(this.modules).forEach(function (key) {
          self.initializeModule(key);
        });
      };
  
      this._destroy = function () {
        // destroy modules with reversed order
        Object.keys(this.modules).reverse().forEach(function (key) {
          self.removeModule(key);
        });
  
        Object.keys(this.memos).forEach(function (key) {
          self.removeMemo(key);
        });
        // trigger custom onDestroy callback
        this.triggerEvent('destroy', this);
      };
  
      this.code = function (html) {
        var isActivated = this.invoke('codeview.isActivated');
  
        if (html === undefined) {
          this.invoke('codeview.sync');
          return isActivated ? this.layoutInfo.codable.val() : this.layoutInfo.editable.html();
        } else {
          if (isActivated) {
            this.layoutInfo.codable.val(html);
          } else {
            this.layoutInfo.editable.html(html);
          }
          $note.val(html);
          this.triggerEvent('change', html);
        }
      };
  
      this.isDisabled = function () {
        return this.layoutInfo.editable.attr('contenteditable') === 'false';
      };
  
      this.enable = function () {
        this.layoutInfo.editable.attr('contenteditable', true);
        this.invoke('toolbar.activate', true);
      };
  
      this.disable = function () {
        // close codeview if codeview is opend
        if (this.invoke('codeview.isActivated')) {
          this.invoke('codeview.deactivate');
        }
        this.layoutInfo.editable.attr('contenteditable', false);
        this.invoke('toolbar.deactivate', true);
      };
  
      this.triggerEvent = function () {
        var namespace = list.head(arguments);
        var args = list.tail(list.from(arguments));
  
        var callback = this.options.callbacks[func.namespaceToCamel(namespace, 'on')];
        if (callback) {
          callback.apply($note[0], args);
        }
        $note.trigger('summernote.' + namespace, args);
      };
  
      this.initializeModule = function (key) {
        var module = this.modules[key];
        module.shouldInitialize = module.shouldInitialize || func.ok;
        if (!module.shouldInitialize()) {
          return;
        }
  
        // initialize module
        if (module.initialize) {
          module.initialize();
        }
  
        // attach events
        if (module.events) {
          dom.attachEvents($note, module.events);
        }
      };
  
      this.module = function (key, ModuleClass, withoutIntialize) {
        if (arguments.length === 1) {
          return this.modules[key];
        }
  
        this.modules[key] = new ModuleClass(this);
  
        if (!withoutIntialize) {
          this.initializeModule(key);
        }
      };
  
      this.removeModule = function (key) {
        var module = this.modules[key];
        if (module.shouldInitialize()) {
          if (module.events) {
            dom.detachEvents($note, module.events);
          }
  
          if (module.destroy) {
            module.destroy();
          }
        }
  
        delete this.modules[key];
      };
  
      this.memo = function (key, obj) {
        if (arguments.length === 1) {
          return this.memos[key];
        }
        this.memos[key] = obj;
      };
  
      this.removeMemo = function (key) {
        if (this.memos[key] && this.memos[key].destroy) {
          this.memos[key].destroy();
        }
  
        delete this.memos[key];
      };
  
      this.createInvokeHandler = function (namespace, value) {
        return function (event) {
          event.preventDefault();
          self.invoke(namespace, value || $(event.target).closest('[data-value]').data('value'));
        };
      };
  
      this.invoke = function () {
        var namespace = list.head(arguments);
        var args = list.tail(list.from(arguments));
  
        var splits = namespace.split('.');
        var hasSeparator = splits.length > 1;
        var moduleName = hasSeparator && list.head(splits);
        var methodName = hasSeparator ? list.last(splits) : list.head(splits);
  
        var module = this.modules[moduleName || 'editor'];
        if (!moduleName && this[methodName]) {
          return this[methodName].apply(this, args);
        } else if (module && module[methodName] && module.shouldInitialize()) {
          return module[methodName].apply(module, args);
        }
      };
  
      return this.initialize();
    };
  
    return Context;
  });
  