package yi.iu.imgdb.desc.gui;

import javax.swing.*;
import com.borland.jbcl.layout.*;
import java.awt.*;
import java.util.*;
import java.awt.event.*;
import javax.swing.border.*;
import java.awt.event.InputEvent;
import yi.gui.helper.*;

/**
 * MultiSelectPanel implements a visual component which provides multiple
 * selections.  An example is to use it in image description application.  A
 * keyword for an image is a selections.  Multiple selections are listed for
 * user to choose to describe an image.  User can also enter new keyword or
 * delete an wrong keyword.
 *
 * It maintains a selection event list.  Those whose want to monitor the changes
 * of the multiple selections can register itself to that list by calling
 * addSelectionListener and implementing the interface of SelectionListener.
 */

public class MultiSelectPanel extends JPanel implements ActionListener {
  final static Integer UNKNOWN = new Integer(0);
  final static Integer SELECTED = new Integer(1);
  final static Integer UNSELECTED = new Integer(-1);
  final static Color UNKNOWN_COLOR = Color.gray;


  /**
   * Store the mapping from each selection to the corresponding checkbox
   */
  HashMap m_selection2checkbox = new HashMap();

  /**
   * Store the mapping from each  to the corresponding checkboxselection
   */
  HashMap m_checkbox2selection = new HashMap();

  /**
   * The check boxs
   */
  ArrayList m_checkBoxs = new ArrayList();

  /**
   * Store the checkbox status
   */
  HashMap m_checkBoxStatus = new HashMap();

  JTextField addTextField = new JTextField();
  JPanel listPanel = new JPanel();
  BoxLayout2 listPaneBoxLayout = new BoxLayout2();
  JScrollPane scrollPane = new JScrollPane();
  BorderLayout multiSelectPanelBorderLayout = new BorderLayout();
  private transient Vector selectionListeners;
  Border border1;
  Border border2;

  public MultiSelectPanel() {
    try {
      jbInit();
    }
    catch(Exception e) {
      e.printStackTrace();
    }
  }

  private void jbInit() throws Exception {

    // set up the "add" text field
    // add listener to "add" text field
    border1 = BorderFactory.createCompoundBorder(BorderFactory.createBevelBorder(BevelBorder.LOWERED,Color.white,Color.white,new Color(93, 93, 93),new Color(134, 134, 134)),BorderFactory.createEmptyBorder(4,4,4,4));
    addTextField.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        String name = e.getActionCommand();
        System.out.println("command:" +name);
        addSelection(name, SELECTED);
        fireSelectionSelected(new SelectionEvent(MultiSelectPanel.this), name);
        addTextField.setText(null);
        addTextField.repaint();
      }
    });

    // popup menu
    JPopupMenu popupMenu = new JPopupMenu();

    // Delete
    JMenuItem delMenuItem = new JMenuItem("Delete");
    delMenuItem.addActionListener(this);
    popupMenu.add(delMenuItem);

    // Enable showing by binding popup to frame
    m_popupMouseListener = new JPopupMenuShower(popupMenu);



    // set up the list panel
    // add container listener to list panel, so that we can moniter
    // everything added to it
    listPanel.addContainerListener(new ContainerAdapter() {
      public void componentAdded(ContainerEvent e) {
        Component c = e.getChild();
        if (c instanceof JCheckBox) {
          JCheckBox checkbox = (JCheckBox)c;
          checkbox.addActionListener(MultiSelectPanel.this);
//          System.out.println("listener added");
          checkbox.addMouseListener (m_popupMouseListener);
        }
//        System.out.println("component added");
      }
    });
    listPaneBoxLayout.setAxis(BoxLayout.Y_AXIS);
    listPanel.setLayout(listPaneBoxLayout);
    listPanel.setOpaque(false);

    // set up the scroll pane
    // scrollPane.setBorder(null);
    scrollPane.getViewport().add(listPanel, null);

    // setup the multiple selection panel
    this.setLayout(multiSelectPanelBorderLayout);
    this.add(scrollPane,  BorderLayout.CENTER);
    this.add(addTextField, BorderLayout.NORTH);

  }

  MouseListener m_popupMouseListener;

  /**
   * Add a selection.
   * @param selection
   * @param status
   */
  public void addSelection(String selection, Integer status) {
    boolean isAdded = addCheckBox(selection, status);
    if (isAdded) redraw();
  }

  /**
   * Add multiple selections and set them to the specified status
   * @param selections
   * @param status
   */
  public void addSelections(Collection selections, Integer status) {
    boolean isAdded = false;
    Iterator iter = selections.iterator();
    while (iter.hasNext()) {
      String selection = (String)iter.next();
      if (addCheckBox(selection, status)) isAdded = true;
    }
    if (isAdded) redraw();
  }

  /**
   * Add the checkbox; update the hashmaps.  Don't repaint the whole pane though
   * since the caller may want to add multiple checkboxs before repainting.
   * Users should invoke addSelection(s) instead of calling this method.
   * @param selection
   * @param status
   * @return
   */
  private boolean addCheckBox(String selection, Integer status) {
    boolean isAdded = false;
    JCheckBox checkbox = (JCheckBox)m_selection2checkbox.get(selection);
    if (checkbox == null) {
      isAdded = true;
//      System.out.println("add  new checkbox : " + selection);
      // new a checkbox
      checkbox = new JCheckBox(selection);
      m_checkBoxs.add(checkbox);

      // remember the mappings
      m_selection2checkbox.put(selection, checkbox);
      m_checkbox2selection.put(checkbox, selection);
    }

    m_checkBoxStatus.put(checkbox, status);
    setCheckBoxAppearance(checkbox, status);
    return isAdded;
  }

  public void removeSelection(String selection) {
    if (removeCheckBox(selection)) redraw();
  }

  public void removeAllSelectons() {
    m_checkbox2selection.clear();
    m_selection2checkbox.clear();
    m_checkBoxStatus.clear();
    m_checkBoxs.clear();
    repaint();
  }

  private boolean removeCheckBox(String selection) {
    boolean isRemoved = false;

    // find the corresponding checkbox
    JCheckBox checkbox = (JCheckBox)m_selection2checkbox.get(selection);
    if (checkbox != null) {

      m_checkBoxs.remove(checkbox);
      m_checkBoxStatus.remove(selection);

      // remove the mappings
      m_selection2checkbox.remove(selection);
      m_selection2checkbox.remove(checkbox);

      isRemoved = true;
    }

    return isRemoved;
  }


  public void setStatusForAll(Integer status) {
    Iterator iter = m_checkBoxs.iterator();
    while (iter.hasNext()) {
      JCheckBox checkbox = (JCheckBox)iter.next();
      m_checkBoxStatus.put(checkbox, status);
      setCheckBoxAppearance(checkbox, status);
      checkbox.repaint();
    }
  }

  private void setCheckBoxAppearance(JCheckBox checkbox, Integer status) {
    if (status.compareTo(SELECTED) == 0) {
      checkbox.setForeground(Color.black);
      checkbox.setSelected(true);
    } else if (status.compareTo(UNSELECTED) == 0) {
      checkbox.setForeground(Color.black);
      checkbox.setSelected(false);
    } else {  // unknown
      checkbox.setForeground(UNKNOWN_COLOR);
      checkbox.setSelected(true);
    }
  }

  private void setCheckBoxAppearance(String selection, Integer status) {
    JCheckBox checkbox = (JCheckBox)m_selection2checkbox.get(selection);
    setCheckBoxAppearance(checkbox, status);
  }

  public void sortCheckboxs() {
    listPanel.removeAll();
    Collections.sort(m_checkBoxs, new Comparator() {
      public int compare(Object o1, Object o2) {
        Integer i1 = (Integer)m_checkBoxStatus.get(o1);
        Integer i2 = (Integer)m_checkBoxStatus.get(o2);
        String s1 = (String)m_checkbox2selection.get(o1);
        String s2 = (String)m_checkbox2selection.get(o2);
        if (i1.compareTo(i2) != 0) return -i1.compareTo(i2);
        return s1.compareTo(s2);
      }
    });
    Iterator iter = m_checkBoxs.iterator();
    while (iter.hasNext()) {
      JCheckBox checkbox = (JCheckBox)iter.next();
      listPanel.add(checkbox);
    }
  }



  public void redraw() {
    sortCheckboxs();
    scrollPane.validate();
    listPanel.repaint();
  }

  public void actionPerformed(ActionEvent e) {
    System.out.println("here action");
    Object source = e.getSource();
    if (source instanceof JCheckBox) {
      JCheckBox checkbox = (JCheckBox)source;
      Integer status = syncCheckBoxStatus(checkbox);
      setCheckBoxAppearance(checkbox, status);
      checkbox.repaint();
      if (status.compareTo(SELECTED)==0) {
        fireSelectionSelected(new SelectionEvent(this),
                           (String)m_checkbox2selection.get(checkbox));
      } else if (status.compareTo(UNSELECTED)==0) {
        fireSelectionUnselected(new SelectionEvent(this),
                           (String)m_checkbox2selection.get(checkbox));
      }

    } else if (e.getActionCommand().compareTo("Delete")==0) {
      // deal with pop up menu
      System.err.println("delete command received from" + source.getClass().getName());

      int result = JOptionPane.showConfirmDialog(this,
        "<html><P>Deleting a keyword will delete it from all the related descriptions.<br>" +
        "To just remove the keyword from the descriptions of the current<br> " +
        "image, use checkbox instead.<br></P>" +
        "<P>Are you sure you want to delete it?</P></html>",
        "Warning", JOptionPane.YES_NO_OPTION);
      if (result == JOptionPane.NO_OPTION) {
        return;
      }

      JMenuItem menuitem = (JMenuItem)source;
      JPopupMenu popup =(JPopupMenu)menuitem.getParent();

      JCheckBox checkbox =  (JCheckBox) popup.getInvoker();
      fireSelectionRemoved(new SelectionEvent(this), (String)m_checkbox2selection.get(checkbox));
    }
  }

  /**
   * Synchornize the status of checkboxs stored in m_checkBoxStatus.  (specially
   * after user click on checkboxs
   * @param selection
   * @return
   */
  private Integer syncCheckBoxStatus(JCheckBox checkbox) {
    if (checkbox == null) {
      System.err.println("syncCheckBoxStatus can't find the corresponding checkbox");
      return null;
    }
    Color color = checkbox.getForeground();
    boolean isSelected = checkbox.isSelected();
    Integer status = isSelected ? ((color == UNKNOWN_COLOR) ? UNKNOWN : SELECTED) : UNSELECTED;
    m_checkBoxStatus.put(checkbox, status);
    return status;
  }

  public HashMap getSelectionStatus() {
    HashMap status = new HashMap();
    Iterator iter = m_checkBoxStatus.keySet().iterator();
    while (iter.hasNext()) {
      Object checkbox = iter.next();
      Object selection = m_checkbox2selection.get(checkbox);
      status.put(selection, m_checkBoxStatus.get(checkbox));
    }
    return status;
  }

  public void setEnabled(boolean isEnabled) {
    super.setEnabled(isEnabled);
    addTextField.setEnabled(isEnabled);
    Iterator iter = m_checkBoxs.iterator();
    while (iter.hasNext()) {
      JCheckBox checkbox = (JCheckBox)iter.next();
      checkbox.setEnabled(isEnabled);
    }
  }
  public synchronized void removeSelectionListener(SelectionListener l) {
    if (selectionListeners != null && selectionListeners.contains(l)) {
      Vector v = (Vector) selectionListeners.clone();
      v.removeElement(l);
      selectionListeners = v;
    }
  }
  public synchronized void addSelectionListener(SelectionListener l) {
    Vector v = selectionListeners == null ? new Vector(2) : (Vector) selectionListeners.clone();
    if (!v.contains(l)) {
      v.addElement(l);
      selectionListeners = v;
    }
  }
  protected void fireSelectionSelected(SelectionEvent e, String selection) {
    if (selectionListeners != null) {
      Vector listeners = selectionListeners;
      int count = listeners.size();
      for (int i = 0; i < count; i++) {
        ((SelectionListener) listeners.elementAt(i)).selectionSelected(e, selection);
      }
    }
  }

  protected void fireSelectionUnselected(SelectionEvent e, String selection) {
    if (selectionListeners != null) {
      Vector listeners = selectionListeners;
      int count = listeners.size();
      for (int i = 0; i < count; i++) {
        ((SelectionListener) listeners.elementAt(i)).selectionUnselected(e, selection);
      }
    }
  }

  protected void fireSelectionRemoved(SelectionEvent e, String selection) {
    if (selectionListeners != null) {
      Vector listeners = selectionListeners;
      int count = listeners.size();
      for (int i = 0; i < count; i++) {
        ((SelectionListener) listeners.elementAt(i)).selectionRemoved(e, selection);
      }
    }
  }


}