/*
 * DesktopApplicationHeader.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.application.ui.impl;

import java.util.ArrayList;

import org.rstudio.core.client.command.CommandBinder;
import org.rstudio.core.client.command.Handler;
import org.rstudio.core.client.command.impl.DesktopMenuCallback;
import org.rstudio.core.client.dom.DomUtils;
import org.rstudio.core.client.js.JsObject;
import org.rstudio.core.client.theme.res.ThemeResources;
import org.rstudio.core.client.theme.res.ThemeStyles;
import org.rstudio.core.client.widget.Operation;
import org.rstudio.studio.client.RStudioGinjector;
import org.rstudio.studio.client.application.ApplicationQuit;
import org.rstudio.studio.client.application.ApplicationQuit.QuitContext;
import org.rstudio.studio.client.application.Desktop;
import org.rstudio.studio.client.application.DesktopHooks;
import org.rstudio.studio.client.application.IgnoredUpdates;
import org.rstudio.studio.client.application.events.EventBus;
import org.rstudio.studio.client.application.model.ApplicationServerOperations;
import org.rstudio.studio.client.application.model.UpdateCheckResult;
import org.rstudio.studio.client.application.ui.ApplicationHeader;
import org.rstudio.studio.client.application.ui.GlobalToolbar;
import org.rstudio.studio.client.common.GlobalDisplay;
import org.rstudio.studio.client.common.debugging.ErrorManager;
import org.rstudio.studio.client.events.BeginPasteEvent;
import org.rstudio.studio.client.events.EndPasteEvent;
import org.rstudio.studio.client.server.ServerError;
import org.rstudio.studio.client.server.ServerRequestCallback;
import org.rstudio.studio.client.workbench.codesearch.CodeSearch;
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.ClientState;
import org.rstudio.studio.client.workbench.model.Session;
import org.rstudio.studio.client.workbench.model.SessionInfo;
import org.rstudio.studio.client.workbench.model.helper.JSObjectStateValue;
import org.rstudio.studio.client.workbench.prefs.model.UIPrefs;
import org.rstudio.studio.client.workbench.views.console.events.SendToConsoleEvent;
import org.rstudio.studio.client.workbench.views.files.events.ShowFolderEvent;
import org.rstudio.studio.client.workbench.views.files.events.ShowFolderHandler;

import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.JsArrayString;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.ScheduledCommand;
import com.google.gwt.dom.client.Element;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.Event.NativePreviewHandler;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.Event.NativePreviewEvent;
import com.google.gwt.user.client.ui.Widget;
import com.google.inject.Inject;
import com.google.inject.Provider;

public class DesktopApplicationHeader implements ApplicationHeader
{
   public interface Binder
         extends CommandBinder<Commands, DesktopApplicationHeader>
   {
   }
   private static Binder binder_ = GWT.create(Binder.class);

   public DesktopApplicationHeader()
   {
      RStudioGinjector.INSTANCE.injectMembers(this);
   }

   @Inject
   public void initialize(final Commands commands,
                          EventBus events,
                          final Session session,
                          ApplicationServerOperations server, 
                          Provider<DesktopHooks> pDesktopHooks,
                          Provider<CodeSearch> pCodeSearch,
                          Provider<UIPrefs> pUIPrefs,
                          ErrorManager errorManager,
                          GlobalDisplay globalDisplay,
                          ApplicationQuit appQuit)
   {
      session_ = session;
      eventBus_= events;
      pUIPrefs_ = pUIPrefs;
      globalDisplay_ = globalDisplay;
      ignoredUpdates_ = IgnoredUpdates.create();
      server_ = server;
      appQuit_ = appQuit;
      binder_.bind(commands, this);
      commands.mainMenu(new DesktopMenuCallback());

      pDesktopHooks.get();

      commands.uploadFile().remove();
      commands.exportFiles().remove();
      commands.updateCredentials().remove();
   
      commands.checkForUpdates().setVisible(true);
      commands.showLogFiles().setVisible(true);
      commands.diagnosticsReport().setVisible(true);
      commands.showFolder().setVisible(true);

      events.addHandler(SessionInitEvent.TYPE, new SessionInitHandler() {
         public void onSessionInit(SessionInitEvent sie)
         {
            final SessionInfo sessionInfo = session.getSessionInfo();
            
            toolbar_.completeInitialization(sessionInfo);
            
            new JSObjectStateValue(
                  "updates",
                  "ignoredUpdates",
                  ClientState.PERSISTENT,
                  session_.getSessionInfo().getClientState(),
                  false)
            {
               @Override
               protected void onInit(JsObject value)
               {
                  if (value != null)
                     ignoredUpdates_ = value.cast();
               }
         
               @Override
               protected JsObject getValue()
               {
                  ignoredUpdatesDirty_ = false;
                  return ignoredUpdates_.cast();
               }
               
               @Override
               protected boolean hasChanged()
               {
                  return ignoredUpdatesDirty_;
               }
            };

            Scheduler.get().scheduleFinally(new ScheduledCommand()
            {
               public void execute()
               {
                  Desktop.getFrame().onWorkbenchInitialized(
                        sessionInfo.getScratchDir());
                  
                  if (sessionInfo.getDisableCheckForUpdates())
                     commands.checkForUpdates().remove();
                  
                  if (!sessionInfo.getDisableCheckForUpdates() &&
                      pUIPrefs_.get().checkForUpdates().getValue())
                  {
                     checkForUpdates(false);
                  }
               }
            });
         }
      });

      events.addHandler(ShowFolderEvent.TYPE, new ShowFolderHandler()
      {
         public void onShowFolder(ShowFolderEvent event)
         {
            Desktop.getFrame().showFolder(event.getPath().getPath());
         }
      });
      
      toolbar_ = new GlobalToolbar(commands, 
                                   events, 
                                   pCodeSearch);
      ThemeStyles styles = ThemeResources.INSTANCE.themeStyles(); 
      toolbar_.addStyleName(styles.desktopGlobalToolbar());
   }
   
   public void showToolbar(boolean showToolbar)
   {
      toolbar_.setVisible(showToolbar);
   }
   
   public boolean isToolbarVisible()
   {
      return toolbar_.isVisible();
   }
   
   public void focusGoToFunction()
   {
      toolbar_.focusGoToFunction();
   }

   @Handler
   void onUndoDummy()
   {
      Desktop.getFrame().undo(isFocusInAceInstance());
   }

   @Handler
   void onRedoDummy()
   {
      Desktop.getFrame().redo(isFocusInAceInstance());
   }

   @Handler
   void onCutDummy()
   {
      Desktop.getFrame().clipboardCut();
   }

   @Handler
   void onCopyDummy()
   {
      Desktop.getFrame().clipboardCopy();
   }

   @Handler
   void onPasteDummy()
   {
      eventBus_.fireEvent(new BeginPasteEvent());
      keyListener_ = Event.addNativePreviewHandler(new NativePreviewHandler()
      {
         
         @Override
         public void onPreviewNativeEvent(NativePreviewEvent event)
         {
            // Defer so that this event can go through and execute unabated
            Scheduler.get().scheduleDeferred(new ScheduledCommand()
            {
               
               @Override
               public void execute()
               {
                  eventBus_.fireEvent(new EndPasteEvent());
                  keyListener_.removeHandler();
                  keyListener_ = null;
               }
            });
         }
      });
      
      Desktop.getFrame().clipboardPaste();
   }

   @Handler
   void onShowLogFiles()
   {
      Desktop.getFrame().showFolder(session_.getSessionInfo().getLogDir());
   }
   
   @Handler
   void onDiagnosticsReport()
   {
      eventBus_.fireEvent(
         new SendToConsoleEvent("rstudio::diagnosticsReport()", true));
      
      new Timer() {
         @Override
         public void run()
         {
            Desktop.getFrame().showFolder("~/rstudio-diagnostics");
         }
      }.schedule(1000);
      
   }

   @Handler
   void onCheckForUpdates()
   {
      checkForUpdates(true);
   }

   public int getPreferredHeight()
   {
      if (toolbar_.isVisible())
         return 32;
      else
         return 5;
   }

   public Widget asWidget()
   {
      return toolbar_;
   }

   private void checkForUpdates(final boolean manual)
   {
      server_.checkForUpdates(manual, 
            new ServerRequestCallback<UpdateCheckResult>()
      {
         @Override
         public void onResponseReceived(UpdateCheckResult result)
         {
            respondToUpdateCheck(result, manual);
         }
         
         @Override
         public void onError(ServerError error)
         {
            globalDisplay_.showErrorMessage("Error Checking for Updates", 
                  "An error occurred while checking for updates: "
                  + error.getMessage());
         }
      });
   }

   private void respondToUpdateCheck(final UpdateCheckResult result, 
                                     boolean manual)
   {
      boolean ignoredUpdate = false;
      if (result.getUpdateVersion().length() > 0)
      {
         JsArrayString ignoredUpdates = ignoredUpdates_.getIgnoredUpdates();
         for (int i = 0; i < ignoredUpdates.length(); i++)
         {
            if (ignoredUpdates.get(i).equals(result.getUpdateVersion()))
            {
               ignoredUpdate = true;
            }
         }
      }
      if (result.getUpdateVersion().length() > 0 &&
          !ignoredUpdate)
      {
         ArrayList<String> buttonLabels = new ArrayList<String>();
         ArrayList<Operation> buttonOperations = new ArrayList<Operation>();
         
         buttonLabels.add("Quit and Download...");
         buttonOperations.add(new Operation() {
            @Override
            public void execute()
            {
               appQuit_.prepareForQuit("Update RStudio", new QuitContext()
               {
                  @Override
                  public void onReadyToQuit(boolean saveChanges)
                  {
                     Desktop.getFrame().browseUrl(result.getUpdateUrl());
                     appQuit_.performQuit(saveChanges);
                  }
               }); 
            }
         });

         buttonLabels.add("Remind Later");
         buttonOperations.add(new Operation() {
            @Override
            public void execute()
            {
               // Don't do anything here; the prompt will re-appear the next
               // time we do an update check
            }
         });

         // Only provide the option to ignore the update if it's not urgent.
         if (result.getUpdateUrgency() == 0)
         {
            buttonLabels.add("Ignore Update");
            buttonOperations.add(new Operation() {
               @Override
               public void execute()
               {
                  ignoredUpdates_.addIgnoredUpdate(result.getUpdateVersion());
                  ignoredUpdatesDirty_ = true;
               }
            });
         }

         globalDisplay_.showGenericDialog(GlobalDisplay.MSG_QUESTION, 
               "Update Available", 
               result.getUpdateMessage(), 
               buttonLabels, 
               buttonOperations, 0);
      }
      else if (manual) 
      {
         globalDisplay_.showMessage(GlobalDisplay.MSG_INFO, 
                              "No Update Available", 
                              "You're using the newest version of RStudio.");
      }
   }
   
   private static boolean isFocusInAceInstance()
   {
      Element focusElem = DomUtils.getActiveElement();
      if (focusElem != null)
         return focusElem.hasClassName("ace_text-input");
      return false;
   }
   
   private Session session_;
   private EventBus eventBus_;
   private GlobalToolbar toolbar_;
   private GlobalDisplay globalDisplay_;
   Provider<UIPrefs> pUIPrefs_;
   private ApplicationServerOperations server_;
   private IgnoredUpdates ignoredUpdates_;
   private boolean ignoredUpdatesDirty_ = false;
   private ApplicationQuit appQuit_; 
   private HandlerRegistration keyListener_;
}
