text: define([
    'jquery',
    'summernote/base/core/func',
    'summernote/base/core/list',
    'summernote/base/core/agent'
  ], function ($, func, list, agent) {
    var Buttons = function (context) {
      var self = this;
      var ui = $.summernote.ui;
  
      var $toolbar = context.layoutInfo.toolbar;
      var options = context.options;
      var lang = options.langInfo;
  
      var invertedKeyMap = func.invertObject(options.keyMap[agent.isMac ? 'mac' : 'pc']);
  
      var representShortcut = this.representShortcut = function (editorMethod) {
        var shortcut = invertedKeyMap[editorMethod];
        if (agent.isMac) {
          shortcut = shortcut.replace('CMD', '⌘').replace('SHIFT', '⇧');
        }
  
        shortcut = shortcut.replace('BACKSLASH', '\\')
                           .replace('SLASH', '/')
                           .replace('LEFTBRACKET', '[')
                           .replace('RIGHTBRACKET', ']');
  
        return ' (' + shortcut + ')';
      };
  
      this.initialize = function () {
        this.addToolbarButtons();
        this.addImagePopoverButtons();
        this.addLinkPopoverButtons();
      };
  
      this.addToolbarButtons = function () {
        context.memo('button.style', function () {
          return ui.buttonGroup([
            ui.button({
              className: 'dropdown-toggle',
              contents: ui.icon(options.icons.magic) + ' ' + ui.icon(options.icons.caret, 'span'),
              tooltip: lang.style.style,
              data: {
                toggle: 'dropdown'
              }
            }),
            ui.dropdown({
              className: 'dropdown-style',
              items: context.options.styleTags,
              click: context.createInvokeHandler('editor.formatBlock')
            })
          ]).render();
        });
  
        context.memo('button.bold', function () {
          return ui.button({
            className: 'note-btn-bold',
            contents: ui.icon(options.icons.bold),
            tooltip: lang.font.bold + representShortcut('bold'),
            click: context.createInvokeHandler('editor.bold')
          }).render();
        });
  
        context.memo('button.italic', function () {
          return ui.button({
            className: 'note-btn-italic',
            contents: ui.icon(options.icons.italic),
            tooltip: lang.font.italic + representShortcut('italic'),
            click: context.createInvokeHandler('editor.italic')
          }).render();
        });
  
        context.memo('button.underline', function () {
          return ui.button({
            className: 'note-btn-underline',
            contents: ui.icon(options.icons.underline),
            tooltip: lang.font.underline + representShortcut('underline'),
            click: context.createInvokeHandler('editor.underline')
          }).render();
        });
  
        context.memo('button.clear', function () {
          return ui.button({
            contents: ui.icon(options.icons.eraser),
            tooltip: lang.font.clear + representShortcut('removeFormat'),
            click: context.createInvokeHandler('editor.removeFormat')
          }).render();
        });
  
        context.memo('button.strikethrough', function () {
          return ui.button({
            contents: ui.icon(options.icons.strikethrough),
            tooltip: lang.font.strikethrough + representShortcut('strikethrough'),
            click: context.createInvokeHandler('editor.strikethrough')
          }).render();
        });
  
        context.memo('button.superscript', function () {
          return ui.button({
            contents: ui.icon(options.icons.superscript),
            tooltip: lang.font.superscript,
            click: context.createInvokeHandler('editor.superscript')
          }).render();
        });
  
        context.memo('button.subscript', function () {
          return ui.button({
            contents: ui.icon(options.icons.subscript),
            tooltip: lang.font.subscript,
            click: context.createInvokeHandler('editor.subscript')
          }).render();
        });
  
        context.memo('button.fontname', function () {
          return ui.buttonGroup([
            ui.button({
              className: 'dropdown-toggle',
              contents: '<span class="note-current-fontname"/> ' + ui.icon(options.icons.caret, 'span'),
              tooltip: lang.font.name,
              data: {
                toggle: 'dropdown'
              }
            }),
            ui.dropdownCheck({
              className: 'dropdown-fontname',
              checkClassName : options.icons.menuCheck,
              items: options.fontNames.filter(function (name) {
                return agent.isFontInstalled(name) ||
                  list.contains(options.fontNamesIgnoreCheck, name);
              }),
              click: context.createInvokeHandler('editor.fontName')
            })
          ]).render();
        });
  
        context.memo('button.fontsize', function () {
          return ui.buttonGroup([
            ui.button({
              className: 'dropdown-toggle',
              contents: '<span class="note-current-fontsize"/>' + ui.icon(options.icons.caret, 'span'),
              tooltip: lang.font.size,
              data: {
                toggle: 'dropdown'
              }
            }),
            ui.dropdownCheck({
              className: 'dropdown-fontsize',
              checkClassName : options.icons.menuCheck,
              items: options.fontSizes,
              click: context.createInvokeHandler('editor.fontSize')
            })
          ]).render();
        });
  
        context.memo('button.color', function () {
          return ui.buttonGroup({
            className: 'note-color',
            children: [
              ui.button({
                className : 'note-current-color-button',
                contents: ui.icon(options.icons.font + ' note-recent-color'),
                tooltip: lang.color.recent,
                click: context.createInvokeHandler('editor.color'),
                callback: function ($button) {
                  var $recentColor = $button.find('.note-recent-color');
                  $recentColor.css({
                    'background-color': 'yellow'
                  });
  
                  $button.data('value', {
                    backColor: 'yellow'
                  });
                }
              }),
              ui.button({
                className: 'dropdown-toggle',
                contents: ui.icon(options.icons.caret, 'span'),
                tooltip: lang.color.more,
                data: {
                  toggle: 'dropdown'
                }
              }),
              ui.dropdown({
                items: [
                  '<li>',
                  '<div class="btn-group">',
                  '  <div class="note-palette-title">' + lang.color.background + '</div>',
                  '  <div class="note-color-reset" data-event="backColor" data-value="inherit">' + lang.color.transparent + '</div>',
                  '  <div class="note-holder" data-event="backColor"/>',
                  '</div>',
                  '<div class="btn-group">',
                  '  <div class="note-palette-title">' + lang.color.foreground + '</div>',
                  '  <div class="note-color-reset" data-event="foreColor" data-value="inherit">' + lang.color.resetToDefault + '</div>',
                  '  <div class="note-holder" data-event="foreColor"/>',
                  '</div>',
                  '</li>'
                ].join(''),
                callback: function ($dropdown) {
                  $dropdown.find('.note-holder').each(function () {
                    var $holder = $(this);
                    $holder.append(ui.palette({
                      colors: options.colors,
                      eventName: $holder.data('event')
                    }).render());
                  });
                },
                click: function (event) {
                  var $button = $(event.target);
                  var eventName = $button.data('event');
                  var value = $button.data('value');
  
                  if (eventName && value) {
                    var key = eventName === 'backColor' ? 'background-color' : 'color';
                    var $color = $button.closest('.note-color').find('.note-recent-color');
                    var $currentButton = $button.closest('.note-color').find('.note-current-color-button');
  
                    var colorInfo = $currentButton.data('value');
                    colorInfo[eventName] = value;
                    $color.css(key, value);
                    $currentButton.data('value', colorInfo);
  
                    context.invoke('editor.' + eventName, value);
                  }
                }
              })
            ]
          }).render();
        });
  
        context.memo('button.ol',  function () {
          return ui.button({
            contents: ui.icon(options.icons.unorderedlist),
            tooltip: lang.lists.unordered + representShortcut('insertUnorderedList'),
            click: context.createInvokeHandler('editor.insertUnorderedList')
          }).render();
        });
  
        context.memo('button.ul', function () {
          return ui.button({
            contents: ui.icon(options.icons.orderedlist),
            tooltip: lang.lists.ordered + representShortcut('insertOrderedList'),
            click:  context.createInvokeHandler('editor.insertOrderedList')
          }).render();
        });
  
        context.memo('button.paragraph', function () {
          return ui.buttonGroup([
            ui.button({
              className: 'dropdown-toggle',
              contents: ui.icon(options.icons.align) + ' ' + ui.icon(options.icons.caret, 'span'),
              tooltip: lang.paragraph.paragraph,
              data: {
                toggle: 'dropdown'
              }
            }),
            ui.dropdown([
              ui.buttonGroup({
                className: 'note-align',
                children: [
                  ui.button({
                    contents: ui.icon(options.icons.alignLeft),
                    tooltip: lang.paragraph.left + representShortcut('justifyLeft'),
                    click: context.createInvokeHandler('editor.justifyLeft')
                  }),
                  ui.button({
                    contents: ui.icon(options.icons.alignCenter),
                    tooltip: lang.paragraph.center + representShortcut('justifyCenter'),
                    click: context.createInvokeHandler('editor.justifyCenter')
                  }),
                  ui.button({
                    contents: ui.icon(options.icons.alignRight),
                    tooltip: lang.paragraph.right + representShortcut('justifyRight'),
                    click: context.createInvokeHandler('editor.justifyRight')
                  }),
                  ui.button({
                    contents: ui.icon(options.icons.alignJustify),
                    tooltip: lang.paragraph.justify + representShortcut('justifyFull'),
                    click: context.createInvokeHandler('editor.justifyFull')
                  })
                ]
              }),
              ui.buttonGroup({
                className: 'note-list',
                children: [
                  ui.button({
                    contents: ui.icon(options.icons.outdent),
                    tooltip: lang.paragraph.outdent + representShortcut('outdent'),
                    click: context.createInvokeHandler('editor.outdent')
                  }),
                  ui.button({
                    contents: ui.icon(options.icons.indent),
                    tooltip: lang.paragraph.indent + representShortcut('indent'),
                    click: context.createInvokeHandler('editor.indent')
                  })
                ]
              })
            ])
          ]).render();
        });
  
        context.memo('button.height', function () {
          return ui.buttonGroup([
            ui.button({
              className: 'dropdown-toggle',
              contents: ui.icon(options.icons.textHeight) + ' ' + ui.icon(options.icons.caret, 'span'),
              tooltip: lang.font.height,
              data: {
                toggle: 'dropdown'
              }
            }),
            ui.dropdownCheck({
              items: options.lineHeights,
              checkClassName : options.icons.menuCheck,
              className: 'dropdown-line-height',
              click: context.createInvokeHandler('editor.lineHeight')
            })
          ]).render();
        });
  
        context.memo('button.table', function () {
          return ui.buttonGroup([
            ui.button({
              className: 'dropdown-toggle',
              contents: ui.icon(options.icons.table) + ' ' + ui.icon(options.icons.caret, 'span'),
              tooltip: lang.table.table,
              data: {
                toggle: 'dropdown'
              }
            }),
            ui.dropdown({
              className: 'note-table',
              items: [
                '<div class="note-dimension-picker">',
                '  <div class="note-dimension-picker-mousecatcher" data-event="insertTable" data-value="1x1"/>',
                '  <div class="note-dimension-picker-highlighted"/>',
                '  <div class="note-dimension-picker-unhighlighted"/>',
                '</div>',
                '<div class="note-dimension-display">1 x 1</div>'
              ].join('')
            })
          ], {
            callback: function ($node) {
              var $catcher = $node.find('.note-dimension-picker-mousecatcher');
              $catcher.css({
                width: options.insertTableMaxSize.col + 'em',
                height: options.insertTableMaxSize.row + 'em'
              }).mousedown(context.createInvokeHandler('editor.insertTable'))
                .on('mousemove', self.tableMoveHandler);
            }
          }).render();
        });
  
        context.memo('button.link', function () {
          return ui.button({
            contents: ui.icon(options.icons.link),
            tooltip: lang.link.link,
            click: context.createInvokeHandler('linkDialog.show')
          }).render();
        });
  
        context.memo('button.picture', function () {
          return ui.button({
            contents: ui.icon(options.icons.picture),
            tooltip: lang.image.image,
            click: context.createInvokeHandler('imageDialog.show')
          }).render();
        });
  
        context.memo('button.video', function () {
          return ui.button({
            contents: ui.icon(options.icons.video),
            tooltip: lang.video.video,
            click: context.createInvokeHandler('videoDialog.show')
          }).render();
        });
  
        context.memo('button.hr', function () {
          return ui.button({
            contents: ui.icon(options.icons.minus),
            tooltip: lang.hr.insert + representShortcut('insertHorizontalRule'),
            click: context.createInvokeHandler('editor.insertHorizontalRule')
          }).render();
        });
  
        context.memo('button.fullscreen', function () {
          return ui.button({
            className: 'btn-fullscreen',
            contents: ui.icon(options.icons.arrowsAlt),
            tooltip: lang.options.fullscreen,
            click: context.createInvokeHandler('fullscreen.toggle')
          }).render();
        });
  
        context.memo('button.codeview', function () {
          return ui.button({
            className: 'btn-codeview',
            contents: ui.icon(options.icons.code),
            tooltip: lang.options.codeview,
            click: context.createInvokeHandler('codeview.toggle')
          }).render();
        });
  
        context.memo('button.redo', function () {
          return ui.button({
            contents: ui.icon(options.icons.redo),
            tooltip: lang.history.redo + representShortcut('redo'),
            click: context.createInvokeHandler('editor.redo')
          }).render();
        });
  
        context.memo('button.undo', function () {
          return ui.button({
            contents: ui.icon(options.icons.undo),
            tooltip: lang.history.undo + representShortcut('undo'),
            click: context.createInvokeHandler('editor.undo')
          }).render();
        });
  
        context.memo('button.help', function () {
          return ui.button({
            contents: ui.icon(options.icons.question),
            tooltip: lang.options.help,
            click: context.createInvokeHandler('helpDialog.show')
          }).render();
        });
      };
  
      /**
       * image : [
       *   ['imagesize', ['imageSize100', 'imageSize50', 'imageSize25']],
       *   ['float', ['floatLeft', 'floatRight', 'floatNone' ]],
       *   ['remove', ['removeMedia']]
       * ],
       */
      this.addImagePopoverButtons = function () {
        // Image Size Buttons
        context.memo('button.imageSize100', function () {
          return ui.button({
            contents: '<span class="note-fontsize-10">100%</span>',
            tooltip: lang.image.resizeFull,
            click: context.createInvokeHandler('editor.resize', '1')
          }).render();
        });
        context.memo('button.imageSize50', function () {
          return  ui.button({
            contents: '<span class="note-fontsize-10">50%</span>',
            tooltip: lang.image.resizeHalf,
            click: context.createInvokeHandler('editor.resize', '0.5')
          }).render();
        });
        context.memo('button.imageSize25', function () {
          return ui.button({
            contents: '<span class="note-fontsize-10">25%</span>',
            tooltip: lang.image.resizeQuarter,
            click: context.createInvokeHandler('editor.resize', '0.25')
          }).render();
        });
  
        // Float Buttons
        context.memo('button.floatLeft', function () {
          return ui.button({
            contents: ui.icon(options.icons.alignLeft),
            tooltip: lang.image.floatLeft,
            click: context.createInvokeHandler('editor.floatMe', 'left')
          }).render();
        });
  
        context.memo('button.floatRight', function () {
          return ui.button({
            contents: ui.icon(options.icons.alignRight),
            tooltip: lang.image.floatRight,
            click: context.createInvokeHandler('editor.floatMe', 'right')
          }).render();
        });
  
        context.memo('button.floatNone', function () {
          return ui.button({
            contents: ui.icon(options.icons.alignJustify),
            tooltip: lang.image.floatNone,
            click: context.createInvokeHandler('editor.floatMe', 'none')
          }).render();
        });
  
        // Remove Buttons
        context.memo('button.removeMedia', function () {
          return ui.button({
            contents: ui.icon(options.icons.trash),
            tooltip: lang.image.remove,
            click: context.createInvokeHandler('editor.removeMedia')
          }).render();
        });
      };
  
      this.addLinkPopoverButtons = function () {
        context.memo('button.linkDialogShow', function () {
          return ui.button({
            contents: ui.icon(options.icons.link),
            tooltip: lang.link.edit,
            click: context.createInvokeHandler('linkDialog.show')
          }).render();
        });
  
        context.memo('button.unlink', function () {
          return ui.button({
            contents: ui.icon(options.icons.unlink),
            tooltip: lang.link.unlink,
            click: context.createInvokeHandler('editor.unlink')
          }).render();
        });
      };
  
      this.build = function ($container, groups) {
        for (var groupIdx = 0, groupLen = groups.length; groupIdx < groupLen; groupIdx++) {
          var group = groups[groupIdx];
          var groupName = group[0];
          var buttons = group[1];
  
          var $group = ui.buttonGroup({
            className: 'note-' + groupName
          }).render();
  
          for (var idx = 0, len = buttons.length; idx < len; idx++) {
            var button = context.memo('button.' + buttons[idx]);
            if (button) {
              $group.append(typeof button === 'function' ? button(context) : button);
            }
          }
          $group.appendTo($container);
        }
      };
  
      this.updateCurrentStyle = function () {
        var styleInfo = context.invoke('editor.currentStyle');
        this.updateBtnStates({
          '.note-btn-bold': function () {
            return styleInfo['font-bold'] === 'bold';
          },
          '.note-btn-italic': function () {
            return styleInfo['font-italic'] === 'italic';
          },
          '.note-btn-underline': function () {
            return styleInfo['font-underline'] === 'underline';
          }
        });
  
        if (styleInfo['font-family']) {
          var fontNames = styleInfo['font-family'].split(',').map(function (name) {
            return name.replace(/[\'\"]/g, '')
              .replace(/\s+$/, '')
              .replace(/^\s+/, '');
          });
          var fontName = list.find(fontNames, function (name) {
            return agent.isFontInstalled(name) ||
              list.contains(options.fontNamesIgnoreCheck, name);
          });
  
          $toolbar.find('.dropdown-fontname li a').each(function () {
            // always compare string to avoid creating another func.
            var isChecked = ($(this).data('value') + '') === (fontName + '');
            this.className = isChecked ? 'checked' : '';
          });
          $toolbar.find('.note-current-fontname').text(fontName);
        }
  
        if (styleInfo['font-size']) {
          var fontSize = styleInfo['font-size'];
          $toolbar.find('.dropdown-fontsize li a').each(function () {
            // always compare with string to avoid creating another func.
            var isChecked = ($(this).data('value') + '') === (fontSize + '');
            this.className = isChecked ? 'checked' : '';
          });
          $toolbar.find('.note-current-fontsize').text(fontSize);
        }
  
        if (styleInfo['line-height']) {
          var lineHeight = styleInfo['line-height'];
          $toolbar.find('.dropdown-line-height li a').each(function () {
            // always compare with string to avoid creating another func.
            var isChecked = ($(this).data('value') + '') === (lineHeight + '');
            this.className = isChecked ? 'checked' : '';
          });
        }
      };
  
      this.updateBtnStates = function (infos) {
        $.each(infos, function (selector, pred) {
          ui.toggleBtnActive($toolbar.find(selector), pred());
        });
      };
  
      this.tableMoveHandler = function (event) {
        var PX_PER_EM = 18;
        var $picker = $(event.target.parentNode); // target is mousecatcher
        var $dimensionDisplay = $picker.next();
        var $catcher = $picker.find('.note-dimension-picker-mousecatcher');
        var $highlighted = $picker.find('.note-dimension-picker-highlighted');
        var $unhighlighted = $picker.find('.note-dimension-picker-unhighlighted');
  
        var posOffset;
        // HTML5 with jQuery - e.offsetX is undefined in Firefox
        if (event.offsetX === undefined) {
          var posCatcher = $(event.target).offset();
          posOffset = {
            x: event.pageX - posCatcher.left,
            y: event.pageY - posCatcher.top
          };
        } else {
          posOffset = {
            x: event.offsetX,
            y: event.offsetY
          };
        }
  
        var dim = {
          c: Math.ceil(posOffset.x / PX_PER_EM) || 1,
          r: Math.ceil(posOffset.y / PX_PER_EM) || 1
        };
  
        $highlighted.css({ width: dim.c + 'em', height: dim.r + 'em' });
        $catcher.data('value', dim.c + 'x' + dim.r);
  
        if (3 < dim.c && dim.c < options.insertTableMaxSize.col) {
          $unhighlighted.css({ width: dim.c + 1 + 'em'});
        }
  
        if (3 < dim.r && dim.r < options.insertTableMaxSize.row) {
          $unhighlighted.css({ height: dim.r + 1 + 'em'});
        }
  
        $dimensionDisplay.html(dim.c + ' x ' + dim.r);
      };
    };
  
    return Buttons;
  });
  