/*
 * ProfilerPresenter.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.views.source.editors.profiler;

import com.google.gwt.core.client.GWT;
import org.rstudio.core.client.Debug;
import org.rstudio.core.client.HandlerRegistrations;
import org.rstudio.core.client.command.CommandBinder;
import org.rstudio.core.client.command.Handler;
import org.rstudio.core.client.files.FileSystemItem;
import org.rstudio.core.client.js.JsObject;
import org.rstudio.core.client.widget.Operation;
import org.rstudio.core.client.widget.OperationWithInput;
import org.rstudio.core.client.widget.ProgressIndicator;
import org.rstudio.core.client.widget.ProgressOperationWithInput;
import org.rstudio.studio.client.application.events.EventBus;
import org.rstudio.studio.client.common.FileDialogs;
import org.rstudio.studio.client.common.GlobalDisplay;
import org.rstudio.studio.client.common.SimpleRequestCallback;
import org.rstudio.studio.client.common.dependencies.DependencyManager;
import org.rstudio.studio.client.common.filetypes.FileTypeRegistry;
import org.rstudio.studio.client.server.ServerError;
import org.rstudio.studio.client.server.ServerRequestCallback;
import org.rstudio.studio.client.workbench.WorkbenchContext;
import org.rstudio.studio.client.workbench.commands.Commands;
import org.rstudio.studio.client.workbench.model.RemoteFileSystemContext;
import org.rstudio.studio.client.workbench.views.source.SourceWindowManager;
import org.rstudio.studio.client.workbench.views.source.ViewsSourceConstants;
import org.rstudio.studio.client.workbench.views.source.editors.profiler.model.ProfileOperationRequest;
import org.rstudio.studio.client.workbench.views.source.editors.profiler.model.ProfileOperationResponse;
import org.rstudio.studio.client.workbench.views.source.editors.profiler.model.ProfilerContents;
import org.rstudio.studio.client.workbench.views.source.editors.profiler.model.ProfilerServerOperations;
import org.rstudio.studio.client.workbench.views.source.model.SourceDocument;
import org.rstudio.studio.client.workbench.views.source.model.SourceServerOperations;

import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.ScheduledCommand;
import com.google.gwt.user.client.Command;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;

@Singleton
public class ProfilerPresenter implements RprofEvent.Handler
{
   private final ProfilerServerOperations server_;
   private final SourceServerOperations sourceServer_;
   private final Commands commands_;
   private final DependencyManager dependencyManager_;
   private final HandlerRegistrations handlerRegistrations_ = new HandlerRegistrations();
   private final GlobalDisplay globalDisplay_;
   private final EventBus events_;
   private Provider<SourceWindowManager> pSourceWindowManager_;
   private final FileDialogs fileDialogs_;
   private final RemoteFileSystemContext fileContext_;
   private final WorkbenchContext workbenchContext_;
   private final FileTypeRegistry fileTypeRegistry_;
   private String currentDocId_;
   
   final String profilerDependecyUserAction_ = constants_.theProfilerText();
   
   private ProfileOperationResponse response_ = null;

   public interface Binder extends CommandBinder<Commands, ProfilerPresenter>
   {
   }
   
   public interface Display
   {
   }

   @Inject
   public ProfilerPresenter(ProfilerServerOperations server,
                            Binder binder,
                            Commands commands,
                            DependencyManager dependencyManager,
                            GlobalDisplay globalDisplay,
                            EventBus events,
                            Provider<SourceWindowManager> pSourceWindowManager,
                            FileDialogs fileDialogs,
                            RemoteFileSystemContext fileContext,
                            WorkbenchContext workbenchContext,
                            FileTypeRegistry fileTypeRegistry,
                            SourceServerOperations sourceServer)
   {
      server_ = server;
      commands_ = commands;
      dependencyManager_ = dependencyManager;
      globalDisplay_ = globalDisplay;
      events_ = events;
      pSourceWindowManager_ = pSourceWindowManager;
      fileDialogs_ = fileDialogs;
      fileContext_ = fileContext;
      workbenchContext_ = workbenchContext;
      fileTypeRegistry_ = fileTypeRegistry;
      sourceServer_ = sourceServer;
      
      binder.bind(commands, this);

      // by default, one can always start profiling
      enableStoppedCommands();
      
      events_.addHandler(RprofEvent.TYPE, this);
   }
   
   public void onRprofEvent(RprofEvent event)
   {
      switch (event.getEventType())
      {
         case START:
            enableStartedCommands();
            break;
         case STOP:
            enableStoppedCommands();
            break;
         case CREATE:
            events_.fireEvent(new OpenProfileEvent(
               event.getData().getPath(),
               event.getData().getHtmlPath(),
               event.getData().getHtmlLocalPath(),
               true,
               currentDocId_));
            currentDocId_ = null;
            break;
         default:
            break;
      }
   }

   public void attach(SourceDocument doc, Display view)
   {
   }

   public void detach()
   {
      // unsubscribe from view
      handlerRegistrations_.removeHandler();
   }

   @Handler
   public void onStartProfiler()
   {
      dependencyManager_.withProfvis(profilerDependecyUserAction_, new Command()
      {
         @Override
         public void execute()
         {
            ProfileOperationRequest request = ProfileOperationRequest
                  .create("");
            server_.startProfiling(request,
                  new ServerRequestCallback<ProfileOperationResponse>()
            {
               @Override
               public void onResponseReceived(ProfileOperationResponse response)
               {
                  if (response.getErrorMessage() != null)
                  {
                     globalDisplay_.showErrorMessage(constants_.profilerError(),
                           response.getErrorMessage());
                     return;
                  }
                  
                  pSourceWindowManager_.get().ensureVisibleSourcePaneIfNecessary();

                  response_ = response;
                  
                  sourceServer_.newDocument(
                     FileTypeRegistry.PROFILER.getTypeId(),
                     null,
                     (JsObject) ProfilerContents.create(
                           response.getFileName(),
                           null,
                           null,
                           true).cast(),
                     new SimpleRequestCallback<SourceDocument>(constants_.showProfiler())
                     {
                        @Override
                        public void onResponseReceived(SourceDocument response)
                        {
                           currentDocId_ = response.getId();
                        }
                        
                        @Override
                        public void onError(ServerError error)
                        {
                           Debug.logError(error);
                        }
                     });
               }

               @Override
               public void onError(ServerError error)
               {
                  Debug.logError(error);
                  globalDisplay_.showErrorMessage(constants_.failedToStopProfiler(),
                        error.getMessage());
               }
            });
         }
      });
   }

   @Handler
   public void onStopProfiler()
   {
      ProfileOperationRequest request = ProfileOperationRequest
            .create(response_ != null ? response_.getFileName() : null);

      response_ = null;
      server_.stopProfiling(request,
            new ServerRequestCallback<ProfileOperationResponse>()
            {
               @Override
               public void onResponseReceived(ProfileOperationResponse response)
               {
                  if (response.getErrorMessage() != null)
                  {
                     globalDisplay_.showErrorMessage(constants_.profilerError(),
                           response.getErrorMessage());
                     return;
                  }
               }

               @Override
               public void onError(ServerError error)
               {
                  Debug.logError(error);
                  globalDisplay_.showErrorMessage(constants_.failedToStopProfiler(),
                        error.getMessage());
               }
            });
   }
   
   @Handler
   public void onOpenProfile()
   {
      fileDialogs_.openFile(
         constants_.openFile(),
         fileContext_,
         workbenchContext_.getDefaultFileDialogDir(),
         "Profvis Profiles (*.Rprofvis)",
         new ProgressOperationWithInput<FileSystemItem>()
         {
            public void execute(final FileSystemItem input,
                                ProgressIndicator indicator)
            {
               if (input == null)
                  return;
   
               workbenchContext_.setDefaultFileDialogDir(
                                                input.getParentPath());
   
               indicator.onCompleted();
               Scheduler.get().scheduleDeferred(new ScheduledCommand()
               {
                  public void execute()
                  {
                     fileTypeRegistry_.openFile(input);
                  }
               });
            }
         });
   }
   
   @Handler
   public void onSaveProfileAs()
   {
      commands_.saveSourceDocAs().execute();
   }
   
   public void buildHtmlPath(final OperationWithInput<ProfileOperationResponse> continuation,
                             final Operation onError,
                             final String path)
   {
      ProfileOperationRequest request = ProfileOperationRequest.create(path);
       
      server_.openProfile(request, 
            new ServerRequestCallback<ProfileOperationResponse>()
      {
         @Override
         public void onResponseReceived(ProfileOperationResponse response)
         {
            if (response.getErrorMessage() != null)
            {
               globalDisplay_.showErrorMessage(constants_.profilerError(),
                     response.getErrorMessage());
               onError.execute();
               return;
            }
             
            continuation.execute(response);
         }
   
         @Override
         public void onError(ServerError error)
         {
            Debug.logError(error);
            globalDisplay_.showErrorMessage(constants_.failedToOpenProfile(),
                  error.getMessage());
            onError.execute();
         }
      });
   }

   private void enableStartedCommands()
   {
      commands_.startProfiler().setEnabled(false);
      commands_.stopProfiler().setEnabled(true);
   }

   private void enableStoppedCommands()
   {
      commands_.startProfiler().setEnabled(true);
      commands_.stopProfiler().setEnabled(false);
   }
   private static final ViewsSourceConstants constants_ = GWT.create(ViewsSourceConstants.class);
}
