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

import org.rstudio.core.client.ElementIds;
import org.rstudio.core.client.command.AppCommand;
import org.rstudio.core.client.resources.ImageResource2x;
import org.rstudio.core.client.theme.res.ThemeResources;
import org.rstudio.core.client.theme.res.ThemeStyles;
import org.rstudio.core.client.widget.ToolbarButton;
import org.rstudio.core.client.widget.ToolbarMenuButton;
import org.rstudio.core.client.widget.ToolbarPopupMenu;
import org.rstudio.studio.client.RStudioGinjector;
import org.rstudio.studio.client.application.StudioClientApplicationConstants;
import org.rstudio.studio.client.application.Desktop;
import org.rstudio.studio.client.application.events.EventBus;
import org.rstudio.studio.client.application.model.ProductEditionInfo;
import org.rstudio.studio.client.common.GlobalProgressDelayer;
import org.rstudio.studio.client.projects.ProjectMRUList;
import org.rstudio.studio.client.projects.model.ProjectMRUEntry;
import org.rstudio.studio.client.projects.model.ProjectsServerOperations;
import org.rstudio.studio.client.projects.model.SharedProjectDetails;
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.WorkbenchListManager;
import org.rstudio.studio.client.workbench.commands.Commands;
import org.rstudio.studio.client.workbench.events.UpdateWindowTitleEvent;
import org.rstudio.studio.client.workbench.model.Session;
import org.rstudio.studio.client.workbench.model.SessionInfo;
import org.rstudio.studio.client.workbench.prefs.model.UserPrefs;

import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.JsArray;
import com.google.gwt.dom.client.Document;
import com.google.gwt.dom.client.Element;
import com.google.gwt.event.logical.shared.AttachEvent;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.resources.client.ClientBundle;
import com.google.gwt.resources.client.ImageResource;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.ui.MenuItem;
import com.google.inject.Inject;
import com.google.inject.Provider;

public class ProjectPopupMenu extends ToolbarPopupMenu
{
   /**
    *
    * @param sessionInfo
    * @param commands
    * @param instance suffix appended to element identifier to disambiguate multiple
    *                 instances of this control in the UI
    */
   public ProjectPopupMenu(SessionInfo sessionInfo, Commands commands, String instance)
   {
      RStudioGinjector.INSTANCE.injectMembers(this);
      
      commands_ = commands;
      instance_ = instance;
      
      activeProjectFile_ = sessionInfo.getActiveProjectFile();
   }
   
   @Inject
   void initialize(ProjectMRUList mruList,
                   ProjectsServerOperations server,
                   EventBus events,
                   Session session,
                   ProductEditionInfo editionInfo,
                   Provider<UserPrefs> pUserPrefs,
                   Provider<WorkbenchListManager> pWorkbenchLists,
                   WorkbenchContext workbenchContext)
   {
      server_ = server;
      events_ = events;
      mruList_ = mruList;
      allowSharedProjects_ = session.getSessionInfo().getAllowOpenSharedProjects();
      editionInfo_ = editionInfo;
      pUserPrefs_ = pUserPrefs;
      workbenchContext_ = workbenchContext;
      pWorkbenchLists_ = pWorkbenchLists;
   }
   
   public ToolbarButton getToolbarButton()
   {
      if (toolbarButton_ == null)
      {
         toolbarButton_ = new ToolbarMenuButton(
                getProjectDisplayName(),
                ToolbarButton.NoTitle,
                new ImageResource2x(RESOURCES.projectMenu2x()),
                this, 
                true);
         ElementIds.assignElementId(toolbarButton_, ElementIds.PROJECT_MENUBUTTON + "_" + instance_);

         if (activeProjectFile_ != null)
         {
            toolbarButton_.setTitle(activeProjectFile_);

            // also set the doc title so the browser tab carries the project
            if (!Desktop.isDesktop())
               setDocumentTitle();

            // Update UI when the project display name changes.
            // Tied to the toolbar button lifecycle (session-long)
            // rather than the popup lifecycle (transient).
            projectNameHandler_ = pUserPrefs_.get().projectName().addValueChangeHandler(valueChangeEvent ->
            {
               if (!Desktop.isDesktop())
                  setDocumentTitle();

               String title = workbenchContext_.createWindowTitle();
               if (title != null)
                  events_.fireEventToAllSatellites(new UpdateWindowTitleEvent(title));

               ProjectMRUEntry entry = new ProjectMRUEntry(activeProjectFile_, valueChangeEvent.getValue());
               pWorkbenchLists_.get().getProjectNameMruList().updateExtraData(entry.getMRUValue());

               if (toolbarButton_ != null)
                  toolbarButton_.setText(getProjectDisplayName());
            });

            toolbarButton_.addAttachHandler(event ->
            {
               if (!event.isAttached() && projectNameHandler_ != null)
               {
                  projectNameHandler_.removeHandler();
                  projectNameHandler_ = null;
               }
            });
         }
        
          if (activeProjectFile_ == null)
          {
             toolbarButton_.addStyleName(
                   ThemeResources.INSTANCE.themeStyles().emptyProjectMenu());
          }
      }
       
       return toolbarButton_;
   }

   private void setDocumentTitle()
   {
      // put project title first so it isn't cut off when there are many tabs
      Document.get().setTitle(getProjectDisplayName() + " \u00b7 " + editionInfo_.editionName());
   }

   private String getProjectDisplayName()
   {
      if (activeProjectFile_ != null)
      {
         if (pUserPrefs_.get().projectName().getValue().length() > 0)
            return pUserPrefs_.get().projectName().getValue();
         else
            return mruList_.getQualifiedLabel(activeProjectFile_);

      } else {
         return constants_.toolBarButtonText();
      }
   }

   @Override
   protected ToolbarMenuBar createMenuBar()
   {
      return new ProjectsPopupMenuBar();
   }
   
   private class ProjectsPopupMenuBar extends ToolbarMenuBar
   {
      public ProjectsPopupMenuBar()
      {
         super(true);
      }
      
      @Override
      public void onBrowserEvent(Event event) {
        Element element = DOM.eventGetTarget(event);
        switch (DOM.eventGetType(event)) {
          case Event.ONCLICK: {
             if (element.getClassName() == ThemeStyles.INSTANCE.menuRightImage())
                ProjectMRUList.setOpenInNewWindow(true);
          }
        }
        super.onBrowserEvent(event);
      }
   }
   
   @Override
   public void getDynamicPopupMenu(final DynamicPopupMenuCallback callback)
   {
      ProjectMRUList.setOpenInNewWindow(false);
      if (allowSharedProjects_)
      {
         final GlobalProgressDelayer progress = new GlobalProgressDelayer(
               RStudioGinjector.INSTANCE.getGlobalDisplay(),
               250, constants_.popupMenuProgressMessage());

         // if shared projects are on, check for them every time the user drops
         // the menu; we request one more than the maximum we can display so 
         // we can let the user know whether there are more projects than those
         // that can be displayed in the menu
         server_.getSharedProjects(MAX_SHARED_PROJECTS + 1, 
               new ServerRequestCallback<JsArray<SharedProjectDetails>>()
         {
            @Override
            public void onResponseReceived(JsArray<SharedProjectDetails> result)
            {
               rebuildMenu(result, callback);
               progress.dismiss();
            }

            @Override
            public void onError(ServerError error)
            {
               // if we can't get the shared projects, we can at least show
               // the menu without them
               rebuildMenu(null, callback);
               progress.dismiss();
            }
         });
      }
      else
      {
         rebuildMenu(null, callback);
      }
   }

   interface Resources extends ClientBundle
   {
      @Source("projectMenu_2x.png")
      ImageResource projectMenu2x();
   }
   
   private void rebuildMenu(final JsArray<SharedProjectDetails> sharedProjects,
         DynamicPopupMenuCallback callback)
   {
      // clean out existing entries
      clearItems();

      boolean hasSharedProjects = sharedProjects != null && 
                                  sharedProjects.length() > 0;

      int maxMruEntries = MAX_MRU_ENTRIES;

      // shared projects are always shown, and count against the MRU limit
      if (hasSharedProjects)
         maxMruEntries -= Math.min(sharedProjects.length(), 
               MAX_SHARED_PROJECTS);
      
      addItem(commands_.newProject().createMenuItem(false));

      // ensure the menu doesn't get too narrow
      addSeparator(225);

      addItem(commands_.openProject().createMenuItem(false));
      if (Desktop.hasDesktopFrame())
         addItem(commands_.openProjectInNewWindow().createMenuItem(false));
      addItem(commands_.closeProject().createMenuItem(false));
      addSeparator();
      addItem(commands_.shareProject().createMenuItem(false));
      if (hasSharedProjects)
         addSeparator(constants_.recentProjectsLabel());
      else
         addSeparator();

      // add as many MRU items as is appropriate for our screen size and number
      // of shared projects
      AppCommand[] mruCommands = new AppCommand[] {
         commands_.projectMru0(),
         commands_.projectMru1(),
         commands_.projectMru2(),
         commands_.projectMru3(),
         commands_.projectMru4(),
         commands_.projectMru5(),
         commands_.projectMru6(),
         commands_.projectMru7(),
         commands_.projectMru8(),
         commands_.projectMru9(),
         commands_.projectMru10(),
         commands_.projectMru11(),
         commands_.projectMru12(),
         commands_.projectMru13(),
         commands_.projectMru14()
      };
      
      for (int i = 0; i < Math.min(maxMruEntries, mruCommands.length); i++)
      {
         addItem(mruCommands[i].createMenuItem(false));
      }
      
      // show shared projects if enabled
      if (hasSharedProjects)
      {
         addSeparator(constants_.sharedWithMeLabel());
         for (int i = 0; i < Math.min(sharedProjects.length(),
               MAX_SHARED_PROJECTS); i ++)
         {
            final SharedProjectDetails details = sharedProjects.get(i);

            String menuHtml = AppCommand.formatMenuLabel(
                  null, details.getName(), false, null, 
                  commands_.openHtmlExternal().getImageResource(), 
                  ProjectMRUList.NEW_SESSION_DESC);
            addItem(new MenuItem(menuHtml, true, () ->
            {
               ProjectMRUList.openProjectFromMru(events_, details.getProjectFile());
            }));
         }
         
         // if there are more shared projects on the server than those we 
         // displayed, offer a link
         if (sharedProjects.length() > MAX_SHARED_PROJECTS)
         {
            addItem(commands_.openSharedProject().createMenuItem(false));
         }
      }

      addSeparator();
      addItem(commands_.clearRecentProjects().createMenuItem(false));
      addSeparator();
      addItem(commands_.projectOptions().createMenuItem(false));
      
      callback.onPopupMenu(this);
   }

   private static final Resources RESOURCES = GWT.create(Resources.class);
   private static final int MAX_SHARED_PROJECTS = 5;
   private static final int MAX_MRU_ENTRIES = 10;
   private final String activeProjectFile_;
   private ToolbarMenuButton toolbarButton_ = null;
   private HandlerRegistration projectNameHandler_;

   private ProjectMRUList mruList_;
   private final Commands commands_;
   private final String instance_;
   private EventBus events_;
   private ProjectsServerOperations server_;
   private boolean allowSharedProjects_ = false;
   private ProductEditionInfo editionInfo_;
   private static final StudioClientApplicationConstants constants_ = GWT.create(StudioClientApplicationConstants.class);
   private Provider<UserPrefs> pUserPrefs_;
   private Provider<WorkbenchListManager> pWorkbenchLists_;
   private WorkbenchContext workbenchContext_;
}
