/*
 * EditSnippetsPanel.java
 *
 * Copyright (C) 2022 by Posit Software, PBC
 *
 * Unless you have received this program directly from Posit Software pursuant
 * to the terms of a commercial license agreement with Posit Software, 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.snippets.ui;


import java.util.ArrayList;

import javax.inject.Inject;

import org.rstudio.core.client.BrowseCap;
import org.rstudio.core.client.ElementIds;
import org.rstudio.core.client.Size;
import org.rstudio.core.client.command.KeyboardShortcut;
import org.rstudio.core.client.dom.DomMetrics;
import org.rstudio.core.client.widget.ModalDialogBase;
import org.rstudio.core.client.widget.ModalDialogTracker;
import org.rstudio.core.client.widget.ThemedButton;
import org.rstudio.core.client.widget.WidgetListBox;
import org.rstudio.studio.client.RStudioGinjector;
import org.rstudio.studio.client.application.events.EventBus;
import org.rstudio.studio.client.common.GlobalDisplay;
import org.rstudio.studio.client.common.HelpLink;
import org.rstudio.studio.client.common.filetypes.FileTypeRegistry;
import org.rstudio.studio.client.common.filetypes.TextFileType;
import org.rstudio.studio.client.server.VoidResponse;
import org.rstudio.studio.client.server.VoidServerRequestCallback;
import org.rstudio.studio.client.workbench.prefs.model.UserPrefs;
import org.rstudio.studio.client.workbench.snippets.SnippetHelper;
import org.rstudio.studio.client.workbench.snippets.SnippetsConstants;
import org.rstudio.studio.client.workbench.snippets.model.SnippetData;
import org.rstudio.studio.client.workbench.ui.FontSizeManager;
import org.rstudio.studio.client.workbench.views.output.lint.model.LintServerOperations;
import org.rstudio.studio.client.workbench.views.source.editors.text.AceEditor;
import org.rstudio.studio.client.workbench.views.source.editors.text.DocDisplay;
import org.rstudio.studio.client.workbench.views.source.editors.text.TextDisplay;
import org.rstudio.studio.client.workbench.views.source.editors.text.TextEditingTarget;

import com.google.gwt.aria.client.Roles;
import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.JavaScriptException;
import com.google.gwt.core.client.JsArray;
import com.google.gwt.dom.client.NativeEvent;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.event.dom.client.ChangeEvent;
import com.google.gwt.event.dom.client.ChangeHandler;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.logical.shared.ValueChangeEvent;
import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.ui.DockLayoutPanel;
import com.google.gwt.user.client.ui.SimplePanel;
import com.google.gwt.user.client.ui.Widget;

public class EditSnippetsDialog extends ModalDialogBase implements TextDisplay
{
   public EditSnippetsDialog()
   {
      super(Roles.getDialogRole());
      setThemeAware(true);
      setText(constants_.editSnippetsText());
      RStudioGinjector.INSTANCE.injectMembers(this);
      
      addCancelButton();
      ThemedButton saveButton = new ThemedButton(constants_.saveTitle(), new ClickHandler() {
         public void onClick(ClickEvent event) 
         {
            attemptSaveAndClose();
         }
      });
      addButton(saveButton, ElementIds.DIALOG_OK_BUTTON);
      
      addLeftWidget(new HelpLink(constants_.usingCodeSnippetsText(), "code_snippets"));
   }
   
   @Inject
   void initialize(EventBus events, 
                   GlobalDisplay globalDisplay,
                   FontSizeManager fontSizeManager,
                   UserPrefs uiPrefs,
                   LintServerOperations server)
   {
      events_ = events;
      fontSizeManager_ = fontSizeManager;
      globalDisplay_ = globalDisplay;
      uiPrefs_ = uiPrefs;
      server_ = server;
   }
   
   @Override
   protected Widget createMainWidget()
   {
      panel_ = new DockLayoutPanel(Unit.PX);
      
      // compute the widget size and set it
      Size size = new Size(900, 900);
      size = DomMetrics.adjustedElementSize(size, 
                                            null, 
                                            70,   // pad
                                            100); // client margin
      panel_.setWidth(size.width + "px");
      panel_.setHeight(size.height + "px");
      
      // snippet types
      snippetTypes_ = new WidgetListBox<>();
      snippetTypes_.addChangeHandler(new ChangeHandler() {
         @Override
         public void onChange(ChangeEvent event)
         {
            updateEditor(snippetTypes_.getSelectedItem());
         }
      });
      snippetTypes_.addItem(new EditableSnippets("R", FileTypeRegistry.R));
      snippetTypes_.addItem(new EditableSnippets(FileTypeRegistry.CPP));
      snippetTypes_.addItem(new EditableSnippets(FileTypeRegistry.MARKDOWN));
      snippetTypes_.addItem(new EditableSnippets(FileTypeRegistry.TEX));
      snippetTypes_.addItem(new EditableSnippets(FileTypeRegistry.JS));
      snippetTypes_.addItem(new EditableSnippets(FileTypeRegistry.HTML));
      snippetTypes_.addItem(new EditableSnippets(FileTypeRegistry.CSS));
      snippetTypes_.addItem(new EditableSnippets(FileTypeRegistry.SQL));
      snippetTypes_.addItem(new EditableSnippets(FileTypeRegistry.JAVA));
      snippetTypes_.addItem(new EditableSnippets(FileTypeRegistry.PYTHON));
      snippetTypes_.addItem(new EditableSnippets(FileTypeRegistry.STAN));
      snippetTypes_.addItem(new EditableSnippets(FileTypeRegistry.YAML));
     
      panel_.addWest(snippetTypes_, 150);
     
      // editor
      docDisplay_ = new AceEditor();
      docDisplay_.setUseSoftTabs(false);
      docDisplay_.setTabSize(uiPrefs_.numSpacesForTab().getValue());
      docDisplay_.addValueChangeHandler(new ValueChangeHandler<VoidResponse>() {
         @Override
         public void onValueChange(ValueChangeEvent<VoidResponse> event)
         {
            editorDirty_ = true;
         }
         
      });
      docDisplay_.setFileType(FileTypeRegistry.SNIPPETS);
      SimplePanel panel = new SimplePanel();
      panel.addStyleName("EditDialog");
      panel.getElement().getStyle().setMarginLeft(8, Unit.PX);
      panel.setWidget(docDisplay_.asWidget());
      panel_.add(panel);
      
      TextEditingTarget.syncFontSize(releaseOnDismiss_, 
            events_, 
            this, 
            fontSizeManager_); 
      
      snippetTypes_.setSelectedIndex(0, true);
       
      return panel_;
   }
   
   @Override
   public void focusInitialControl()
   {
      docDisplay_.focus();
   }
   
   private void attemptSaveAndClose()
   {
      // record pending edits
      recordEditorState();
      
      // check for edits we need to save
      JsArray<SnippetData> changedSnippets = JsArray.createArray().cast();
      for (int i = 0; i<snippetTypes_.getItemCount(); i++)
      {
         EditableSnippets snippets = snippetTypes_.getItemAtIdx(i);
         String edits = snippets.getPendingEdits();
         if (edits != null)
         {
            String mode = snippets.getEditorMode();
            JavaScriptException ex = SnippetHelper.loadSnippetsForMode(mode, 
                                                                       edits);
            if (ex != null)
            {
               snippetTypes_.setSelectedIndex(i);
               globalDisplay_.showErrorMessage(
                 constants_.applyingSnippetsError(snippets.getFileTypeLabel()),
                 ex.getDescription());
               return; // early return (don't close dialog)
            }  
            
            changedSnippets.push(SnippetData.create(mode, edits));
         }
      }
      
      // perform the save then close the dialog
      if (changedSnippets.length() > 0)
      {
         server_.saveSnippets(changedSnippets, new VoidServerRequestCallback() {
            @Override
            protected void onSuccess()
            {
               closeDialog();
            }
         });
      }
      else
      {
         closeDialog();
      }
   }
   
   @Override
   public void onActivate()
   {
      docDisplay_.onActivate();
   }

   @Override
   public void adaptToFileType(TextFileType fileType)
   {
      docDisplay_.setFileType(fileType, true);
   }

   @Override
   public void setFontSize(double size)
   {
      docDisplay_.setFontSize(size);
   }
   
   @Override
   public Widget asWidget()
   {
      return this;
   }
   
   @Override
   public void onPreviewNativeEvent(Event.NativePreviewEvent event)
   {
      if (!ModalDialogTracker.isTopMost(this))
         return;

      if (event.getTypeInt() == Event.ONKEYDOWN)
      {
         NativeEvent ne = event.getNativeEvent();
         int mod = KeyboardShortcut.getModifierValue(ne);
         if ((mod == KeyboardShortcut.META || 
             (mod == KeyboardShortcut.CTRL && !BrowseCap.hasMetaKey())))
         {
            if (ne.getKeyCode() == 'S')
            {
               ne.preventDefault();
               ne.stopPropagation();
               event.cancel();
               attemptSaveAndClose();
            }
         }  
      }
      
      super.onPreviewNativeEvent(event);
   }
   
   @Override
   public void onUnload()
   {
      super.onUnload();
      
      while (releaseOnDismiss_.size() > 0)
         releaseOnDismiss_.remove(0).removeHandler();
   }
   
   private void updateEditor(EditableSnippets snippets)
   { 
      // record editor state
      recordEditorState();
        
      // set new snippets
      docDisplay_.setCode(snippets.getSnippetText(), false);
      activeSnippets_ = snippets;
      editorDirty_ = false;
      docDisplay_.scrollToY(activeSnippets_.getScrollPosition(), 0);
   }
   
   private void recordEditorState()
   {
      if (activeSnippets_ != null)
      {
         if (editorDirty_)
            activeSnippets_.recordPendingEdits(docDisplay_.getCode());
         
         activeSnippets_.setScrollPosition(docDisplay_.getScrollTop());
      }
   }
  
   
   private DockLayoutPanel panel_;
   private WidgetListBox<EditableSnippets> snippetTypes_;
   private DocDisplay docDisplay_;
   private boolean editorDirty_ = false;
   private EditableSnippets activeSnippets_ = null;
   
   private final ArrayList<HandlerRegistration> releaseOnDismiss_ = new ArrayList<>();
   
   private EventBus events_;
   private GlobalDisplay globalDisplay_;
   private FontSizeManager fontSizeManager_;
   private UserPrefs uiPrefs_;
   private LintServerOperations server_;
   private static final SnippetsConstants constants_ = GWT.create(SnippetsConstants.class);

}
