/*
 * FilesPane.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.files;

import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.JsArray;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.ScheduledCommand;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.ui.DockLayoutPanel;
import com.google.gwt.user.client.ui.MenuItem;
import com.google.gwt.user.client.ui.Widget;
import com.google.gwt.user.client.ui.PopupPanel.PositionCallback;
import com.google.inject.Inject;
import com.google.inject.Provider;

import org.rstudio.core.client.cellview.ColumnSortInfo;
import org.rstudio.core.client.command.AppCommand;
import org.rstudio.core.client.files.FileSystemItem;
import org.rstudio.core.client.resources.ImageResource2x;
import org.rstudio.core.client.widget.Operation;
import org.rstudio.core.client.widget.OperationWithInput;
import org.rstudio.core.client.widget.ProgressOperationWithInput;
import org.rstudio.core.client.widget.Toolbar;
import org.rstudio.core.client.widget.ToolbarPopupMenu;
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.filetypes.FileTypeRegistry;
import org.rstudio.studio.client.common.icons.StandardIcons;
import org.rstudio.studio.client.server.ServerDataSource;
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.model.RemoteFileSystemContext;
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 org.rstudio.studio.client.workbench.ui.WorkbenchPane;
import org.rstudio.studio.client.workbench.views.console.shell.assist.PopupPositioner;
import org.rstudio.studio.client.workbench.views.files.model.DirectoryListing;
import org.rstudio.studio.client.workbench.views.files.model.FileChange;
import org.rstudio.studio.client.workbench.views.files.model.PendingFileUpload;
import org.rstudio.studio.client.workbench.views.files.ui.*;

import java.util.ArrayList;

public class FilesPane extends WorkbenchPane implements Files.Display
{
   @Inject
   public FilesPane(GlobalDisplay globalDisplay,
                    FileDialogs fileDialogs,
                    Commands commands,
                    EventBus events,
                    FileTypeRegistry fileTypeRegistry,
                    Session session,
                    Provider<FileCommandToolbar> pFileCommandToolbar,
                    Provider<UserPrefs> pPrefs)
   {
      super(constants_.filesTitle(), events);
      globalDisplay_ = globalDisplay;
      commands_ = commands;
      fileDialogs_ = fileDialogs;
      fileTypeRegistry_ = fileTypeRegistry;
      pFileCommandToolbar_ = pFileCommandToolbar;
      pPrefs_ = pPrefs;
      session_ = session;
      ensureWidget();
   }
   
   public void setObserver(Files.Display.Observer observer)
   {
      observer_ = observer;
   }
   
   // implement forwarding (and null-safe) observer for sub-components
   private class DisplayObserverProxy implements Files.Display.Observer 
   {
      public void onFileSelectionChanged()
      {
         if (observer_ != null)
            observer_.onFileSelectionChanged();
         
      }
      public void onFileNavigation(FileSystemItem file)
      {
         if (observer_ != null)
            observer_.onFileNavigation(file);
      }
      
      public void onSelectAllValueChanged(boolean value)
      {
         if (observer_ != null)
            observer_.onSelectAllValueChanged(value);
      }
      
      public void onColumnSortOrderChanaged(
                                    JsArray<ColumnSortInfo> sortOrder)
      {
         if (observer_ != null)
            observer_.onColumnSortOrderChanaged(sortOrder);
      }
   }
   
   @Override
   public void setColumnSortOrder(JsArray<ColumnSortInfo> sortOrder)
   {
      filesList_.setColumnSortOrder(sortOrder);
   }
   
   public void resetColumnWidths()
   {
      filesList_.resetColumnWidths();
   }
    
   public void listDirectory(final FileSystemItem directory, 
                             ServerDataSource<DirectoryListing> dataSource)
   {
      setProgress(true);
        
      dataSource.requestData(new ServerRequestCallback<DirectoryListing>(){
         public void onResponseReceived(DirectoryListing response)
         {
            setProgress(false);
            String lastBrowseable = null;
            if (!response.isParentBrowseable())
            {
               // if we can't go up, disable everything up to the current path
               lastBrowseable = directory.getPath();
            }
            else
            {
               // if we're in someone else's project, disable paths above
               // the project
               SessionInfo si = session_.getSessionInfo();
               if (si.getActiveProjectDir() != null && !si.projectParentBrowseable())
                  lastBrowseable = si.getActiveProjectDir().getPath();
            }
               
            filePathToolbar_.setPath(directory.getPath(), lastBrowseable);
            filesList_.displayFiles(directory, response.getFiles()); 
         }
         public void onError(ServerError error)
         {
            setProgress(false);
            globalDisplay_.showErrorMessage(constants_.fileListingErrorCaption(),
                                            constants_.fileListingErrorMessage(directory.getPath(), error.getUserMessage()));

            if (!directory.equalTo(FileSystemItem.home()))
            {
               observer_.onFileNavigation(FileSystemItem.home());
            }
         } 
      });
   }
   
   public void updateDirectoryListing(FileChange fileAction)
   {
      if (filesList_ != null) // can be called by file_changed event
                             // prior to widget creation
      {
         filesList_.updateWithAction(fileAction);
      }
   }
   
   public void renameFile(FileSystemItem from, FileSystemItem to)
   {
      filesList_.renameFile(from, to);
   }
    
   public void showFolderPicker(
         String caption,
         RemoteFileSystemContext fileSystemContext,
         FileSystemItem initialDir,
         ProgressOperationWithInput<FileSystemItem> operation)
   {
      fileDialogs_.chooseFolder(caption,
                                fileSystemContext,
                                initialDir,
                                operation);
   }
   
   public void showFilePicker(
         String caption,
         RemoteFileSystemContext fileSystemContext,
         FileSystemItem initialFile,
         ProgressOperationWithInput<FileSystemItem> operation)
   {
      fileDialogs_.saveFile(caption, 
            fileSystemContext, 
            initialFile, 
            initialFile.getExtension(), false, 
            operation);
   }
   
   public void showFileUpload(
                     String targetURL,
                     FileSystemItem targetDirectory, 
                     RemoteFileSystemContext fileSystemContext,
                     Operation beginOperation,
                     OperationWithInput<PendingFileUpload> completedOperation,
                     Operation failedOperation)
   {
      FileUploadDialog dlg = new FileUploadDialog(targetURL, 
                                                  targetDirectory,
                                                  fileDialogs_,
                                                  fileSystemContext,
                                                  beginOperation,
                                                  completedOperation,
                                                  failedOperation);
      dlg.showModal();
   } 
   
   public void selectAll()
   {
      filesList_.selectAll();
   }
   
   public void selectNone()
   {
      filesList_.selectNone();
   }
   
   public ArrayList<FileSystemItem> getSelectedFiles()
   {
      return filesList_.getSelectedFiles();
   } 
   
   @Override
   public void showHtmlFileChoice(FileSystemItem file, 
                                  Command onEdit,
                                  Command onBrowse)
   {
       final ToolbarPopupMenu menu = new ToolbarPopupMenu();
       
       String editLabel = AppCommand.formatMenuLabel(
          commands_.renameFile().getImageResource(), constants_.openInEditorLabel(), null);
       String openLabel = AppCommand.formatMenuLabel(
          commands_.openHtmlExternal().getImageResource(), 
          constants_.viewInWebBrowserLabel(),
          null);
       
       menu.addItem(new MenuItem(editLabel, true, onEdit));
       menu.addItem(new MenuItem(openLabel, true, onBrowse));
       
       menu.setPopupPositionAndShow(new PositionCallback() {
          @Override
          public void setPosition(int offsetWidth, int offsetHeight)
          {
             Event event = Event.getCurrentEvent();
             PopupPositioner.setPopupPosition(menu, event.getClientX(), event.getClientY());
          }
       });
   }

   @Override
   public void showDataImportFileChoice(FileSystemItem file, 
                                  Command onView,
                                  Command onImport)
   {
       final ToolbarPopupMenu menu = new ToolbarPopupMenu();
       
       String editLabel = AppCommand.formatMenuLabel(
          commands_.renameFile().getImageResource(), constants_.viewFileLabel(), null);
       String importLabel = AppCommand.formatMenuLabel(
          new ImageResource2x(StandardIcons.INSTANCE.import_dataset2x()), 
          constants_.importDatasetLabel(),
          null);
       
       menu.addItem(new MenuItem(editLabel, true, onView));
       menu.addItem(new MenuItem(importLabel, true, onImport));
       
       menu.setPopupPositionAndShow(new PositionCallback() {
          @Override
          public void setPosition(int offsetWidth, int offsetHeight)
          {
             Event event = Event.getCurrentEvent();
             PopupPositioner.setPopupPosition(menu, event.getClientX(), event.getClientY());
          }
       });
   }
   
   @Override 
   protected Widget createMainWidget()
   {
      filePathToolbar_ = new FilePathToolbar(new DisplayObserverProxy(),
            session_.getSessionInfo().getCloudFolderEnabled());

      // create file list and file progress
      filesList_ = new FilesList(new DisplayObserverProxy(), fileTypeRegistry_,
            pPrefs_.get().sortFileNamesNaturally().getValue() ?
               FilesList.SortOrder.Natural :
               FilesList.SortOrder.Lexicographic);

      // redraw the file listing when the date/time display format changes
      pPrefs_.get().dateTimeUseIso8601().addValueChangeHandler(event -> filesList_.redraw());
      pPrefs_.get().dateTimeUseUtc().addValueChangeHandler(event -> filesList_.redraw());

      DockLayoutPanel dockPanel = new DockLayoutPanel(Unit.PX);
      dockPanel.addNorth(filePathToolbar_, filePathToolbar_.getHeight());
      dockPanel.add(filesList_);
      dockPanel.addStyleName("ace_editor_theme");
      
      // return container
      return dockPanel;
   }

   @Override
   public void onBeforeSelected()
   {
      if (needsInit)
      {
         needsInit = false;
         FileSystemItem home = FileSystemItem.home();
         observer_.onFileNavigation(home);
      }
      else
      {
         Scheduler.get().scheduleDeferred(new ScheduledCommand()
         {
            @Override
            public void execute()
            {
               filesList_.redraw();
            }
         });
      }
   }

   @Override
   protected Toolbar createMainToolbar()
   {
      return pFileCommandToolbar_.get();
   }

   private boolean needsInit = false;
   private FilesList filesList_;
   private FilePathToolbar filePathToolbar_;
   private final GlobalDisplay globalDisplay_;
   private final FileDialogs fileDialogs_;
   private Files.Display.Observer observer_;
   private final Session session_;

   private final FileTypeRegistry fileTypeRegistry_;
   private final Commands commands_;
   private final Provider<FileCommandToolbar> pFileCommandToolbar_;
   private final Provider<UserPrefs> pPrefs_;
   private static final FilesConstants constants_ = GWT.create(FilesConstants.class);
}
