/*
 * jNPad v0.3 - jNPad's an Simple Text Editor written in Java
 *
 * Copyright (C) 2014-2017  rgs
 *
 * Require JDK 1.6 (or later)
 *
 * 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.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 *
 *
 * Info, Questions, Suggestions & Bugs Report to rgsevero@gmail.com
 */

package jnpad.ui.toolbar;

import java.awt.Component;
import java.awt.Dimension;
import java.awt.GridLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

import javax.swing.AbstractAction;
import javax.swing.AbstractButton;
import javax.swing.Action;
import javax.swing.Icon;
import javax.swing.JButton;
import javax.swing.JPopupMenu;
import javax.swing.JToggleButton;
import javax.swing.JToolBar;
import javax.swing.SwingConstants;

import jnpad.ui.icon.ArrowIcon;
import jnpad.util.Utilities;

/**
 * The Class JNPadToolBar.
 *
 * @version 0.3
 * @since   jNPad v0.1
 */
public class JNPadToolBar extends JToolBar {
  private NavigationButton  _navigationButton;
  private List<Component>   l_hiddenComponents;

  /** UID */
  private static final long serialVersionUID = -7494045572706190958L;

  /**
   * Instantiates a new jNPad tool bar.
   */
  public JNPadToolBar() {
    this(HORIZONTAL);
  }

  /**
   * Instantiates a new jNPad tool bar.
   *
   * @param orientation the orientation
   */
  public JNPadToolBar(int orientation) {
    this(null, orientation);
  }

  /**
   * Instantiates a new jNPad tool bar.
   *
   * @param name the name
   */
  public JNPadToolBar(String name) {
    this(name, HORIZONTAL);
  }

  /**
   * Instantiates a new jNPad tool bar.
   *
   * @param name the name
   * @param orientation the orientation
   */
  public JNPadToolBar(String name, int orientation) {
    super(name, orientation);
    //setRollover(true);
    _navigationButton = new NavigationButton(this);
    addComponentListener(new ResizeListener());
    addPropertyChangeListener("orientation", new OrientationListener()); //$NON-NLS-1$
  }

  /**
   * Gets the minimum size.
   *
   * @return the minimum size
   * @see javax.swing.JComponent#getMinimumSize()
   */
  @Override
  public Dimension getMinimumSize() {
    return new Dimension(0, 0);
  }

  /**
   * Sets the orientation.
   *
   * @param orientation the new orientation
   * @see javax.swing.JToolBar#setOrientation(int)
   */
  @Override
  public void setOrientation(int orientation) {
    super.setOrientation(orientation);
    if (_navigationButton != null)
      _navigationButton.setOrientation(orientation);
  }

  /**
   * Update ui.
   *
   * @see javax.swing.JToolBar#updateUI()
   */
  @Override
  public void updateUI() {
    super.updateUI();
    /*try {
      SwingUtilities.updateComponentTreeUI(_navigationButton);
    }
    catch (Exception ex) {
    }*/
  }

  /**
   * Gets the navigation button.
   *
   * @return JButton
   */
  public JButton getNavigationButton() {
    return _navigationButton;
  }

  /**
   * Adds the impl.
   *
   * @param comp the comp
   * @param constraints the constraints
   * @param index the index
   * @see javax.swing.JToolBar#addImpl(java.awt.Component, java.lang.Object, int)
   */
  @Override
  protected void addImpl(Component comp, Object constraints, int index) {
    if (comp instanceof NavigationButton)
      _navigationButton = (NavigationButton) comp;
    super.addImpl(comp, constraints, index);
  }

  /**
   * Gets the overflow components.
   *
   * @return the overflow components
   */
  public Component[] getOverflowComponents() {
    return l_hiddenComponents.toArray(new Component[l_hiddenComponents.size()]);
  }

  //////////////////////////////////////////////////////////////////////////////
  /**
   * The Class OrientationListener.
   */
  private class OrientationListener implements PropertyChangeListener {
    /**
     * Property change.
     *
     * @param e the PropertyChangeEvent
     * @see java.beans.PropertyChangeListener#propertyChange(java.beans.PropertyChangeEvent)
     */
    @Override
    public void propertyChange(PropertyChangeEvent e) {
      if (_navigationButton != null)
        _navigationButton.setOrientation(getOrientation());
    }
  }

  //////////////////////////////////////////////////////////////////////////////

  //////////////////////////////////////////////////////////////////////////////
  /**
   * The Class ResizeListener.
   */
  private class ResizeListener extends ComponentAdapter {
    /**
     * Component resized.
     *
     * @param e the ComponentEvent
     * @see java.awt.event.ComponentAdapter#componentResized(java.awt.event.ComponentEvent)
     */
    @Override
    public void componentResized(ComponentEvent e) {
      super.componentResized(e);
      if (l_hiddenComponents != null) {
        remove(_navigationButton);
        for (Component l_hiddenComponent : l_hiddenComponents)
          add(l_hiddenComponent);
        l_hiddenComponents = null;
      }

      boolean flag = false;
      switch (JNPadToolBar.this.getOrientation()) {
        case SwingConstants.VERTICAL:
          int height = getHeight() - _navigationButton.getHeight();
          _navigationButton.setLocation(_navigationButton.getWidth(), height);
          for (int k = 0; k < getComponentCount() && !flag; k++) {
            Component c = getComponent(k);
            if (c != _navigationButton &&
                c.getY() + c.getHeight() > getHeight())
              flag = true;
          }
          if (flag) {
            l_hiddenComponents = new ArrayList<Component>();
            // --------
            for (int jj = 0; jj < getComponentCount();) {
              Component c = getComponent(jj);
              if (c != _navigationButton &&
                  c.getY() + c.getHeight() > height) {
                l_hiddenComponents.add(c);
                remove(c);
              }
              else {
                jj++;
              }
            }
            // ---------

            remove(_navigationButton);
            add(_navigationButton);
            validate();
          }
          break;

        case SwingConstants.HORIZONTAL:
          int width = getWidth() - _navigationButton.getWidth();
          _navigationButton.setLocation(width, _navigationButton.getHeight());
          for (int k = 0; k < getComponentCount() && !flag; k++) {
            Component c = getComponent(k);
            if (c != _navigationButton &&
                c.getX() + c.getWidth() > getWidth())
              flag = true;
          }
          if (flag) {
            l_hiddenComponents = new ArrayList<Component>();
            // --------
            for (int jj = 0; jj < getComponentCount();) {
              Component c = getComponent(jj);
              if (c != _navigationButton &&
                  c.getX() + c.getWidth() > width) {
                l_hiddenComponents.add(c);
                remove(c);
              }
              else {
                jj++;
              }
            }
            // ---------

            remove(_navigationButton);
            add(_navigationButton);
            validate();
          }
          break;
        default: //Keep FindBugs happy
          break;
      }
    }
  }
  //////////////////////////////////////////////////////////////////////////////
}

//////////////////////////////////////////////////////////////////////////////
/**
 * The Class NavigationButton.
 */
class NavigationButton extends ToolBarButton {
  private JNPadToolBar      _toolBar;

  private static Icon       iiVertical       = new ArrowIcon(SwingConstants.SOUTH);
  private static Icon       iiHorizontal     = new ArrowIcon(SwingConstants.EAST);

  /** UID */
  private static final long serialVersionUID = 2934778029232424274L;

  /**
   * Instantiates a new navigation button.
   *
   * @param toolbar the toolbar
   */
  public NavigationButton(JNPadToolBar toolbar) {
    _toolBar = toolbar;
    setAction(new NavigationAction());
    setOrientation(toolbar.getOrientation());
  }

  /**
   * Sets the orientation.
   *
   * @param orientation the new orientation
   */
  void setOrientation(int orientation) {
    switch (orientation) {
      case SwingConstants.HORIZONTAL:
        setIcon(iiHorizontal);
        setMargin(new Insets(2, 0, 2, 0));
        break;
      case SwingConstants.VERTICAL:
        setIcon(iiVertical);
        setMargin(new Insets(0, 2, 0, 2));
        break;
      default: //Keep FindBugs happy
        break;
    }
  }

  /**
   * Popup.
   */
  private void popup() {
    Component[] components = _toolBar.getOverflowComponents();

    PopupToolBox popuptoolbox = new PopupToolBox(components.length, 1,
                                                 isRolloverEnabled());

    for (Component component : components) {
      if (component instanceof AbstractButton) {
        AbstractButton button = (AbstractButton) component;
        popuptoolbox.add(button);
      }
    }

    final JPopupMenu popup = new JPopupMenu();
    MouseAdapter mouse_adapter = new MouseAdapter() {
      @Override
      public void mouseClicked(final MouseEvent e) {
        popup.setVisible(false);
      }
    };

    popuptoolbox.setButtonMouseListener(mouse_adapter);
    popuptoolbox.rebuild();
    popup.add(popuptoolbox);
    popup.show(this, 0, getHeight());
  }

  //////////////////////////////////////////////////////////////////////////////
  /**
   * The Class NavigationAction.
   */
  private class NavigationAction extends AbstractAction {
    /** UID */
    private static final long serialVersionUID = -4171959259495145172L;

    /**
     * Instantiates a new navigation action.
     */
    NavigationAction() {
      //super(" ");
    }

    /**
     * Action performed.
     *
     * @param e the ActionEvent
     * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
     */
    @Override
    public void actionPerformed(ActionEvent e) {
      popup();
    }
  }
  //////////////////////////////////////////////////////////////////////////////
}

//////////////////////////////////////////////////////////////////////////////

//////////////////////////////////////////////////////////////////////////////
/**
 * The Class PopupToolBox.
 */
class PopupToolBox extends JToolBar {
  private List<AbstractButton> l_buttons        = new ArrayList<AbstractButton>();
  //private List l_actions = new ArrayList();
  private MouseListener        _mouseListener;
  private int                  _rows;
  private int                  _cols;

  /** UID */
  private static final long    serialVersionUID = -260339599596718508L;

  /**
   * Instantiates a new popup tool box.
   *
   * @param rows int
   * @param cols int
   * @param rollover boolean
   */
  public PopupToolBox(int rows, int cols, boolean rollover) {
    setRollover(rollover);
    _rows = rows;
    _cols = cols;
    setLayout(new GridLayout(_rows, _cols));
    setFloatable(false);
  }

  /**
   * Adds the.
   *
   * @param button AbstractButton
   * @return AbstractButton
   */
  public AbstractButton add(AbstractButton button) {
    AbstractButton toolbutton;
    if (button instanceof JButton) {
      toolbutton = new ToolBoxButton((JButton) button);
    }
    else if (button instanceof JToggleButton) {
      toolbutton = new ToolBoxToggleButton((JToggleButton) button);
    }
    else {
      toolbutton = button;
    }
    super.add(toolbutton);
    l_buttons.add(toolbutton);
    return toolbutton;
  }

  /**
   * Sets the button mouse listener.
   *
   * @param mouseListener the new button mouse listener
   */
  public void setButtonMouseListener(MouseListener mouseListener) {
    _mouseListener = mouseListener;
  }

  /**
   * Rebuild.
   */
  public void rebuild() {
    super.removeAll();
    setLayout(new GridLayout(_rows, _cols)); // evita bug al pasar de LAFs
    for (AbstractButton button : l_buttons) {
      super.add(button);
      if (_mouseListener != null)
        button.addMouseListener(_mouseListener);
    }
  }
}

//////////////////////////////////////////////////////////////////////////////

//////////////////////////////////////////////////////////////////////////////
/**
 * The Class ToolBoxButton.
 */
class ToolBoxButton extends ToolBarButton {
  JButton                        _reference;
  Action                         _action;
  private PropertyChangeListener _actionPropertyChangeListener;

  /** UID */
  private static final long      serialVersionUID = -4210047031620478770L;

  /**
   * Instantiates a new tool box button.
   *
   * @param reference JButton
   */
  ToolBoxButton(JButton reference) {
    _reference = reference;

    // set icon
    Icon icon = reference.getIcon();
    if (icon != null)
      setIcon(icon);

    // set text
    String text = reference.getText();
    if (text != null)
      setText(text);

    // set tooltip
    String tooltip = reference.getToolTipText();
    if (Utilities.isEmptyString(tooltip))
      tooltip = reference.getText();
    setToolTipText(tooltip);

    // set description
    if (reference instanceof ToolBarButton) {
      String desc = ((ToolBarButton) reference).getDescription();
      if (desc != null)
        setDescription(desc);
    }

    // set enabled
    setEnabled(reference.isEnabled());

    // set margin
    setMargin(reference.getMargin());

    // add action listener
    addActionListener(new ActionListenerImpl());

    // handle action
    if (_action != null && _actionPropertyChangeListener != null) {
      _action.removePropertyChangeListener(_actionPropertyChangeListener);
      _actionPropertyChangeListener = null;
    }
    _action = reference.getAction();
    if (_action != null) {
      _action.addPropertyChangeListener(_actionPropertyChangeListener = new PropertyChangeListenerImpl());
    }
  }

  //////////////////////////////////////////////////////////////////////////////
  private class PropertyChangeListenerImpl implements PropertyChangeListener, Serializable { // Serializable keep FindBugs happy 
    /** UID */
    private static final long serialVersionUID = -276468994510092616L;

    /**
     * Property change.
     *
     * @param e PropertyChangeEvent
     * @see java.beans.PropertyChangeListener#propertyChange(java.beans.PropertyChangeEvent)
     */
    @Override
    public void propertyChange(final PropertyChangeEvent e) {
      if ("enabled".equals(e.getPropertyName())) { //$NON-NLS-1$
        setEnabled(((Boolean) e.getNewValue()).booleanValue());
      }
    }
  }
  //////////////////////////////////////////////////////////////////////////////
  
  //////////////////////////////////////////////////////////////////////////////
  /**
   * The Class ActionListenerImpl.
   */
  private class ActionListenerImpl implements ActionListener {
    /**
     * Action performed.
     *
     * @param e ActionEvent
     * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
     */
    @Override
    public void actionPerformed(final ActionEvent e) {
      for (ActionListener listener : _reference.getActionListeners()) {
        listener.actionPerformed(new ActionEvent(_reference, 
                                                 e.getID(),
                                                 e.getActionCommand(), 
                                                 e.getWhen(), 
                                                 e.getModifiers()));
      }
    }
  }
  //////////////////////////////////////////////////////////////////////////////

}

//////////////////////////////////////////////////////////////////////////////

//////////////////////////////////////////////////////////////////////////////
/**
 * The Class ToolBoxToggleButton.
 */
class ToolBoxToggleButton extends ToolBarToggleButton {
  JToggleButton                  _reference;
  Action                         _action;
  private PropertyChangeListener _actionPropertyChangeListener;

  /** UID */
  private static final long      serialVersionUID = -7301613799735578041L;

  /**
   * Instantiates a new tool box toggle button.
   *
   * @param reference JToggleButton
   */
  ToolBoxToggleButton(JToggleButton reference) {
    _reference = reference;

    Icon icon = reference.getIcon();
    if (icon != null)
      setIcon(icon);

    String text = reference.getText();
    if (text != null)
      setText(text);

    String tooltip = reference.getToolTipText();
    if (Utilities.isEmptyString(tooltip))
      tooltip = reference.getText();
    setToolTipText(tooltip);

    if (reference instanceof ToolBarToggleButton) {
      String desc = ((ToolBarToggleButton) reference).getDescription();
      if (desc != null)
        setDescription(desc);
    }

    setEnabled(reference.isEnabled());
    setSelected(reference.isSelected());
    setMargin(reference.getMargin());

    addActionListener(new ActionListenerImpl());
    addItemListener(new ItemListenerImpl());

    // ---
    if (_action != null && _actionPropertyChangeListener != null) {
      _action.removePropertyChangeListener(_actionPropertyChangeListener);
      _actionPropertyChangeListener = null;
    }
    _action = reference.getAction();
    if (_action != null) {
      _action.addPropertyChangeListener(_actionPropertyChangeListener = new PropertyChangeListenerImpl());
    }
    // ---
  }

  //////////////////////////////////////////////////////////////////////////////
  private class PropertyChangeListenerImpl implements PropertyChangeListener, Serializable { // Serializable keep FindBugs happy 
    /** UID */
    private static final long serialVersionUID = -276468994510092616L;

    /**
     * Property change.
     *
     * @param e PropertyChangeEvent
     * @see java.beans.PropertyChangeListener#propertyChange(java.beans.PropertyChangeEvent)
     */
    @Override
    public void propertyChange(final PropertyChangeEvent e) {
      if ("enabled".equals(e.getPropertyName())) { //$NON-NLS-1$
        setEnabled(((Boolean) e.getNewValue()).booleanValue());
      }
      else if ("selected".equals(e.getPropertyName())) { //$NON-NLS-1$
        setSelected(((Boolean) e.getNewValue()).booleanValue());
      }
    }
  }
  //////////////////////////////////////////////////////////////////////////////

  //////////////////////////////////////////////////////////////////////////////
  /**
   * The Class ActionListenerImpl.
   */
  private class ActionListenerImpl implements ActionListener {
    /**
     * Action performed.
     *
     * @param e ActionEvent
     * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
     */
    @Override
    public void actionPerformed(final ActionEvent e) {
      _reference.setSelected(ToolBoxToggleButton.this.isSelected());
      for (ActionListener listener : _reference.getActionListeners()) {
        listener.actionPerformed(new ActionEvent(_reference, 
                                                 e.getID(),
                                                 e.getActionCommand(), 
                                                 e.getWhen(), 
                                                 e.getModifiers()));
      }
    }
  }

  //////////////////////////////////////////////////////////////////////////////

  //////////////////////////////////////////////////////////////////////////////
  /**
   * The Class ItemListenerImpl.
   */
  private class ItemListenerImpl implements ItemListener {
    /**
     * Item state changed.
     *
     * @param e ItemEvent
     * @see java.awt.event.ItemListener#itemStateChanged(java.awt.event.ItemEvent)
     */
    @Override
    public void itemStateChanged(final ItemEvent e) {
      for (ItemListener listener : _reference.getItemListeners()) {
        listener.itemStateChanged(new ItemEvent(_reference, 
                                                e.getID(),
                                                e.getItem(), 
                                                e.getStateChange()));
      }
    }
  }
  //////////////////////////////////////////////////////////////////////////////

}
//////////////////////////////////////////////////////////////////////////////
