// ----------------------------------------------------------------------------
// markItUp! Universal MarkUp Engine, JQuery plugin
// v 1.1.5
// Dual licensed under the MIT and GPL licenses.
// ----------------------------------------------------------------------------
// Copyright (C) 2007-2008 Jay Salvat
// http://markitup.jaysalvat.com/
// ----------------------------------------------------------------------------
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
// 
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
// ----------------------------------------------------------------------------
(function($) {
   $.fn.markItUp = function(settings,extraSettings) {
      var options, ctrlKey, shiftKey, altKey;
      ctrlKey = shiftKey = altKey = false;

      options = {   id:                  '',
         nameSpace:            '',
         root:               '',
         previewInWindow:      '', // 'width=800, height=600, resizable=yes, scrollbars=yes'
         previewAutoRefresh:      true,
         previewPosition:      'after',
         previewTemplatePath:   '~/templates/preview.html',
         previewParserPath:      '',
         previewParserVar:      'data',
         resizeHandle:         true,
         beforeInsert:         '',
         afterInsert:         '',
         onEnter:            {},
         onShiftEnter:         {},
         onCtrlEnter:         {},
         onTab:               {},
         markupSet:         [
            {
               /* set */
            }
         ]
      };
      $.extend(options,settings,extraSettings);

      // compute markItUp! path
      if(!options.root) {
         $('script').each(function(a,tag) {
            miuScript = $(tag).get(0).src.match(/(.*)jquery\.markitup(\.pack)?\.js$/);
            if(miuScript !== null) {
               options.root = miuScript[1];
            }
         });
      }

      return this.each(function() {
         var $$, textarea, levels, scrollPosition, caretPosition, caretOffset,
                 clicked, hash, header, footer, previewWindow, template, iFrame, abort;
         $$ = $(this);
         textarea = this;
         levels = [];
         abort = false;
         scrollPosition = caretPosition = 0;
         caretOffset = -1;

         options.previewParserPath = localize(options.previewParserPath);
         options.previewTemplatePath = localize(options.previewTemplatePath);

         // apply the computed path to ~/
         function localize(data,inText) {
            if(inText) {
               return    data.replace(/("|')~\//g,"$1" + options.root);
            }
            return    data.replace(/^~\//,options.root);
         }

         // init and build editor
         function init() {
            id = '';
            nameSpace = '';
            if(options.id) {
               id = 'id="' + options.id + '"';
            } else if($$.attr("id")) {
               id = 'id="markItUp' + ($$.attr("id").substr(0,1).toUpperCase()) + ($$.attr("id").substr(1)) + '"';

            }
            if(options.nameSpace) {
               nameSpace = 'class="' + options.nameSpace + '"';
            }
            $$.wrap('<div ' + nameSpace + '></div>');
            $$.wrap('<div ' + id + ' class="markItUp"></div>');
            $$.wrap('<div class="markItUpContainer"></div>');
            $$.addClass("markItUpEditor");

            // add the header before the textarea
            header = $('<div class="markItUpHeader"></div>').insertBefore($$);
            $(dropMenus(options.markupSet)).appendTo(header);

            // add the footer after the textarea
            footer = $('<div class="markItUpFooter"></div>').insertAfter($$);

            // add the resize handle after textarea
            if(options.resizeHandle === true && $.browser.safari !== true) {
               resizeHandle = $('<div class="markItUpResizeHandle"></div>')
                       .insertAfter($$)
                       .bind("mousedown",function(e) {
                  var h = $$.height(), y = e.clientY, mouseMove, mouseUp;
                  mouseMove = function(e) {
                     $$.css("height",Math.max(20,e.clientY + h - y) + "px");
                     return false;
                  };
                  mouseUp = function(e) {
                     $("html").unbind("mousemove",mouseMove).unbind("mouseup",mouseUp);
                     return false;
                  };
                  $("html").bind("mousemove",mouseMove).bind("mouseup",mouseUp);
               });
               footer.append(resizeHandle);
            }

            // listen key events
            $$.keydown(keyPressed).keyup(keyPressed);

            // bind an event to catch external calls
            $$.bind("insertion",function(e,settings) {
               if(settings.target !== false) {
                  get();
               }
               if(textarea === $.markItUp.focused) {
                  markup(settings);
               }
            });

            // remember the last focus
            $$.focus(function() {
               $.markItUp.focused = this;
            });
         }

         // recursively build header with dropMenus from markupset
         function dropMenus(markupSet) {
            var ul = $('<ul></ul>'), i = 0;
            $('li:hover > ul',ul).css('display','block');
            $.each(markupSet,function() {
               var button = this, t = '', title, li, j;
               title = (button.key) ? (button.name || '') + ' [Ctrl+' + button.key + ']' : (button.name || '');
               key = (button.key) ? 'accesskey="' + button.key + '"' : '';
               if(button.separator) {
                  li = $('<li class="markItUpSeparator">' + (button.separator || '') + '</li>').appendTo(ul);
               } else {
                  i++;
                  for(j = levels.length - 1; j >= 0; j--) {
                     t += levels[j] + "-";
                  }
                  li = $('<li class="markItUpButton markItUpButton' + t + (i) + ' ' + (button.className || '')
                          + '"><a href="" ' + key + ' title="' + title + '">' + (button.name || '') + '</a></li>')
                          .bind("contextmenu",function() { // prevent contextmenu on mac and allow ctrl+click
                     return false;
                  }).click(function() {
                     return false;
                  }).mouseup(function() {
                     if(button.call) {
                        eval(button.call)();
                     }
                     markup(button);
                     return false;
                  }).hover(function() {
                     $('> ul',this).show();
                     $(document).one('click',function() { // close dropmenu if click outside
                        $('ul ul',header).hide();
                     }
                             );
                  },function() {
                     $('> ul',this).hide();
                  }
                          ).appendTo(ul);
                  if(button.dropMenu) {
                     levels.push(i);
                     $(li).addClass('markItUpDropMenu').append(dropMenus(button.dropMenu));
                  }
               }
            });
            levels.pop();
            return ul;
         }

         // markItUp! markups
         function magicMarkups(string) {
            if(string) {
               string = string.toString();
               string = string.replace(/\(\!\(([\s\S]*?)\)\!\)/g,
                       function(x,a) {
                          var b = a.split('|!|');
                          if(altKey === true) {
                             return (b[1] !== undefined) ? b[1] : b[0];
                          } else {
                             return (b[1] === undefined) ? "" : b[0];
                          }
                       }
                       );
               // [![prompt]!], [![prompt:!:value]!]
               string = string.replace(/\[\!\[([\s\S]*?)\]\!\]/g,
                       function(x,a) {
                          var b = a.split(':!:');
                          if(abort === true) {
                             return false;
                          }
                          value = prompt(b[0],(b[1]) ? b[1] : '');
                          if(value === null) {
                             abort = true;
                          }
                          return value;
                       }
                       );
               return string;
            }
            return "";
         }

         // prepare action
         function prepare(action) {
            if($.isFunction(action)) {
               action = action(hash);
            }
            return magicMarkups(action);
         }

         // build block to insert
         function build(string) {
            openWith = prepare(clicked.openWith);
            placeHolder = prepare(clicked.placeHolder);
            replaceWith = prepare(clicked.replaceWith);
            closeWith = prepare(clicked.closeWith);
            if(replaceWith !== "") {
               block = openWith + replaceWith + closeWith;
            } else if(selection === '' && placeHolder !== '') {
               block = openWith + placeHolder + closeWith;
            } else {
               block = openWith + (string || selection) + closeWith;
            }
            return {   block:block,
               openWith:openWith,
               replaceWith:replaceWith,
               placeHolder:placeHolder,
               closeWith:closeWith
            };
         }

         // define markup to insert
         function markup(button) {
            var len, j, n, i;
            hash = clicked = button;
            get();

            $.extend(hash,{   line:"",
               root:options.root,
               textarea:textarea,
               selection:(selection || ''),
               caretPosition:caretPosition,
               ctrlKey:ctrlKey,
               shiftKey:shiftKey,
               altKey:altKey
            }
                    );
            // callbacks before insertion
            prepare(options.beforeInsert);
            prepare(clicked.beforeInsert);
            if(ctrlKey === true && shiftKey === true) {
               prepare(clicked.beforeMultiInsert);
            }
            $.extend(hash,{ line:1 });
            //TODO(AP) dirty hack for auto multilines
            if(ctrlKey === true && shiftKey === true) {
               lines = selection.split(/\r?\n/);
               for(j = 0,n = lines.length,i = 0; i < n; i++) {
                  if($.trim(lines[i]) !== '') {
                     $.extend(hash,{ line:++j, selection:lines[i] });
                     lines[i] = build(lines[i]).block;
                  } else {
                     lines[i] = "";
                  }
               }
               string = { block:lines.join('\n')};
               start = caretPosition;
               len = string.block.length + (($.browser.opera) ? n : 0);
            } else if(clicked.useAsMulti) {
               if(selection.length > 0) {
                  lines = selection.split(/\r?\n/);
                  for(j = 0,n = lines.length,i = 0; i < n; i++) {
                     if($.trim(lines[i]) !== '') {
                        $.extend(hash,{ line:++j, selection:lines[i] });
                        lines[i] = build(lines[i]).block;
                     } else {
                        lines[i] = "";
                     }
                  }
                  string = { block:lines.join('\n')};
                  start = caretPosition;
                  len = string.block.length + (($.browser.opera) ? n : 0);
               } else {
                  string = build(selection);
                  start = caretPosition + string.block.length;
                  len = 0;
                  start -= fixIeBug(string.block);
               }
            } else if(ctrlKey === true) {
               string = build(selection);
               start = caretPosition + string.openWith.length;
               len = string.block.length - string.openWith.length - string.closeWith.length;
               len -= fixIeBug(string.block);
            } else if(shiftKey === true) {
               string = build(selection);
               start = caretPosition;
               len = string.block.length;
               len -= fixIeBug(string.block);
            } else {
               string = build(selection);
               start = caretPosition + string.block.length;
               len = 0;
               start -= fixIeBug(string.block);
            }
            if((selection === '' && string.replaceWith === '')) {
               caretOffset += fixOperaBug(string.block);

               start = caretPosition + string.openWith.length;
               len = string.block.length - string.openWith.length - string.closeWith.length;

               caretOffset = $$.val().substring(caretPosition,$$.val().length).length;
               caretOffset -= fixOperaBug($$.val().substring(0,caretPosition));
            }
            $.extend(hash,{ caretPosition:caretPosition, scrollPosition:scrollPosition });

            if(string.block !== selection && abort === false) {
               insert(string.block);
               set(start,len);
            } else {
               caretOffset = -1;
            }
            get();

            $.extend(hash,{ line:'', selection:selection });

            // callbacks after insertion
            if(ctrlKey === true && shiftKey === true) {
               prepare(clicked.afterMultiInsert);
            }
            prepare(clicked.afterInsert);
            prepare(options.afterInsert);

            // refresh preview if opened
            if(previewWindow && options.previewAutoRefresh) {
               refreshPreview();
            }

            // reinit keyevent
            shiftKey = altKey = ctrlKey = abort = false;
         }

         // Substract linefeed in Opera
         function fixOperaBug(string) {
            if($.browser.opera) {
               return string.length - string.replace(/\n*/g,'').length;
            }
            return 0;
         }

         // Substract linefeed in IE
         function fixIeBug(string) {
            if($.browser.msie) {
               return string.length - string.replace(/\r*/g,'').length;
            }
            return 0;
         }

         // add markup
         function insert(block) {
            if(document.selection) {
               var newSelection = document.selection.createRange();
               newSelection.text = block;
            } else {
               $$.val($$.val().substring(0,caretPosition) + block + $$.val().substring(caretPosition
                       + selection.length,$$.val().length));
            }
         }

         // set a selection
         function set(start,len) {
            if(textarea.createTextRange) {
               // quick fix to make it work on Opera 9.5
               if($.browser.opera && $.browser.version >= 9.5 && len == 0) {
                  return false;
               }
               range = textarea.createTextRange();
               range.collapse(true);
               range.moveStart('character',start);
               range.moveEnd('character',len);
               range.select();
            } else if(textarea.setSelectionRange) {
               textarea.setSelectionRange(start,start + len);
            }
            textarea.scrollTop = scrollPosition;
            textarea.focus();
         }

         // get the selection
         function get() {
            textarea.focus();

            scrollPosition = textarea.scrollTop;
            if(document.selection) {
               selection = document.selection.createRange().text;
               if($.browser.msie) { // ie
                  var range = document.selection.createRange(), rangeCopy = range.duplicate();
                  rangeCopy.moveToElementText(textarea);
                  caretPosition = -1;
                  while(rangeCopy.inRange(range)) { // fix most of the ie bugs with linefeeds...
                     rangeCopy.moveStart('character');
                     caretPosition ++;
                  }
               } else { // opera
                  caretPosition = textarea.selectionStart;
               }
            } else { // gecko
               caretPosition = textarea.selectionStart;
               selection = $$.val().substring(caretPosition,textarea.selectionEnd);
            }
            //TODO(AP) See less hack method
            if(selection != '' && (selection.lastIndexOf(' ') == selection.length - 1)) {
               selection = selection.trim();
            }
            return selection;
         }

         // open preview window
         function preview() {
            if(!previewWindow || previewWindow.closed) {
               if(options.previewInWindow) {
                  previewWindow = window.open('','preview',options.previewInWindow);
               } else {
                  iFrame = $('<iframe class="markItUpPreviewFrame"></iframe>');
                  if(options.previewPosition == 'after') {
                     iFrame.insertAfter(footer);
                  } else {
                     iFrame.insertBefore(header);
                  }
                  previewWindow = iFrame[iFrame.length - 1].contentWindow || frame[iFrame.length - 1];
               }
            } else if(altKey === true) {
               if(iFrame) {
                  iFrame.remove();
               }
               previewWindow.close();
               previewWindow = iFrame = false;
            }
            if(!options.previewAutoRefresh) {
               refreshPreview();
            }
         }

         // refresh Preview window
         function refreshPreview() {
            if(previewWindow.document) {
               try {
                  sp = previewWindow.document.documentElement.scrollTop
               } catch(e) {
                  sp = 0;
               }
               previewWindow.document.open();
               previewWindow.document.write(renderPreview());
               previewWindow.document.close();
               previewWindow.document.documentElement.scrollTop = sp;
            }
            if(options.previewInWindow) {
               previewWindow.focus();
            }
         }

         function renderPreview() {
            if(options.previewParserPath !== '') {
               $.ajax({
                  type: 'POST',
                  async: false,
                  url: options.previewParserPath,
                  data: options.previewParserVar + '=' + encodeURIComponent($$.val()),
                  success: function(data) {
                     phtml = localize(data,1);
                  }
               });
            } else {
               if(!template) {
                  $.ajax({
                     async: false,
                     url: options.previewTemplatePath,
                     success: function(data) {
                        template = localize(data,1);
                     }
                  });
               }
               phtml = template.replace(/<!-- content -->/g,$$.val());
            }
            return phtml;
         }

         // set keys pressed
         function keyPressed(e) {
            shiftKey = e.shiftKey;
            altKey = e.altKey;
            ctrlKey = (!(e.altKey && e.ctrlKey)) ? e.ctrlKey : false;

            if(e.type === 'keydown') {
               if(ctrlKey === true) {
                  li = $("a[accesskey=" + String.fromCharCode(e.keyCode) + "]",header).parent('li');
                  if(li.length !== 0) {
                     ctrlKey = false;
                     li.triggerHandler('mouseup');
                     return false;
                  }
               }
               if(e.keyCode === 13 || e.keyCode === 10) { // Enter key
                  if(ctrlKey === true) {  // Enter + Ctrl
                     ctrlKey = false;
                     markup(options.onCtrlEnter);
                     return options.onCtrlEnter.keepDefault;
                  } else if(shiftKey === true) { // Enter + Shift
                     shiftKey = false;
                     markup(options.onShiftEnter);
                     return options.onShiftEnter.keepDefault;
                  } else { // only Enter
                     markup(options.onEnter);
                     return options.onEnter.keepDefault;
                  }
               }
               if(e.keyCode === 9) { // Tab key
                  if(shiftKey == true || ctrlKey == true || altKey == true) { // Thx Dr Floob.
                     return false;
                  }
                  if(caretOffset !== -1) {
                     get();
                     caretOffset = $$.val().length - caretOffset;
                     set(caretOffset,0);
                     caretOffset = -1;
                     return false;
                  } else {
                     markup(options.onTab);
                     return options.onTab.keepDefault;
                  }
               }
            }
         }

         init();
      });
   };

   $.fn.markItUpRemove = function() {
      return this.each(function() {
         $$ = $(this).unbind().removeClass('markItUpEditor');
         $$.parent('div').parent('div.markItUp').parent('div').replaceWith($$);
      }
              );
   };

   $.markItUp = function(settings) {
      var options = { target:false };
      $.extend(options,settings);
      if(options.target) {
         return $(options.target).each(function() {
            $(this).focus();
            $(this).trigger('insertion',[options]);
         });
      } else {
         $('textarea').trigger('insertion',[options]);
      }
   };
})(jQuery);

