define([

  'summernote/core/agent',

  'summernote/core/list',

  'summernote/core/dom',

  'summernote/core/range',

  'summernote/defaults',

  'summernote/EventHandler',

  'summernote/Renderer'

], function (agent, list, dom, range,

             defaults, EventHandler, Renderer) {



  // jQuery namespace for summernote

  /**

   * @class $.summernote 

   * 

   * summernote attribute  

   * 

   * @mixin defaults

   * @singleton  

   * 

   */

  $.summernote = $.summernote || {};



  // extends default settings

  //  - $.summernote.version

  //  - $.summernote.options

  //  - $.summernote.lang

  $.extend($.summernote, defaults);



  var renderer = new Renderer();

  var eventHandler = new EventHandler();



  $.extend($.summernote, {

    /** @property {Renderer} */

    renderer: renderer,

    /** @property {EventHandler} */

    eventHandler: eventHandler,

    /** 

     * @property {Object} core 

     * @property {core.agent} core.agent 

     * @property {core.dom} core.dom

     * @property {core.range} core.range 

     */

    core: {

      agent: agent,

      list : list,

      dom: dom,

      range: range

    },

    /** 

     * @property {Object} 

     * pluginEvents event list for plugins

     * event has name and callback function.

     * 

     * ``` 

     * $.summernote.addPlugin({

     *     events : {

     *          'hello' : function(layoutInfo, value, $target) {

     *              console.log('event name is hello, value is ' + value );

     *          }

     *     }     

     * })

     * ```

     * 

     * * event name is data-event property.

     * * layoutInfo is a summernote layout information.

     * * value is data-value property.

     */

    pluginEvents: {},



    plugins : []

  });



  /**

   * @method addPlugin

   *

   * add Plugin in Summernote 

   * 

   * Summernote can make a own plugin.

   *

   * ### Define plugin

   * ```

   * // get template function  

   * var tmpl = $.summernote.renderer.getTemplate();

   * 

   * // add a button   

   * $.summernote.addPlugin({

   *     buttons : {

   *        // "hello"  is button's namespace.      

   *        "hello" : function(lang, options) {

   *            // make icon button by template function          

   *            return tmpl.iconButton(options.iconPrefix + 'header', {

   *                // callback function name when button clicked 

   *                event : 'hello',

   *                // set data-value property                 

   *                value : 'hello',                

   *                hide : true

   *            });           

   *        }

   *     

   *     }, 

   *     

   *     events : {

   *        "hello" : function(layoutInfo, value) {

   *            // here is event code 

   *        }

   *     }     

   * });

   * ``` 

   * ### Use a plugin in toolbar

   * 

   * ``` 

   *    $("#editor").summernote({

   *    ...

   *    toolbar : [

   *        // display hello plugin in toolbar     

   *        ['group', [ 'hello' ]]

   *    ]

   *    ...    

   *    });

   * ```

   *  

   *  

   * @param {Object} plugin

   * @param {Object} [plugin.buttons] define plugin button. for detail, see to Renderer.addButtonInfo

   * @param {Object} [plugin.dialogs] define plugin dialog. for detail, see to Renderer.addDialogInfo

   * @param {Object} [plugin.events] add event in $.summernote.pluginEvents 

   * @param {Object} [plugin.langs] update $.summernote.lang

   * @param {Object} [plugin.options] update $.summernote.options

   */

  $.summernote.addPlugin = function (plugin) {



    // save plugin list

    $.summernote.plugins.push(plugin);



    if (plugin.buttons) {

      $.each(plugin.buttons, function (name, button) {

        renderer.addButtonInfo(name, button);

      });

    }



    if (plugin.dialogs) {

      $.each(plugin.dialogs, function (name, dialog) {

        renderer.addDialogInfo(name, dialog);

      });

    }



    if (plugin.events) {

      $.each(plugin.events, function (name, event) {

        $.summernote.pluginEvents[name] = event;

      });

    }



    if (plugin.langs) {

      $.each(plugin.langs, function (locale, lang) {

        if ($.summernote.lang[locale]) {

          $.extend($.summernote.lang[locale], lang);

        }

      });

    }



    if (plugin.options) {

      $.extend($.summernote.options, plugin.options);

    }

  };



  /*

   * extend $.fn

   */

  $.fn.extend({

    /**

     * @method

     * Initialize summernote

     *  - create editor layout and attach Mouse and keyboard events.

     * 

     * ```

     * $("#summernote").summernote( { options ..} );

     * ```

     *   

     * @member $.fn

     * @param {Object|String} options reference to $.summernote.options

     * @return {this}

     */

    summernote: function () {

      // check first argument's type

      //  - {String}: External API call {{module}}.{{method}}

      //  - {Object}: init options

      var type = $.type(list.head(arguments));

      var isExternalAPICalled = type === 'string';

      var hasInitOptions = type === 'object';



      // extend default options with custom user options

      var options = hasInitOptions ? list.head(arguments) : {};



      options = $.extend({}, $.summernote.options, options);

      options.icons = $.extend({}, $.summernote.options.icons, options.icons);



      // Include langInfo in options for later use, e.g. for image drag-n-drop

      // Setup language info with en-US as default

      options.langInfo = $.extend(true, {}, $.summernote.lang['en-US'], $.summernote.lang[options.lang]);



      // override plugin options

      if (!isExternalAPICalled && hasInitOptions) {

        for (var i = 0, len = $.summernote.plugins.length; i < len; i++) {

          var plugin = $.summernote.plugins[i];



          if (options.plugin[plugin.name]) {

            $.summernote.plugins[i] = $.extend(true, plugin, options.plugin[plugin.name]);

          }

        }

      }



      this.each(function (idx, holder) {

        var $holder = $(holder);



        // if layout isn't created yet, createLayout and attach events

        if (!renderer.hasNoteEditor($holder)) {

          renderer.createLayout($holder, options);



          var layoutInfo = renderer.layoutInfoFromHolder($holder);

          $holder.data('layoutInfo', layoutInfo);



          eventHandler.attach(layoutInfo, options);

          eventHandler.attachCustomEvent(layoutInfo, options);

        }

      });



      var $first = this.first();

      if ($first.length) {

        var layoutInfo = renderer.layoutInfoFromHolder($first);



        // external API

        if (isExternalAPICalled) {

          var moduleAndMethod = list.head(list.from(arguments));

          var args = list.tail(list.from(arguments));



          // TODO now external API only works for editor

          var params = [moduleAndMethod, layoutInfo.editable()].concat(args);

          return eventHandler.invoke.apply(eventHandler, params);

        } else if (options.focus) {

          // focus on first editable element for initialize editor

          layoutInfo.editable().focus();

        }

      }



      return this;

    },



    /**

     * @method 

     * 

     * get the HTML contents of note or set the HTML contents of note.

     *

     * * get contents 

     * ```

     * var content = $("#summernote").code();

     * ```

     * * set contents 

     *

     * ```

     * $("#summernote").code(html);

     * ```

     *

     * @member $.fn 

     * @param {String} [html] - HTML contents(optional, set)

     * @return {this|String} - context(set) or HTML contents of note(get).

     */

    code: function (html) {

      // get the HTML contents of note

      if (html === undefined) {

        var $holder = this.first();

        if (!$holder.length) {

          return;

        }



        var layoutInfo = renderer.layoutInfoFromHolder($holder);

        var $editable = layoutInfo && layoutInfo.editable();



        if ($editable && $editable.length) {

          var isCodeview = eventHandler.invoke('codeview.isActivated', layoutInfo);

          eventHandler.invoke('codeview.sync', layoutInfo);

          return isCodeview ? layoutInfo.codable().val() :

                              layoutInfo.editable().html();

        }

        return dom.value($holder);

      }



      // set the HTML contents of note

      this.each(function (i, holder) {

        var layoutInfo = renderer.layoutInfoFromHolder($(holder));

        var $editable = layoutInfo && layoutInfo.editable();

        if ($editable) {

          $editable.html(html);

        }

      });



      return this;

    },



    /**

     * @method

     * 

     * destroy Editor Layout and detach Key and Mouse Event

     *

     * @member $.fn

     * @return {this}

     */

    destroy: function () {

      this.each(function (idx, holder) {

        var $holder = $(holder);



        if (!renderer.hasNoteEditor($holder)) {

          return;

        }



        var info = renderer.layoutInfoFromHolder($holder);

        var options = info.editor().data('options');



        eventHandler.detach(info, options);

        renderer.removeLayout($holder, info, options);

      });



      return this;

    }

  });

});

