package usda.weru.util;

//TODO: figure out a way not to use protected Sun classes
import com.sun.java.swing.plaf.motif.MotifComboBoxUI;
import com.sun.java.swing.plaf.windows.WindowsComboBoxUI;

import java.awt.Color;
import javax.swing.event.PopupMenuListener;
import javax.swing.event.PopupMenuEvent;
import javax.swing.event.TreeExpansionEvent;
import javax.swing.plaf.basic.ComboPopup;
import javax.swing.plaf.metal.MetalComboBoxUI;
import javax.swing.border.EmptyBorder;
import java.awt.BorderLayout;
import java.awt.Cursor;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.util.Map;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JList;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JTree;
import javax.swing.ScrollPaneConstants;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.event.TreeExpansionListener;
import javax.swing.event.TreeWillExpandListener;
import javax.swing.plaf.ComboBoxUI;
import javax.swing.plaf.basic.BasicComboBoxUI;
import javax.swing.tree.*;
import org.apache.log4j.Logger;

public class WepsTreeComboBox extends JComponent{
    private static final Logger LOGGER = Logger.getLogger(WepsTreeComboBox.class);
    public static final String PROP_MODEL = "model";
    public static final String PROP_SELECTED_ITEM = "selectedItem";

    protected JComboBox c_comboBox = null;
    protected JTree c_tree = null;
    protected LazyLoadingTreeController c_treeController;
    protected Object c_selectedItem;
    
    protected boolean c_selectOnLossOfFocus = false;
    
    protected Map <Object, TreePath> c_pathCache;

    public WepsTreeComboBox() {
        setLayout(new BorderLayout());
        initializeComboBox();
        initializeTree();

    }

    public WepsTreeComboBox(TreeModel model) {
        this();
        setModel(model);
    }
    
    public WepsTreeComboBox(TreeNode root) {
        this(new DefaultTreeModel(root));        
    }
    
    private void initializeComboBox() {
        c_comboBox = new JComboBox() {

            public void updateUI() {
                super.updateUI();
                ComboBoxUI cui = (ComboBoxUI) UIManager.getUI(this);
                if (cui instanceof MetalComboBoxUI) {
                    cui = new MetalTreeComboBoxUI();
                } else if (cui instanceof MotifComboBoxUI) {
                    cui = new MotifTreeComboBoxUI();
                } else if (cui instanceof WindowsComboBoxUI) {
                    cui = new WindowsTreeComboBoxUI();
                }
                setUI(cui);

            }
        };

        c_comboBox.setMaximumRowCount(14);

        add(c_comboBox);
    }
        

    private void initializeTree() {
        c_tree = new JTree();
        c_treeController = new LazyLoadingTreeController(c_tree);

        c_tree.setVisibleRowCount(8);
        c_tree.setShowsRootHandles(true);
    }

    
    public JTree getTree() {
        return c_tree;
    }
    
    public JComboBox getComboBox(){
        return c_comboBox;
    }   
    

    public void setModel(TreeModel model) {
        TreeModel old = c_tree.getModel();
        c_pathCache = null;
        c_tree.setModel(model);
        firePropertyChange(PROP_MODEL, old, c_tree.getModel());
    }
    
    public TreeModel getModel(){
        return c_tree.getModel();
    }
    
    protected void setComboSelectedItem(Object item){
        c_comboBox.removeAllItems();
        c_comboBox.addItem(item);
        c_comboBox.setSelectedItem(item);
    }
    
     
    protected void setTreeSelectedPath(TreePath path){
        c_tree.setSelectionPath(path);        
    }
    
    protected void setSelectedItem(Object item){
        Object old = c_selectedItem;
        c_selectedItem = item;
        //firePropertyChange(PROP_SELECTED_ITEM, null, c_selectedItem);        
        firePropertyChange(PROP_SELECTED_ITEM, null, c_selectedItem);        
    }  
    
    public void setSelectedPath(TreePath path){
        if (path != null){
            Object item = path.getLastPathComponent();
            setTreeSelectedPath(path);        
            setComboSelectedItem(item);
            setSelectedItem(item);        
        }
        else{
            c_tree.setSelectionRow(-1);
            c_comboBox.removeAllItems();  
            setSelectedItem(null);
        }
    }
    
   
    public Object getSelectedItem(){
        return c_selectedItem;
    }
    
    public void setSelectOnLossOfFocus(boolean select){
        c_selectOnLossOfFocus = select;
    }

    @Override
    public void setCursor(Cursor cursor) {
        super.setCursor(cursor);
        c_tree.setCursor(cursor);
        c_comboBox.setCursor(cursor);
    }

    @Override
    public void setEnabled(boolean enabled) {
        super.setEnabled(enabled);
        c_tree.setEnabled(enabled);
        c_comboBox.setEnabled(enabled);
    }
    
    
    
    //pass through methods
    
    public void setRootVisible(boolean rootVisible){
        c_tree.setRootVisible(rootVisible);        
    }
            
    public boolean isRootVisible(){
        return c_tree.isRootVisible();
    }
        
    public boolean isPopupVisible(){
        return c_comboBox.isPopupVisible();
    }
    
    public void setPopupVisible(boolean visible){
        c_comboBox.setPopupVisible(visible);
    }
        
    
    public void showPopup(){
        c_comboBox.showPopup();
    }
    
    public void hidePopup(){
        c_comboBox.hidePopup();
    }

    @Override
    public void setBackground(Color bg) {
        super.setBackground(bg);
        c_comboBox.setBackground(bg);
        c_tree.setBackground(bg);        
    }

    @Override
    public void setForeground(Color fg) {
        super.setForeground(fg);
        c_comboBox.setForeground(fg);
        c_tree.setForeground(fg);
    }        
 
    

    class TreePopup implements ComboPopup, MouseMotionListener, MouseListener, KeyListener, PopupMenuListener, TreeExpansionListener, TreeWillExpandListener, FocusListener {

        protected JScrollPane c_scroller;
        protected JComboBox c_comboBox;
        protected JPopupMenu c_popup;        
        protected boolean c_mouseInside = false;
        protected final JList list = new JList();   //required for the ComboPopup interface, this class does nothing with it but return it.

        public TreePopup(JComboBox comboBox) {
            c_comboBox = comboBox;
            initializePopup();
        }

        protected void initializePopup() {
            c_scroller = new JScrollPane();

            //never show the horizontal scrollbar, the popup will automagically resize through updatePopup();
            c_scroller.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);

            c_popup = new JPopupMenu();
            c_popup.setLightWeightPopupEnabled(false);
            c_popup.setLayout(new BorderLayout());
            c_popup.setBorder(new EmptyBorder(0, 0, 0, 0));
            c_popup.addPopupMenuListener(this);
            c_popup.add(c_scroller);
            c_popup.pack();


        }

        protected void updatePopup() {
            int comboWidth = c_comboBox.getWidth() + 1;
            int popupWidth = (int) c_tree.getPreferredSize().getWidth();
            int width = Math.max(comboWidth, popupWidth);

            int height = (int) c_tree.getPreferredScrollableViewportSize().getHeight() * 2;

            c_popup.setPopupSize(width, height);

            //prevent loss of focus
            c_tree.requestFocusInWindow();
            
        }

        @Override
        public void show() {
            c_scroller.setViewportView(c_tree);

            //make sure we're the correct size
            updatePopup();

            if(c_comboBox.isShowing()){
                c_popup.show(c_comboBox, 0, c_comboBox.getHeight());
            }
            else{
                LOGGER.warn("Can't show popup. Parent not shiowing.");
                return;
            }

            TreePath path = c_tree.getSelectionPath();
            if (path != null) {
                c_tree.setSelectionPath(path);                
            }
            else{
                c_tree.setSelectionRow(0);
            }

            //scroll to the currently selected node
            c_tree.scrollPathToVisible(path);


            c_tree.addTreeExpansionListener(this);
            c_tree.addTreeWillExpandListener(this);
            c_tree.addMouseListener(this);
            c_tree.addKeyListener(this);
            c_tree.addFocusListener(this);
            
            //show the popup
            c_popup.setVisible(true);
            
            //make sure we have focus
            c_tree.requestFocus();                        
        }

        @Override
        public void hide() {
            c_popup.setVisible(false);

            //cleanup listeners
            c_tree.removeTreeExpansionListener(this);
            c_tree.removeTreeWillExpandListener(this);
            c_tree.removeMouseListener(this);
            c_tree.removeKeyListener(this);
            c_tree.removeFocusListener(this);
            
        }

        @Override
        public JList getList() {
            return list;
        }

        @Override
        public MouseListener getMouseListener() {
            return this;
        }

        @Override
        public MouseMotionListener getMouseMotionListener() {
            return this;
        }

        @Override
        public KeyListener getKeyListener() {
            return this;
        }

        @Override
        public boolean isVisible() {
            return c_popup.isVisible();
        }

        @Override
        public void uninstallingUI() {
            c_popup.removePopupMenuListener(this);
        }
        // MouseListener
        @Override
        public void mousePressed(MouseEvent e) {
        }

        @Override
        public void mouseReleased(MouseEvent e) {
        }
        // something else registered for MousePressed
        @Override
        public void mouseClicked(MouseEvent e) {
            WepsTreeComboBox.this.processEvent(e);
            if (e.getSource().equals(c_tree)) {
                //tree event
                TreePath path = c_tree.getPathForLocation(e.getX(), e.getY());
                if (path != null) {
                    Object o = path.getLastPathComponent();
                    if (o != null && c_tree.getModel().isLeaf(o)) {
                        //user clicked a leaf, select it!
                        setComboSelectedItem(o);
                        setSelectedItem(o);
                        hide();
                    }
                }

            } else {
                //combo box
                if (e.getClickCount() > 1) {
                    return;
                }
                if (!SwingUtilities.isLeftMouseButton(e)) {
                    return;
                }
                if (!c_comboBox.isEnabled()) {
                    return;
                }
                if (c_comboBox.isEditable()) {
                    c_comboBox.getEditor().getEditorComponent().requestFocusInWindow();
                } else {
                    c_comboBox.requestFocusInWindow();
                }
                togglePopup();
            }
        }

        @Override
        public void mouseEntered(MouseEvent e) {
            if (e.getSource().equals(c_tree)) {
                c_mouseInside = true;
            }
        }

        @Override
        public void mouseExited(MouseEvent e) {
            if (e.getSource().equals(c_tree)) {
                c_mouseInside = false;
            }
        }

        // MouseMotionListener
        @Override
        public void mouseDragged(MouseEvent e) {
        }

        @Override
        public void mouseMoved(MouseEvent e) {
        }

        @Override
        public void keyPressed(KeyEvent e) {
        }

        @Override
        public void keyTyped(KeyEvent e) {
        }

        @Override
        public void keyReleased(KeyEvent e) {
            if (e.getSource().equals(c_tree)){
                if (e.getKeyCode() == KeyEvent.VK_ENTER){
                    TreePath path = c_tree.getSelectionPath();
                    if (path != null) {
                        Object o = path.getLastPathComponent();
                        if (o != null && c_tree.getModel().isLeaf(o)) {
                            //user clicked a leaf, select it!
                            setComboSelectedItem(o);
                            setSelectedItem(o);
                            hide();
                        }
                    }
                }
            }            
        }

        @Override
        public void focusGained(FocusEvent e) {
            
        }

        @Override
        public void focusLost(FocusEvent e) {
            if (c_selectOnLossOfFocus){
                TreePath path = c_tree.getSelectionPath();
                if (path != null) {
                    Object o = path.getLastPathComponent();
                    if (o != null && c_tree.getModel().isLeaf(o)) {
                        //user clicked a leaf, select it!
                        setComboSelectedItem(o);
                        setSelectedItem(o);                            
                    }
                }
                hide();            
            }
        }
        
        

        /**
         * Variables hideNext and mouseInside are used to
         * hide the popupMenu by clicking the mouse in the JComboBox
         */
        @Override
        public void popupMenuCanceled(PopupMenuEvent e) {
        }

        @Override
        public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
            
        }

        @Override
        public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
        }

        protected void togglePopup() {
            if (isVisible()) {
                hide();
            } else {
                show();
            }
        }

        @Override
        public void treeWillExpand(TreeExpansionEvent event) throws ExpandVetoException {
            //begin loading the kids, so show a friendly wait cursor
            c_tree.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
        }

        @Override
        public void treeWillCollapse(TreeExpansionEvent event) throws ExpandVetoException {
            //do nothing
        }

        @Override
        public void treeExpanded(TreeExpansionEvent event) {
            //done loading the kids
            c_tree.setCursor(Cursor.getDefaultCursor());

            //resize
            updatePopup();
        }

        @Override
        public void treeCollapsed(TreeExpansionEvent event) {
            //resize
            updatePopup();
        }
    }
    
    //special ui classes for creating the popup, it's an ugly hack.
    class MetalTreeComboBoxUI extends MetalComboBoxUI {

        @Override
        protected ComboPopup createPopup() {
            return new TreePopup(comboBox);
        }
    }

    class WindowsTreeComboBoxUI extends WindowsComboBoxUI {

        @Override
        protected ComboPopup createPopup() {
            return new TreePopup(comboBox);
        }
    }

    class MotifTreeComboBoxUI extends MotifComboBoxUI {

        @Override
        protected ComboPopup createPopup() {
            return new TreePopup(comboBox);
        }
    }

    class BacicTreeComboBoxUI extends BasicComboBoxUI {

        @Override
        protected ComboPopup createPopup() {
            return new TreePopup(comboBox);
        }
    }
}
