/*
 * AceEditorNative.java
 *
 * Copyright (C) 2009-12 by RStudio, Inc.
 *
 * Unless you have received this program directly from RStudio pursuant
 * to the terms of a commercial license agreement with RStudio, then
 * this program is licensed to you under the terms of version 3 of the
 * GNU Affero General Public License. This program is distributed WITHOUT
 * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
 * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
 * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
 *
 */
package org.rstudio.studio.client.workbench.views.source.editors.text.ace;

import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.dom.client.Element;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.event.shared.HasHandlers;
import com.google.gwt.user.client.Command;

import org.rstudio.core.client.CommandWithArg;
import java.util.LinkedList;

public class AceEditorNative extends JavaScriptObject {

   protected AceEditorNative() {}

   public native final EditSession getSession() /*-{
      return this.getSession();
   }-*/;

   public native final Renderer getRenderer() /*-{
      return this.renderer;
   }-*/;

   public native final void resize() /*-{
      this.resize();
   }-*/;

   public native final void setShowPrintMargin(boolean show) /*-{
      this.setShowPrintMargin(show);
   }-*/;

   public native final void setPrintMarginColumn(int column) /*-{
      this.setPrintMarginColumn(column);
   }-*/;

   public native final boolean getHighlightActiveLine() /*-{
      return this.getHighlightActiveLine();
   }-*/;
   
   public native final void setHighlightActiveLine(boolean highlight) /*-{
      this.setHighlightActiveLine(highlight);
   }-*/;
   
   public native final void setHighlightGutterLine(boolean highlight) /*-{
      this.setHighlightGutterLine(highlight);
   }-*/;

   public native final void setHighlightSelectedWord(boolean highlight) /*-{
      this.setHighlightSelectedWord(highlight);
   }-*/;

   public native final boolean getReadOnly() /*-{
      return this.getReadOnly();
   }-*/;

   public native final void setReadOnly(boolean readOnly) /*-{
      this.setReadOnly(readOnly);
   }-*/;
   
   public native final void toggleCommentLines() /*-{
      this.toggleCommentLines();
   }-*/;

   public native final void focus() /*-{
      this.focus();
   }-*/;
   
   public native final boolean isFocused() /*-{
      return this.isFocused();
   }-*/;
   
   public native final boolean isRowFullyVisible(int row) /*-{
      return this.isRowFullyVisible(row);
   }-*/;

   public native final void blur() /*-{
      this.blur();
   }-*/;

   public native final void setKeyboardHandler(KeyboardHandler keyboardHandler) /*-{
      this.setKeyboardHandler(keyboardHandler);
   }-*/;
   
   public native final void addKeyboardHandler(KeyboardHandler keyboardHandler) /*-{
      this.keyBinding.addKeyboardHandler(keyboardHandler);
   }-*/;


   public native final void onChange(CommandWithArg<AceDocumentChangeEventNative> command) /*-{
      this.getSession().on("change",
        $entry(function (arg) {
            command.@org.rstudio.core.client.CommandWithArg::execute(Ljava/lang/Object;)(arg);
        }));
   }-*/;

   public native final void onChangeFold(Command command) /*-{
      this.getSession().on("changeFold",
              $entry(function () {
                 command.@com.google.gwt.user.client.Command::execute()();
              }));
   }-*/;
   
   public native final <T> void onGutterMouseDown(CommandWithArg<T> command) /*-{
      this.on("guttermousedown",
         $entry(function (arg) {
            command.@org.rstudio.core.client.CommandWithArg::execute(Ljava/lang/Object;)(arg);
         }));         
   }-*/;

   public final HandlerRegistration delegateEventsTo(HasHandlers handlers)
   {
      final LinkedList<JavaScriptObject> handles = new LinkedList<JavaScriptObject>();
      handles.add(addDomListener(getTextInputElement(), "keydown", handlers));
      handles.add(addDomListener(getTextInputElement(), "keypress", handlers));
      handles.add(addDomListener(this.<Element>cast(), "focus", handlers));
      handles.add(addDomListener(this.<Element>cast(), "blur", handlers));

      return new HandlerRegistration()
      {
         public void removeHandler()
         {
            while (!handles.isEmpty())
               removeDomListener(handles.remove());
         }
      };
   }

   private native Element getTextInputElement() /*-{
      return this.textInput.getElement();
   }-*/;

   private native static JavaScriptObject addDomListener(
         Element element,
         String eventName,
         HasHandlers hasHandlers) /*-{
      var event = $wnd.require("ace/lib/event");
      var listener = $entry(function(e) {
         @com.google.gwt.event.dom.client.DomEvent::fireNativeEvent(Lcom/google/gwt/dom/client/NativeEvent;Lcom/google/gwt/event/shared/HasHandlers;Lcom/google/gwt/dom/client/Element;)(e, hasHandlers, element);
      }); 
      event.addListener(element, eventName, listener);
      return $entry(function() {
         event.removeListener(element, eventName, listener);
      });
   }-*/;

   private native static void removeDomListener(JavaScriptObject handle) /*-{
      handle();
   }-*/;

   public static native AceEditorNative createEditor(Element container) /*-{
      var require = $wnd.require;
      var loader = require("rstudio/loader");
      return loader.loadEditor(container);
   }-*/;

   public static <T> HandlerRegistration addEventListener(
         JavaScriptObject target,
         String event,
         CommandWithArg<T> command)
   {
      final JavaScriptObject functor = addEventListenerInternal(target,
                                                                event,
                                                                command);
      return new HandlerRegistration()
      {
         public void removeHandler()
         {
            invokeFunctor(functor);
         }
      };
   }

   private static native <T> JavaScriptObject addEventListenerInternal(
         JavaScriptObject target,
         String eventName,
         CommandWithArg<T> command) /*-{
      var callback = $entry(function(arg) {
         command.@org.rstudio.core.client.CommandWithArg::execute(Ljava/lang/Object;)(arg);
      });

      target.addEventListener(eventName, callback);
      return function() {
         target.removeEventListener(eventName, callback);
      };
   }-*/;

   private static native void invokeFunctor(JavaScriptObject functor) /*-{
      functor();
   }-*/;

   public final native void scrollToRow(int row) /*-{
      this.scrollToRow(row);
   }-*/;

   public final native void scrollToLine(int line, boolean center) /*-{
      this.scrollToLine(line, center);
   }-*/;
   
   public final native void jumpToMatching()  /*-{
      this.jumpToMatching();
   }-*/;
   
   public native final void revealRange(Range range, boolean animate) /*-{
      this.revealRange(range, animate);
   }-*/;

   public final native void autoHeight() /*-{
      var editor = this;
      function updateEditorHeight() {
         editor.container.style.height = (Math.max(1, editor.getSession().getScreenLength()) * editor.renderer.lineHeight) + 'px';
         editor.resize();
         editor.renderer.scrollToY(0);
         editor.renderer.scrollToX(0);
      }
      if (!editor.autoHeightAttached) {
         editor.autoHeightAttached = true;
         editor.getSession().getDocument().on("change", updateEditorHeight);
         editor.renderer.$textLayer.on("changeCharacterSize", updateEditorHeight);
      }
      updateEditorHeight();
   }-*/;

   public final native void onCursorChange() /*-{
      this.onCursorChange();
   }-*/;

   public static native void setInsertMatching(boolean insertMatching) /*-{
      $wnd.require("mode/auto_brace_insert").setInsertMatching(insertMatching);
   }-*/;

   public static native void setVerticallyAlignFunctionArgs(
         boolean verticallyAlign) /*-{
      $wnd.require("mode/r_code_model").setVerticallyAlignFunctionArgs(verticallyAlign);
   }-*/;

   public final native int getFirstVisibleRow() /*-{
      return this.getFirstVisibleRow();
   }-*/;

   public final native int getLastVisibleRow() /*-{
      return this.getLastVisibleRow();
   }-*/;
   
}

@@ -6309,32 +6309,90 @@
 function(arg) %7B%0A
+         if (arg && arg.text)%0A            arg = arg.text;%0A
          command

fix exception on paste event