/*
 * ErrorManager.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.common.debugging;

import org.rstudio.studio.client.server.Void;
import org.rstudio.core.client.command.CommandBinder;
import org.rstudio.core.client.command.Handler;
import org.rstudio.studio.client.application.events.EventBus;
import org.rstudio.studio.client.common.debugging.events.ErrorHandlerChangedEvent;
import org.rstudio.studio.client.common.debugging.model.ErrorHandlerType;
import org.rstudio.studio.client.common.debugging.model.ErrorManagerState;
import org.rstudio.studio.client.server.ServerError;
import org.rstudio.studio.client.server.ServerRequestCallback;
import org.rstudio.studio.client.workbench.commands.Commands;
import org.rstudio.studio.client.workbench.events.SessionInitEvent;
import org.rstudio.studio.client.workbench.events.SessionInitHandler;
import org.rstudio.studio.client.workbench.model.Session;
import org.rstudio.studio.client.workbench.views.environment.events.DebugModeChangedEvent;

import com.google.inject.Inject;
import com.google.inject.Singleton;

@Singleton
public class ErrorManager
             implements DebugModeChangedEvent.Handler,
                        ErrorHandlerChangedEvent.Handler,
                        SessionInitHandler
{
   public interface Binder
   extends CommandBinder<Commands, ErrorManager> {}
   
   private enum DebugHandlerState
   {
      None,
      Pending
   }

   @Inject
   public ErrorManager(EventBus events, 
                       Binder binder, 
                       Commands commands, 
                       DebuggingServerOperations server,
                       Session session)
   {
      events_ = events;
      server_ = server;
      commands_ = commands;
      session_ = session;
      binder.bind(commands, this);
      
      events_.addHandler(DebugModeChangedEvent.TYPE, this);
      events_.addHandler(ErrorHandlerChangedEvent.TYPE, this);
      events_.addHandler(SessionInitEvent.TYPE, this);
   }

   // Event and command handlers ----------------------------------------------

   @Override
   public void onDebugModeChanged(DebugModeChangedEvent event)
   {
      // if we expected to go into debug mode, this is what we were waiting
      // for--change the error handler back to whatever it was formerly
      if (event.debugging() && 
          debugHandlerState_ == DebugHandlerState.Pending)
      {
         setErrorManagementType(previousHandlerType_);
      }
      debugHandlerState_ = DebugHandlerState.None;
   }

   @Override
   public void onErrorHandlerChanged(ErrorHandlerChangedEvent event)
   {
      int newType = event.getHandlerType().getType();
      if (newType != errorManagerState_.getErrorHandlerType())
      {
         errorManagerState_.setErrorHandlerType(newType);
         syncHandlerCommandsCheckedState();
      }
   }

   @Override
   public void onSessionInit(SessionInitEvent sie)
   {
      errorManagerState_ = session_.getSessionInfo().getErrorState();
      if (session_.getSessionInfo().getHaveSrcrefAttribute())
      {
         syncHandlerCommandsCheckedState();
      }
      else
      {
         // If we don't have source references, disable the commands that 
         // set error handlers (these generally work on user code only, and
         // we can't reliably distinguish user code without source refs)
         commands_.errorsMessage().setEnabled(false);
         commands_.errorsTraceback().setEnabled(false);
         commands_.errorsBreak().setEnabled(false);
      }
   }

   @Handler
   public void onErrorsMessage()
   {
      setErrorManagementTypeCommand(ErrorHandlerType.ERRORS_MESSAGE);
   }
   
   @Handler 
   public void onErrorsTraceback()
   {
      setErrorManagementTypeCommand(ErrorHandlerType.ERRORS_TRACEBACK);
   }

   @Handler 
   public void onErrorsBreak()
   {
      setErrorManagementTypeCommand(ErrorHandlerType.ERRORS_BREAK);
   }
   
   // Public methods ----------------------------------------------------------

   public void setDebugSessionHandlerType(
         int type, 
         final ServerRequestCallback<Void> callback)
   {
      if (type == errorManagerState_.getErrorHandlerType())
         return;
      
      setErrorManagementType(type, new ServerRequestCallback<Void>()
      {
         @Override
         public void onResponseReceived(Void v)
         {
            previousHandlerType_ = errorManagerState_.getErrorHandlerType();
            debugHandlerState_ = DebugHandlerState.Pending;
            callback.onResponseReceived(v);
         }

         @Override
         public void onError(ServerError error)
         {
            callback.onError(error);
         }
      });
   }
   
   // Private methods ---------------------------------------------------------
   
   private int getErrorHandlerType()
   {
      return errorManagerState_.getErrorHandlerType();
   }

   private void setErrorManagementTypeCommand(int type)
   {
      // The error handler may be currently overridden for debug mode. If the
      // user changes the setting via command during debug mode, we don't want
      // to change it back when leaving debug mode.
      debugHandlerState_ = DebugHandlerState.None;
      setErrorManagementType(type);
   }

   private void setErrorManagementType(
         int type, 
         ServerRequestCallback<Void> callback)
   {
      server_.setErrorManagementType(type, callback);
   }
      
   private void setErrorManagementType(int type)
   {
      setErrorManagementType(type, 
            new ServerRequestCallback<Void>()
      {         
         @Override
         public void onError(ServerError error)
         {
            // No action necessary--the server emits an event when the handler
            // type changes
         }
      });
   }
   
   private void syncHandlerCommandsCheckedState()
   {
      int type = getErrorHandlerType();
      commands_.errorsMessage().setChecked(
            type == ErrorHandlerType.ERRORS_MESSAGE);
      commands_.errorsTraceback().setChecked(
            type == ErrorHandlerType.ERRORS_TRACEBACK);
      commands_.errorsBreak().setChecked(
            type == ErrorHandlerType.ERRORS_BREAK);
   }

   private final EventBus events_;
   private final DebuggingServerOperations server_;
   private final Session session_;
   private final Commands commands_;

   private DebugHandlerState debugHandlerState_ = DebugHandlerState.None;
   private ErrorManagerState errorManagerState_; 
   private int previousHandlerType_;
}
