(function($) {

var BUE = window.BUE = window.BUE || {preset: {}, templates: {}, instances: [], preprocess: {}, postprocess: {}};

// Get editor settings from Drupal.settings and process preset textareas.
BUE.behavior = function(context) {
  var set = Drupal.settings.BUE || null, tpls = BUE.templates, pset = BUE.preset;
  if (set) {
    $.each(set.templates, function (id, tpl) {
      tpls[id] = tpls[id] || $.extend({}, tpl);
    });
    $.extend(pset, set.preset);
    set.templates = {};
    set.preset = {};
  }
  $.each(pset, function (tid, tplid) {
    BUE.processTextarea($('#'+ tid, context).get(0), tplid);
  });
  // Fix enter key on textfields triggering button click.
  $('input:text', context).bind('keydown.bue', BUE.eFixEnter);
};

// Integrate editor template into textarea T
BUE.processTextarea = function (T, tplid) {
  if (!T || !BUE.templates[tplid] || !(T = $(T).filter('textarea')[0])) return false;
  // Check visibility on the element-level only.
  if (T.style.display == 'none' || T.style.visibility == 'hidden') return false;
  if (T.bue) return T.bue;
  var E = new BUE.instance(T, tplid);
  !BUE.active || BUE.active.textArea.disabled ? E.activate() : E.accesskeys(false);
  // Pre&post process.
  for (var i in BUE.preprocess) BUE.preprocess[i](E, $);
  for (var i in BUE.postprocess) BUE.postprocess[i](E, $);
  return E;
};

// Create an editor instance
BUE.instance = function (T, tplid) {
  var i = BUE.instances.length, E = T.bue = BUE.instances[i] = this;
  E.index = i;
  E.textArea = T;
  E.tplid = tplid;
  E.tpl = BUE.templates[tplid];
  E.bindex = null;
  E.safeToPreview = T.value.indexOf('<') == -1;
  E.UI = BUE.$html(BUE.theme(tplid).replace(/\%n/g, i)).insertBefore(T).bind('keydown.bue', BUE.eUIKeydown);
  E.buttons = $('.bue-button', E.UI).each(function(i, B) {
    var arr = B.id.split('-');
    $($.extend(B, {eindex: arr[1], bid: arr[3], bindex: i})).bind('click.bue', BUE.eButtonClick);
  }).get();
  $(T).bind('focus.bue', BUE.eTextareaFocus);
};

// Execute button's click event
BUE.buttonClick = function (eindex, bindex) { try {
  var E = BUE.instances[eindex].activate();
  var domB = E.buttons[bindex];
  var tplB = E.tpl.buttons[domB.bid];
  var content = tplB[1];
  E.bindex = bindex;
  E.dialog.close();
  if (tplB[4]) {
    tplB[4](E, $);
  }
  else if (content) {
    var arr = content.split('%TEXT%');
    if (arr.length == 2) E.tagSelection(arr[0], arr[1]);
    else E.replaceSelection(arr.length == 1 ? content : arr.join(E.getSelection()), 'end');
  }
  !(domB.pops || domB.stayClicked) && E.focus();
  } catch (e) {alert(e.name +': '+ e.message);}
  return false;
};

// Return html for editor templates.
BUE.theme = function (tplid) {
  var tpl = BUE.templates[tplid] || {html: ''}, html = '', sprite;
  if (typeof tpl.html == 'string') return tpl.html;
  // Load sprite
  if (sprite = tpl.sprite) {
    var surl = (new Image()).src = sprite.url, sunit = sprite.unit, sx1 = sprite.x1;
    $(document.body).append('<style type="text/css" media="all">.bue-'+ tplid +' .bue-sprite-button {background-image: url('+ surl +'); width: '+ sunit +'px; height: '+ sunit +'px;}</style>');
  }
  var access = $.browser.mozilla && 'Shift + Alt' || ($.browser.msie || window.chrome) && 'Alt', title, content, icon, key, func;
  // Create html for buttons. B(0-title, 1-content, 2-icon or caption, 3-accesskey) and 4-function for js buttons
  for (var B, isimg, src, type, btype, attr, alt, i = 0, s = 0; B = tpl.buttons[i]; i++) {
    // Empty button.
    if (B.length == 0) {
      s++;
      continue;
    }
    title = B[0], content = B[1], icon = B[2], key = B[3], func = null;
    // Set button function
    if (content.substr(0, 3) == 'js:') {
      func = B[4] = new Function('E', '$', content.substr(3));
    }
    isimg = (/\.(png|gif|jpg)$/i).test(icon);
    // Theme button.
    if (title.substr(0, 4) == 'tpl:') {
      html += func ? (func(null, $) || '') : content;
      html += icon ? ('<span class="separator">'+ (isimg ? '<img src="'+ tpl.iconpath +'/'+ icon +'" />' : icon) +'</span>') : '';
      continue;
    }
    // Text button
    if (!isimg) {
      type = 'button', btype = 'text', attr = 'value="'+ icon +'"';
    }
    else {
      type = 'image';
      // Sprite button
      if (sprite) {
        btype = 'sprite', attr = 'src="'+ sx1 +'" style="background-position: -'+ (s * sunit) +'px 0;"';
        s++;
      }
      // Image button
      else {
        btype = 'image', attr = 'src="'+ tpl.iconpath +'/'+ icon +'"';
      }
    }
    alt = title + (key ? '('+ key +')' : '');
    title += access && key ? ' ('+ access +' + '+ key +')' : '';
    html += '<input type="'+ type +'" alt="'+ alt +'" title="'+ title +'" accesskey="'+ key +'" id="bue-%n-button-'+ i +'" class="bue-button bue-'+ btype +'-button editor-'+ btype +'-button" '+ attr +' tabindex="'+ (i ? -1 : 0) +'" />';
  }
  return tpl.html = '<div class="bue-ui bue-'+ tplid +' editor-container clear-block" id="bue-ui-%n" role="toolbar">'+ html +'</div>';
};

// Cross browser selection handling. 0-1=All, 2=IE, 3=Opera
BUE.mode = (window.getSelection || document.getSelection) ? ($.browser.opera ? 3 : 1) : (document.selection && document.selection.createRange ? 2 : 0 );

// New line standardization. At least make them represented by a single char.
BUE.text = BUE.processText = BUE.mode < 2 ? function (s) {return s.toString()} : function (s) {return s.toString().replace(/\r\n/g, '\n')};

// Create selection in a textarea
BUE.selMake = BUE.mode == 2 ? function (T, start, end) {
  range = T.createTextRange();
  range.collapse();
  range.moveEnd('character', end);
  range.moveStart('character', start);
  range.select();
} :
BUE.mode == 3 ? function (T, start, end) {
  var text = BUE.text(T.value), i = text.substring(0, start).split('\n').length, j = text.substring(start, end).split('\n').length;
  T.setSelectionRange(start + i -1 , end + i + j - 2);
} :
function (T, start, end) {
  T.setSelectionRange(start, end);
};

// Return the selection coordinates in a textarea
BUE.selPos = BUE.mode == 2 ? function (T) {
  T.focus();
  var orange = document.selection.createRange(), range = orange.duplicate();
  range.moveToElementText(T);
  range.setEndPoint('EndToEnd', orange);
  var otext = orange.text, olen = otext.length, prelen = range.text.length - olen;
  var start = prelen - (T.value.substr(0, prelen).split('\r\n').length - 1);
  start && range.moveStart('character', start);
  for (; range.compareEndPoints('StartToStart', orange) < 0; start++) {
    range.moveStart('character', 1);
  }
  var end = start + olen - (otext.split('\r\n').length - 1);
  for (; range.compareEndPoints('EndToStart', orange) > 0; end++) {
    range.moveEnd('character', -1);
    if (range.text.length != olen) break;
  }
  return {start: start, end: end};
} :
BUE.mode == 3 ? function (T) {
  var start = T.selectionStart || 0, end = T.selectionEnd || 0, val = T.value;
  var i = val.substring(0, start).split('\r\n').length, j = val.substring(start, end).split('\r\n').length;
  return {start: start - i + 1, end: end - i - j + 2};
} :
function (T) {
  return {start: T.selectionStart || 0, end: T.selectionEnd || 0}
};

// Enter key fixer for text fields
BUE.eFixEnter = function(e) {
  e.keyCode == 13 && (BUE.enterKeyTime = new Date());
};

// Button click handler
BUE.eButtonClick = function(e) {
  return !(BUE.enterKeyTime && new Date() - BUE.enterKeyTime < 500) && BUE.buttonClick(this.eindex, this.bindex);
};

// Textarea focus handler
BUE.eTextareaFocus = function(e) {
  this.bue && !this.bue.dialog.esp && this.bue.activate();
};

// UI keydown handler
BUE.eUIKeydown = function(e) {
  if (e.keyCode != 37 && e.keyCode != 39) return;
  var len, E = BUE.instances[this.id.split('-').pop()];
  if (E && (len = E.buttons.length)) {
    var A = document.activeElement, i = Math.max(-1, (A && A.eindex == E.index ? A.bindex : -1) + e.keyCode - 38) + len;
    E.buttons[i % len].focus();
  }
};

// Html 2 jquery. Faster than $(html)
BUE.$html = function(s){
  return $(document.createElement('div')).html(s).children();
};

// Backward compatibility.
window.editor = window.editor || BUE;

// Initiate bueditor
$(document).ready(function () {
  (Drupal.behaviors.BUE = BUE.behavior)(document);
});

})(jQuery);


// Bueditor instance methods
(function(E) {

// Focus on editor textarea.
E.focus = function () {
  this.textArea.focus();
  return this;
};

// Return textarea content
E.getContent = function () {
  return BUE.text(this.textArea.value);
};

// Set textarea content
E.setContent = function (content) {
  var T = this.textArea, st = T.scrollTop;
  T.value = content;
  T.scrollTop = st;
  return this;
};

// Return selected text
E.getSelection = function () {
  var pos = this.posSelection();
  return this.getContent().substring(pos.start, pos.end);
};

// Replace selected text
E.replaceSelection = function (txt, cursor) {
  var E = this, pos = E.posSelection(), content = E.getContent(), txt = BUE.text(txt);
  var end = cursor == 'start' ? pos.start : pos.start+txt.length, start = cursor == 'end' ? end : pos.start;
  E.setContent(content.substr(0, pos.start) + txt + content.substr(pos.end));
  return E.makeSelection(start, end);
};

// Wrap selected text.
E.tagSelection = function (left, right, cursor) {
  var E = this, pos = E.posSelection(), content = E.getContent();
  var left = BUE.text(left), right = BUE.text(right), llen = left.length;
  var end = cursor == 'start' ? pos.start+llen : pos.end+llen, start = cursor == 'end' ? end : pos.start+llen;
  E.setContent(content.substr(0, pos.start) + left + content.substring(pos.start, pos.end) + right + content.substr(pos.end));
  return E.makeSelection(start, end);
};

// Make a new selection
E.makeSelection = function (start, end) {
  var E = this;
  if (end === undefined || end < start) end = start;
  BUE.selMake(E.textArea, start, end);
  E.dialog.esp && (E.dialog.esp = {start: start, end: end}) || E.focus();
  return E;
};

// Return selection coordinates.
E.posSelection = function () {
  return this.dialog.esp || BUE.selPos(this.textArea);
};

// Enable/disable editor buttons
E.buttonsDisabled = function (state, bindex) {
  for (var B, i=0; B = this.buttons[i]; i++) {
    B.disabled = i == bindex ? !state : state;
  }
  return this;
};

// Make active/custom button stay clicked
E.stayClicked = function (state, bindex) {
  var B = this.buttons[bindex === undefined ? this.bindex : bindex];
  B && jQuery(B)[state ? 'addClass' : 'removeClass']('stay-clicked') && (B.stayClicked = state || false);
  return this;
};

// Enable/disable button accesskeys
E.accesskeys = function (state) {
  for (var B, i=0; B = this.buttons[i]; i++) {
    B.accessKey = state ? this.tpl.buttons[B.bid][3] : '';
  }
  return this;
};

// Activate editor and make it BUE.active
E.activate = function() {
  var E = this, A = BUE.active || null;
  if (E == A) return E;
  A && A.accesskeys(false) && E.accesskeys(true);
  return BUE.active = E;
};

// Reserve dialog and quickPop
var pop = E.dialog = E.quickPop = BUE.dialog = BUE.quickPop = {};
pop.open = pop.close = function(){};

})(BUE.instance.prototype);
