/*
 * CompletionList.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.workbench.views.console.shell.assist;

import com.google.gwt.event.dom.client.*;
import com.google.gwt.event.logical.shared.HasSelectionHandlers;
import com.google.gwt.event.logical.shared.SelectionEvent;
import com.google.gwt.event.logical.shared.SelectionHandler;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.HTMLTable.Cell;
import com.google.gwt.user.client.ui.HTMLTable.CellFormatter;
import com.google.gwt.user.client.ui.ScrollPanel;
import org.rstudio.core.client.Point;
import org.rstudio.core.client.Rectangle;
import org.rstudio.core.client.dom.DomUtils;
import org.rstudio.core.client.events.HasSelectionCommitHandlers;
import org.rstudio.core.client.events.SelectionCommitEvent;
import org.rstudio.core.client.events.SelectionCommitHandler;
import org.rstudio.core.client.widget.FontSizer;
import org.rstudio.studio.client.workbench.views.console.ConsoleResources;

class CompletionList<TItem> extends Composite 
         implements HasSelectionCommitHandlers<TItem>,
                    HasSelectionHandlers<TItem>
{
   public class GridMouseHandler implements ClickHandler, MouseMoveHandler
   {
      public void onClick(ClickEvent event)
      {
         Cell cell = grid_.getCellForEvent(event) ;
         if (cell != null)
         {
            int rowClicked = cell.getRowIndex() ;
            SelectionCommitEvent.fire(CompletionList.this, items_[rowClicked]) ;
         }
      }

      public void onMouseMove(MouseMoveEvent event)
      {
         if (lastMouseMoveCoordinates_ != null)
         {
            if (event.getScreenX() == lastMouseMoveCoordinates_.getX()
                  && event.getScreenY() == lastMouseMoveCoordinates_.getY())
            {
               return;
            }
         }
         lastMouseMoveCoordinates_ = new Point(event.getScreenX(),
                                               event.getScreenY());

         if (firstEvent_)
         {
            // Want to avoid the bug where the cursor happens to be positioned
            // where the popup shows up and thus fires a mouse move event;
            // so even though the user isn't touching the mouse, the selection
            // changes.
            firstEvent_ = false;
            return;
         }

         int mousedOverRow = grid_.getRowForEvent(event) ;
         if (mousedOverRow >= 0)
         {
            setSelectedIndex(mousedOverRow) ;
         }
      }

      private boolean firstEvent_ = true;
      private Point lastMouseMoveCoordinates_;
   }
   
   public CompletionList(TItem[] items,
                         int visibleItems,
                         boolean asHtml,
                         boolean allowVerticalShrink)
   {
      allowVerticalShrink_ = allowVerticalShrink;
      styles_ = ConsoleResources.INSTANCE.consoleStyles();
      GridEx grid = new GridEx(items.length, 1) ;
      for (int i = 0; i < items.length; i++)
      {
         if (asHtml)
            grid.setHTML(i, 0, items[i].toString()) ;
         else
            grid.setText(i, 0, items[i].toString()) ;
      }
      grid.addClickHandler(new GridMouseHandler()) ;
      grid.addMouseMoveHandler(new GridMouseHandler()) ;
      grid.setStylePrimaryName(styles_.completionGrid()) ;
      
      FontSizer.applyNormalFontSize(grid);

      scrollPanel_ = new ScrollPanel() ;
      scrollPanel_.getElement().getStyle().setProperty("overflowX", "hidden");
      scrollPanel_.add(grid) ;
      scrollPanel_.setHeight((visibleItems * 26) + "px") ;
      
      initWidget(scrollPanel_) ;
      grid_ = grid ;
      items_ = items ;
   }
   
   @Override
   protected void onLoad()
   {
      super.onLoad() ;
      int width = grid_.getOffsetWidth() + 20;
      if (maxWidthInPixels_ != null
          && maxWidthInPixels_ > 0
          && maxWidthInPixels_ < width)
         width = maxWidthInPixels_;
      scrollPanel_.setWidth(width + "px") ;
      if (allowVerticalShrink_ &&
          grid_.getOffsetHeight() < scrollPanel_.getOffsetHeight())
      {
         scrollPanel_.setHeight("") ;
      }
      grid_.setWidth("100%") ;
      selectNext() ;
   }

   public int getItemCount()
   {
      if (grid_ != null)
         return grid_.getRowCount() ;
      else
         return 0;
   }
   
   public TItem getSelectedItem()
   {
      int index = getSelectedIndex() ;
      if (index < 0)
         return null ;
      return items_[index] ;
   }
   
   public boolean selectNext()
   {
      return moveSelection(1, true) ;
   }
   
   public boolean selectPrev()
   {
      return moveSelection(-1, true) ;
   }
   
   public boolean selectNextPage()
   {
      return moveSelection(4, false) ;
   }

   public boolean selectPrevPage()
   {
      return moveSelection(-4, false) ;
   }
   
   public boolean selectFirst()
   {
      return moveSelection(-getItemCount(), false) ;
   }
   
   public boolean selectLast()
   {
      return moveSelection(getItemCount(), false) ;
   }

   private boolean moveSelection(int offset, boolean allowWrap)
   {
      if (getItemCount() == 0)
         return false ;
      
      int index = getSelectedIndex() + offset ;
      if (allowWrap)
         index = (index + getItemCount()) % getItemCount() ;
      else
         index = Math.min(getItemCount() - 1, Math.max(0, index)) ;

      setSelectedIndex(index) ;
      
      return true ;
   }

   public HandlerRegistration addSelectionHandler(
         SelectionHandler<TItem> handler)
   {
      return addHandler(handler, SelectionEvent.getType()) ;
   }
   
   public HandlerRegistration addSelectionCommitHandler(
         SelectionCommitHandler<TItem> handler)
   {
      return addHandler(handler, SelectionCommitEvent.getType()) ;
   }
   
   public HTML getDetailedInfoPane()
   {
      return null ;
   }
   
   public int getSelectedIndex()
   {
      return selectedIndex_ ;
   }
   
   public void setSelectedIndex(int index)
   {
      if (selectedIndex_ != index)
      {
         CellFormatter cf = grid_.getCellFormatter() ;
         if (selectedIndex_ >= 0)
            cf.removeStyleName(selectedIndex_, 0, styles_.selected()) ;
         
         selectedIndex_ = index ;
         
         if (index >= 0)
         {
            cf.addStyleName(selectedIndex_, 0, styles_.selected()) ;
            com.google.gwt.dom.client.Element el =
                  DomUtils.getTableCell(grid_.getElement(), index, 0) ;
            DomUtils.ensureVisibleVert(scrollPanel_.getElement(), el, 2) ;
            SelectionEvent.fire(this, getSelectedItem()) ;
         }
      }
   }
   
   /**
    * Gets the rectangle of the selected row in absolute (document-relative)
    * coordinates, or null if nothing is selected.
    */
   public Rectangle getSelectionRect()
   {
      int index = getSelectedIndex() ;
      if (index < 0)
         return null;
      
      com.google.gwt.dom.client.Element el =
            DomUtils.getTableCell(grid_.getElement(), index, 0) ;
      return new Rectangle(el.getAbsoluteLeft(), 
                      el.getAbsoluteTop(), 
                      el.getOffsetWidth(), 
                      el.getOffsetHeight()) ;
   }
   
   public void setMaxWidth(int maxWidthInPixels)
   {
      maxWidthInPixels_ = maxWidthInPixels;
   }
   
   public TItem[] getItems()
   {
      return items_;
   }

   private int selectedIndex_ = -1 ;
   
   private final GridEx grid_ ;
   private final TItem[] items_ ;
   private final ScrollPanel scrollPanel_ ;
   private final ConsoleResources.ConsoleStyles styles_;
   private final boolean allowVerticalShrink_;
   private Integer maxWidthInPixels_;
}
