/*
 * FindOutputPresenter.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.output.find;

import com.google.gwt.core.client.JsArrayString;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.HasClickHandlers;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.inject.Inject;
import org.rstudio.core.client.CodeNavigationTarget;
import org.rstudio.core.client.StringUtil;
import org.rstudio.core.client.events.HasEnsureHiddenHandlers;
import org.rstudio.core.client.events.HasSelectionCommitHandlers;
import org.rstudio.core.client.events.SelectionCommitEvent;
import org.rstudio.core.client.events.SelectionCommitHandler;
import org.rstudio.core.client.files.FileSystemItem;
import org.rstudio.core.client.js.JsObject;
import org.rstudio.core.client.widget.OperationWithInput;
import org.rstudio.core.client.widget.events.SelectionChangedEvent;
import org.rstudio.core.client.widget.events.SelectionChangedHandler;
import org.rstudio.studio.client.application.events.EventBus;
import org.rstudio.studio.client.common.SimpleRequestCallback;
import org.rstudio.studio.client.common.filetypes.FileTypeRegistry;
import org.rstudio.studio.client.server.VoidServerRequestCallback;
import org.rstudio.studio.client.workbench.WorkbenchContext;
import org.rstudio.studio.client.workbench.WorkbenchView;
import org.rstudio.studio.client.workbench.model.ClientState;
import org.rstudio.studio.client.workbench.model.Session;
import org.rstudio.studio.client.workbench.model.helper.JSObjectStateValue;
import org.rstudio.studio.client.workbench.views.BasePresenter;
import org.rstudio.studio.client.workbench.views.output.find.events.FindInFilesEvent;
import org.rstudio.studio.client.workbench.views.output.find.events.FindOperationEndedEvent;
import org.rstudio.studio.client.workbench.views.output.find.events.FindResultEvent;
import org.rstudio.studio.client.workbench.views.output.find.model.FindInFilesServerOperations;
import org.rstudio.studio.client.workbench.views.output.find.model.FindInFilesState;
import org.rstudio.studio.client.workbench.views.output.find.model.FindResult;

import java.util.ArrayList;

public class FindOutputPresenter extends BasePresenter
{
   public interface Display extends WorkbenchView,
                                    HasSelectionCommitHandlers<CodeNavigationTarget>,
                                    HasEnsureHiddenHandlers
   {
      void addMatches(ArrayList<FindResult> findResults);
      void clearMatches();
      void ensureVisible(boolean activate);

      HasClickHandlers getStopSearchButton();
      void setStopSearchButtonVisible(boolean visible);

      void ensureSelectedRowIsVisible();

      HandlerRegistration addSelectionChangedHandler(SelectionChangedHandler handler);

      void showOverflow();
      
      void showSearchCompleted();

      void updateSearchLabel(String query, String path);
      void clearSearchLabel();
   }

   @Inject
   public FindOutputPresenter(Display view,
                              EventBus events,
                              FindInFilesServerOperations server,
                              final FileTypeRegistry ftr,
                              Session session,
                              WorkbenchContext workbenchContext)
   {
      super(view);
      view_ = view;
      events_ = events;
      server_ = server;
      session_ = session;
      workbenchContext_ = workbenchContext;

      view_.addSelectionChangedHandler(new SelectionChangedHandler()
      {
         @Override
         public void onSelectionChanged(SelectionChangedEvent e)
         {
            view_.ensureSelectedRowIsVisible();
         }
      });

      view_.addSelectionCommitHandler(new SelectionCommitHandler<CodeNavigationTarget>()
      {
         @Override
         public void onSelectionCommit(SelectionCommitEvent<CodeNavigationTarget> event)
         {
            CodeNavigationTarget target = event.getSelectedItem();
            if (target == null)
               return;

            ftr.editFile(FileSystemItem.createFile(target.getFile()),
                         target.getPosition());
         }
      });

      view_.getStopSearchButton().addClickHandler(new ClickHandler()
      {
         @Override
         public void onClick(ClickEvent event)
         {
            stop();
         }
      });

      events_.addHandler(FindResultEvent.TYPE, new FindResultEvent.Handler()
      {
         @Override
         public void onFindResult(FindResultEvent event)
         {
            if (!event.getHandle().equals(currentFindHandle_))
               return;
            view_.addMatches(event.getResults());
            
            view_.ensureVisible(true);
         }
      });

      events_.addHandler(FindOperationEndedEvent.TYPE, new FindOperationEndedEvent.Handler()
      {
         @Override
         public void onFindOperationEnded(
               FindOperationEndedEvent event)
         {
            if (event.getHandle().equals(currentFindHandle_))
            {
               currentFindHandle_ = null;
               view_.setStopSearchButtonVisible(false);
               view_.showSearchCompleted();
            }
         }
      });

      new JSObjectStateValue(GROUP_FIND_IN_FILES, KEY_DIALOG_STATE,
                             ClientState.PROJECT_PERSISTENT,
                             session.getSessionInfo().getClientState(),
                             false)
      {
         @Override
         protected void onInit(JsObject value)
         {
            dialogState_ = value == null
                           ? null
                           : value.<FindInFilesDialog.State>cast();
         }

         @Override
         protected JsObject getValue()
         {
            return dialogState_.cast();
         }
      };
   }

   public void initialize(FindInFilesState state)
   {
      view_.ensureVisible(false);

      currentFindHandle_ = state.getHandle();
      view_.addMatches(state.getResults().toArrayList());
      updateSearchLabel(state.getInput(), state.getPath(), state.isRegex());

      if (state.isRunning())
         view_.setStopSearchButtonVisible(true);
      else
         events_.fireEvent(new FindOperationEndedEvent(state.getHandle()));
   }

   public void onFindInFiles(FindInFilesEvent event)
   {
      FindInFilesDialog dialog = new FindInFilesDialog(new OperationWithInput<FindInFilesDialog.State>()
      {
         @Override
         public void execute(final FindInFilesDialog.State input)
         {
            dialogState_ = input;

            stopAndClear();

            FileSystemItem searchPath =
                                      FileSystemItem.createDir(input.getPath());

            JsArrayString filePatterns = JsArrayString.createArray().cast();
            for (String pattern : input.getFilePatterns())
               filePatterns.push(pattern);

            server_.beginFind(input.getQuery(),
                              input.isRegex(),
                              !input.isCaseSensitive(),
                              searchPath,
                              filePatterns,
                              new SimpleRequestCallback<String>()
                              {
                                 @Override
                                 public void onResponseReceived(String handle)
                                 {
                                    currentFindHandle_ = handle;
                                    updateSearchLabel(input.getQuery(),
                                                      input.getPath(),
                                                      input.isRegex());
                                    view_.setStopSearchButtonVisible(true);

                                    super.onResponseReceived(handle);

                                    view_.ensureVisible(true);
                                 }
                              });
         }
      });

      if (!StringUtil.isNullOrEmpty(event.getSearchPattern()))
         dialog.setSearchPattern(event.getSearchPattern());
      
      if (dialogState_ == null)
      {
         dialog.setDirectory(
               session_.getSessionInfo().getActiveProjectDir() != null ?
               session_.getSessionInfo().getActiveProjectDir() :
               workbenchContext_.getCurrentWorkingDir());
      }
      else
      {
         dialog.setState(dialogState_);
      }

      dialog.showModal();
   }

   public void onDismiss()
   {
      stopAndClear();
      server_.clearFindResults(new VoidServerRequestCallback());
   }

   private void updateSearchLabel(String query, String path, boolean regex)
   {
      if (regex)
         query = "/" + query + "/";
      else
         query = "\"" + query + "\"";

      view_.updateSearchLabel(query, path);
   }

   private void stopAndClear()
   {
      stop();
      view_.clearMatches();
      view_.clearSearchLabel();
   }

   private void stop()
   {
      if (currentFindHandle_ != null)
      {
         server_.stopFind(currentFindHandle_,
                          new VoidServerRequestCallback());
         currentFindHandle_ = null;
      }
      view_.setStopSearchButtonVisible(false);
   }

   private String currentFindHandle_;

   private FindInFilesDialog.State dialogState_;

   private final Display view_;
   private final FindInFilesServerOperations server_;
   private final Session session_;
   private final WorkbenchContext workbenchContext_;
   private EventBus events_;

   private static final String GROUP_FIND_IN_FILES = "find-in-files";
   private static final String KEY_DIALOG_STATE = "dialog-state";
}
