/*
 * Copyright (C) 2012-14 Manindra Moharana <me@mkmoharana.com>
 *
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free Software
 * Foundation; either version 2 of the License, or (at your option) any later
 * version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
 * Place - Suite 330, Boston, MA 02111-1307, USA.
 */

package processing.mode.experimental;
import static processing.mode.experimental.ExperimentalMode.log;
import static processing.mode.experimental.ExperimentalMode.log2;
import static processing.mode.experimental.ExperimentalMode.logE;

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.FontMetrics;
import java.awt.Point;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;

import javax.swing.DefaultListModel;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.ListSelectionModel;
import javax.swing.SwingUtilities;
import javax.swing.text.BadLocationException;

import processing.app.syntax.JEditTextArea;

/**
 * Manages the actual suggestion popup that gets displayed
 * @author Manindra Moharana <me@mkmoharana.com>
 *
 */
public class CompletionPanel {
  
  /**
   * The completion list generated by ASTGenerator
   */
  private JList completionList;

  /**
   * The popup menu in which the suggestion list is shown
   */
  private JPopupMenu popupMenu;

  /**
   * Partial word which triggered the code completion and which needs to be completed
   */
  private String subWord;

  /**
   * Postion where the completion has to be inserted
   */
  private int insertionPosition;

  private TextArea textarea;

  /**
   * Scroll pane in which the completion list is displayed
   */
  private JScrollPane scrollPane;
  
  protected DebugEditor editor;

  /**
   * Triggers the completion popup
   * @param textarea
   * @param position - insertion position(caret pos)
   * @param subWord - Partial word which triggered the code completion and which needs to be completed
   * @param items - completion candidates
   * @param location - Point location where popup list is to be displayed
   * @param dedit
   */
  public CompletionPanel(final JEditTextArea textarea, int position, String subWord,
                         DefaultListModel items, final Point location, DebugEditor dedit) {
    this.textarea = (TextArea) textarea;
    editor = dedit;
    this.insertionPosition = position;
    if (subWord.indexOf('.') != -1)
      this.subWord = subWord.substring(subWord.lastIndexOf('.') + 1);
    else
      this.subWord = subWord;
    popupMenu = new JPopupMenu();
    popupMenu.removeAll();
    popupMenu.setOpaque(false);
    popupMenu.setBorder(null);
    scrollPane = new JScrollPane();
    scrollPane.setViewportView(completionList = createSuggestionList(position, items));
    popupMenu.add(scrollPane, BorderLayout.CENTER);
    popupMenu.setPopupSize(280, setHeight(items.getSize())); //TODO: Eradicate this evil
    this.textarea.errorCheckerService.getASTGenerator()
        .updateJavaDoc((CompletionCandidate) completionList.getSelectedValue());
    textarea.requestFocusInWindow();
    popupMenu.show(textarea, location.x, textarea.getBaseline(0, 0)
        + location.y);
    //log("Suggestion shown: " + System.currentTimeMillis());
  }

  public boolean isVisible() {
    return popupMenu.isVisible();
  }
  
  public void setVisible(boolean v){
    //log("Pred popup visible.");
    popupMenu.setVisible(v);
  }
  
  private int setHeight(int itemCount){
    FontMetrics fm = textarea.getFontMetrics(textarea.getFont());
    float h = (fm.getHeight() + (fm.getDescent()) * 0.5f) * (itemCount);
    if (scrollPane.getHorizontalScrollBar().isVisible())
      h += scrollPane.getHorizontalScrollBar().getHeight() + fm.getHeight()
          + (fm.getDescent() + fm.getAscent()) * 0.8f;
    // 0.5f and 0.8f scaling give respectable results.
    //log("popup height " + Math.min(250,h) 
    //+ scrollPane.getHorizontalScrollBar().isVisible());
    return Math.min(250, (int) h); // popup menu height
  }
  
  /*TODO: Make width dynamic
   protected int setWidth(){
    if(scrollPane.getVerticalScrollBar().isVisible()) return 280;
    float min = 280;
    FontMetrics fm = textarea.getFontMetrics(textarea.getFont()); 
    for (int i = 0; i < completionList.getModel().getSize(); i++) {
      float h = fm.stringWidth(completionList.getModel().getElementAt(i).toString());
      min = Math.min(min, h);
    }
    min += fm.stringWidth("             ");
    log("popup width " + Math.min(280,min));
    return Math.min(280,(int)min); // popup menu height
  }*/

  /**
   * Created the popup list to be displayed 
   * @param position
   * @param items
   * @return
   */
  private JList createSuggestionList(final int position,
                                    final DefaultListModel items) {

    JList list = new JList(items);
    //list.setBorder(BorderFactory.createLineBorder(Color.DARK_GRAY, 1));
    list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
    list.setSelectedIndex(0);
    list.addMouseListener(new MouseAdapter() {
      @Override
      public void mouseClicked(MouseEvent e) {
        if (e.getClickCount() == 2) {
          insertSelection();
          hide();
        }
      }
    });
    list.setCellRenderer(new CustomListRenderer());
    list.setFocusable(false);
    return list;
  }
  
  // possibly defunct
  public boolean updateList(final DefaultListModel items, String newSubword,
                            final Point location, int position) {
    this.subWord = new String(newSubword);
    if (subWord.indexOf('.') != -1)
      this.subWord = subWord.substring(subWord.lastIndexOf('.') + 1);
    insertionPosition = position;
    SwingUtilities.invokeLater(new Runnable() {
      @Override
      public void run() {
        scrollPane.getViewport().removeAll();
        completionList.setModel(items);
        completionList.setSelectedIndex(0);
        scrollPane.setViewportView(completionList);
        popupMenu.setPopupSize(popupMenu.getSize().width, setHeight(items.getSize()));
        //log("Suggestion updated" + System.nanoTime());
        textarea.requestFocusInWindow();
        popupMenu.show(textarea, location.x, textarea.getBaseline(0, 0)
            + location.y);
        completionList.validate();
        scrollPane.validate();
        popupMenu.validate();
      }
    });
    return true;
  }

  /**
   * Inserts the CompletionCandidate chosen from the suggestion list
   * 
   * @return
   */
  public boolean insertSelection() {
    if (completionList.getSelectedValue() != null) {
      try {
        // If user types 'abc.', subword becomes '.' and null is returned
        String currentSubword = fetchCurrentSubword();
        int currentSubwordLen = currentSubword == null ? 0 : currentSubword
            .length();
        //logE(currentSubword + " <= subword,len => " + currentSubword.length());
        String selectedSuggestion = ((CompletionCandidate) completionList
            .getSelectedValue()).getCompletionString();
        
        if (currentSubword != null) {
          selectedSuggestion = selectedSuggestion.substring(currentSubwordLen);
        } else {
          currentSubword = "";
        }
        
        logE(subWord + " <= subword, Inserting suggestion=> "
            + selectedSuggestion + " Current sub: " + currentSubword);
        if (currentSubword.length() > 0) {
          textarea.getDocument().remove(insertionPosition - currentSubwordLen,
                                        currentSubwordLen);
        }
        
        textarea.getDocument()
            .insertString(insertionPosition - currentSubwordLen,
                          ((CompletionCandidate) completionList
                              .getSelectedValue()).getCompletionString(), null);
        if (selectedSuggestion.endsWith(")")) {
          if (!selectedSuggestion.endsWith("()")) {
            int x = selectedSuggestion.indexOf('(');
            if (x != -1) {
              //log("X................... " + x);
              textarea.setCaretPosition(insertionPosition + (x + 1));
            }
          }
        } else {
          textarea.setCaretPosition(insertionPosition
              + selectedSuggestion.length());
        }
        //log("Suggestion inserted: " + System.currentTimeMillis());
        return true;
      } catch (BadLocationException e1) {
        e1.printStackTrace();
      }
      catch (Exception e) {
        e.printStackTrace();
      }
      hide();
    }
    return false;
  }
  
  private String fetchCurrentSubword() {
    //log("Entering fetchCurrentSubword");
    TextArea ta = editor.ta;
    int off = ta.getCaretPosition();
    //log2("off " + off);
    if (off < 0)
      return null;
    int line = ta.getCaretLine();
    if (line < 0)
      return null;
    String s = ta.getLineText(line);
    //log2("lin " + line);
    //log2(s + " len " + s.length());

    int x = ta.getCaretPosition() - ta.getLineStartOffset(line) - 1, x1 = x - 1;
    if(x >= s.length() || x < 0)
      return null; //TODO: Does this check cause problems? Verify.
    log2(" x char: " + s.charAt(x));
    //int xLS = off - getLineStartNonWhiteSpaceOffset(line);    

    String word = (x < s.length() ? s.charAt(x) : "") + "";
    if (s.trim().length() == 1) {
    //      word = ""
    //          + (keyChar == KeyEvent.CHAR_UNDEFINED ? s.charAt(x - 1) : keyChar);
          //word = (x < s.length()?s.charAt(x):"") + "";
      word = word.trim();
      if (word.endsWith("."))
        word = word.substring(0, word.length() - 1);
      
      return word;
    }
    //log("fetchCurrentSubword 1 " + word);
    if(word.equals(".")) return null; // If user types 'abc.', subword becomes '.'
    //    if (keyChar == KeyEvent.VK_BACK_SPACE || keyChar == KeyEvent.VK_DELETE)
    //      ; // accepted these keys
    //    else if (!(Character.isLetterOrDigit(keyChar) || keyChar == '_' || keyChar == '$'))
    //      return null;
    int i = 0;

    while (true) {
      i++;
      //TODO: currently works on single line only. "a. <new line> b()" won't be detected
      if (x1 >= 0) {
//        if (s.charAt(x1) != ';' && s.charAt(x1) != ',' && s.charAt(x1) != '(')
        if (Character.isLetterOrDigit(s.charAt(x1)) || s.charAt(x1) == '_') {

          word = s.charAt(x1--) + word;

        } else {
          break;
        }
      } else {
        break;
      }
      if (i > 200) {
        // time out!
        break;
      }
    }
    //    if (keyChar != KeyEvent.CHAR_UNDEFINED)
    //log("fetchCurrentSubword 2 " + word);
    if (Character.isDigit(word.charAt(0)))
      return null;
    word = word.trim();
    if (word.endsWith("."))
      word = word.substring(0, word.length() - 1);
    //log("fetchCurrentSubword 3 " + word);
    //showSuggestionLater();
    return word;
    //}
  }

  /**
   * Hide the suggestion list
   */
  public void hide() {
    popupMenu.setVisible(false);
    //log("Suggestion hidden" + System.nanoTime());
    //textarea.errorCheckerService.getASTGenerator().jdocWindowVisible(false);
  }

  /**
   * When up arrow key is pressed, moves the highlighted selection up in the list
   */
  public void moveUp() {
    if (completionList.getSelectedIndex() == 0) {
      scrollPane.getVerticalScrollBar().setValue(scrollPane.getVerticalScrollBar().getMaximum());
      selectIndex(completionList.getModel().getSize() - 1);
      return;
    } else {
      int index = Math.max(completionList.getSelectedIndex() - 1, 0);
      selectIndex(index);
    }
    int step = scrollPane.getVerticalScrollBar().getMaximum()
        / completionList.getModel().getSize();
    scrollPane.getVerticalScrollBar().setValue(scrollPane
                                                   .getVerticalScrollBar()
                                                   .getValue()
                                                   - step);
    textarea.errorCheckerService.getASTGenerator()
        .updateJavaDoc((CompletionCandidate) completionList.getSelectedValue());

  }

  /**
   * When down arrow key is pressed, moves the highlighted selection down in the list
   */
  public void moveDown() {
    if (completionList.getSelectedIndex() == completionList.getModel().getSize() - 1) {
      scrollPane.getVerticalScrollBar().setValue(0);
      selectIndex(0);
      return;
    } else {
      int index = Math.min(completionList.getSelectedIndex() + 1, completionList.getModel()
          .getSize() - 1);
      selectIndex(index);
    }
    textarea.errorCheckerService.getASTGenerator()
        .updateJavaDoc((CompletionCandidate) completionList.getSelectedValue());
    int step = scrollPane.getVerticalScrollBar().getMaximum()
        / completionList.getModel().getSize();
    scrollPane.getVerticalScrollBar().setValue(scrollPane
                                                   .getVerticalScrollBar()
                                                   .getValue()
                                                   + step);
  }

  private void selectIndex(int index) {
    completionList.setSelectedIndex(index);
//      final int position = textarea.getCaretPosition();
//      SwingUtilities.invokeLater(new Runnable() {
//        @Override
//        public void run() {
//          textarea.setCaretPosition(position);
//        };
//      });
  }
  
  
  /**
   * Custom cell renderer to display icons along with the completion candidates 
   * @author Manindra Moharana <me@mkmoharana.com>
   *
   */
  private class CustomListRenderer extends
      javax.swing.DefaultListCellRenderer {
    //protected final ImageIcon classIcon, fieldIcon, methodIcon;    
   
    public Component getListCellRendererComponent(JList list, Object value,
                                                  int index,
                                                  boolean isSelected,
                                                  boolean cellHasFocus) {
      JLabel label = (JLabel) super.getListCellRendererComponent(list, value,
                                                                 index,
                                                                 isSelected,
                                                                 cellHasFocus);
      if (value instanceof CompletionCandidate) {
        CompletionCandidate cc = (CompletionCandidate) value;
        switch (cc.getType()) {
        case CompletionCandidate.LOCAL_VAR:
          label.setIcon(editor.dmode.localVarIcon);
          break;
        case CompletionCandidate.LOCAL_FIELD:
        case CompletionCandidate.PREDEF_FIELD:
          label.setIcon(editor.dmode.fieldIcon);
          break;
        case CompletionCandidate.LOCAL_METHOD:
        case CompletionCandidate.PREDEF_METHOD:
          label.setIcon(editor.dmode.methodIcon);
          break;
        case CompletionCandidate.LOCAL_CLASS:
        case CompletionCandidate.PREDEF_CLASS:
          label.setIcon(editor.dmode.classIcon);
          break;

        default:
          log("(CustomListRenderer)Unknown CompletionCandidate type " + cc.getType());
          break;
        }

      }
      else
        log("(CustomListRenderer)Unknown CompletionCandidate object " + value);
      
      return label;
    }
  }
  
}