/*
 * 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.tab;

import java.awt.CardLayout;
import java.awt.Component;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CopyOnWriteArraySet;

import javax.swing.Icon;
import javax.swing.JComponent;
import javax.swing.JPanel;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

import jnpad.GUIUtilities;
import jnpad.config.Configurable;
import jnpad.util.Platform;
import jnpad.util.Utilities;

/**
 * The Class CardTabbedPane.
 *
 * @version 0.3
 * @since jNPad 0.1
 */
public class CardTabbedPane extends JPanel implements ITabbedPane {
  
  /** The my tabs. */
  protected List<TabInfo>                       myTabs                      = new ArrayList<TabInfo>();
  
  /** The selected. */
  protected TabInfo                             selected;
  
  /** The my listeners. */
  protected CopyOnWriteArraySet<ChangeListener> myListeners                 = new CopyOnWriteArraySet<ChangeListener>();
  
  /** The _suppress state changed events. */
  private boolean                               _suppressStateChangedEvents = false;
  
  /** The _auto focus on tab hide close. */
  private boolean                               _autoFocusOnTabHideClose    = true;
  
  /** The _suppress set selected index. */
  private boolean                               _suppressSetSelectedIndex   = false;
  
  /** The _auto request focus. */
  private boolean                               _autoRequestFocus           = true;
  
  /** The layout. */
  protected CardLayout                          layout;

  /** The change event. */
  protected transient ChangeEvent               changeEvent                 = null;

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

  /**
   * Instantiates a new card tabbed pane.
   */
  public CardTabbedPane() {
    layout = new CardLayout();
    setLayout(layout);
  }

  /**
   * Adds the change listener.
   *
   * @param listener the listener
   * @see jnpad.ui.tab.ITabbedPane#addChangeListener(javax.swing.event.ChangeListener)
   */
  @Override
  public void addChangeListener(ChangeListener listener) {
    myListeners.add(listener);
  }

  /**
   * Removes the change listener.
   *
   * @param listener the listener
   * @see jnpad.ui.tab.ITabbedPane#removeChangeListener(javax.swing.event.ChangeListener)
   */
  @Override
  public void removeChangeListener(ChangeListener listener) {
    myListeners.remove(listener);
  }

  /**
   * To component.
   *
   * @return JComponent
   * @see jnpad.ui.tab.ITabbedPane#toComponent()
   */
  @Override
  public JComponent toComponent() {
    return this;
  }

  /**
   * Focus on selected component.
   *
   * @see jnpad.ui.tab.ITabbedPane#focusOnSelectedComponent()
   */
  @Override
  public void focusOnSelectedComponent() {
    Component selectedComponent = getSelectedComponent();
    if (selectedComponent != null) {
      GUIUtilities.requestFocus(selectedComponent);
    }
  }

  /**
   * Configure.
   *
   * @param cfg int
   * @see jnpad.config.Configurable#configure(int)
   */
  @Override
  public void configure(int cfg) {
    for (int i = 0; i < getTabCount(); i++) {
      Component c = getComponentAt(i);
      if (c instanceof Configurable)
        ((Configurable) c).configure(cfg);
    }
  }

  /**
   * Next tab.
   *
   * @see jnpad.ui.tab.ITabbedPane#nextTab()
   */
  @Override
  public void nextTab() {
    if (getTabCount() > 1) {
      int i = getSelectedIndex();
      if (i == 0)
        i = getTabCount() - 1;
      else
        i--;
      setSelectedIndex(i);
    }
  }

  /**
   * Previous tab.
   *
   * @see jnpad.ui.tab.ITabbedPane#previousTab()
   */
  @Override
  public void previousTab() {
    if (getTabCount() > 1) {
      int i = getSelectedIndex();
      if (i == getTabCount() - 1)
        i = 0;
      else
        i++;
      setSelectedIndex(i);
    }
  }

  /**
   * Gets the tab count.
   *
   * @return int
   * @see jnpad.ui.tab.ITabbedPane#getTabCount()
   */
  @Override
  public int getTabCount() {
    return myTabs.size();
  }

  /**
   * Gets the tab at.
   * 
   * @param index the index
   * @return the tab at
   */
  private TabInfo getTabAt(int index) {
    checkIndex(index);
    return myTabs.get(index);
  }

  /**
   * Check index.
   * 
   * @param index the index
   */
  private void checkIndex(int index) {
    if (index < 0 || index >= getTabCount()) {
      throw new ArrayIndexOutOfBoundsException("tabCount=" + getTabCount() + " index=" + index); //$NON-NLS-1$ //$NON-NLS-2$
    }
  }

  /**
   * Gets the component at.
   *
   * @param index the index
   * @return Component
   * @see jnpad.ui.tab.ITabbedPane#getComponentAt(int)
   */
  @Override
  public Component getComponentAt(int index) {
    return getTabAt(index).getComponent();
  }

  /**
   * Gets the icon at.
   *
   * @param index the index
   * @return Icon
   * @see jnpad.ui.tab.ITabbedPane#getIconAt(int)
   */
  @Override
  public Icon getIconAt(int index) {
    return getTabAt(index).getIcon();
  }

  /**
   * Gets the selected component.
   *
   * @return Component
   * @see jnpad.ui.tab.ITabbedPane#getSelectedComponent()
   */
  @Override
  public Component getSelectedComponent() {
    return selected != null ? selected.getComponent() : null;
  }

  /**
   * Gets the selected index.
   *
   * @return int
   * @see jnpad.ui.tab.ITabbedPane#getSelectedIndex()
   */
  @Override
  public int getSelectedIndex() {
    return selected != null ? myTabs.indexOf(selected) : -1;
  }

  /**
   * Gets the title at.
   *
   * @param index the index
   * @return String
   * @see jnpad.ui.tab.ITabbedPane#getTitleAt(int)
   */
  @Override
  public String getTitleAt(int index) {
    return getTabAt(index).getTitle();
  }

  /**
   * Index of component.
   *
   * @param c Component
   * @return int
   * @see jnpad.ui.tab.ITabbedPane#indexOfComponent(java.awt.Component)
   */
  @Override
  public int indexOfComponent(Component c) {
    for (int i = 0; i < myTabs.size(); i++) {
      TabInfo tab = myTabs.get(i);
      if (tab.getComponent().equals(c))
        return i;
    }
    return -1;
  }

  /**
   * Insert tab.
   *
   * @param title the title
   * @param icon the icon
   * @param component the component
   * @param tip the tip
   * @param index the index
   * @see jnpad.ui.tab.ITabbedPane#insertTab(java.lang.String, javax.swing.Icon,
   * java.awt.Component, java.lang.String, int)
   */
  @Override
  public void insertTab(String title, Icon icon, Component component, String tip, int index) {
    if (component == null)
      return;

    int newIndex = index;

    // If component already exists, remove corresponding
    // tab so that new tab gets added correctly
    int removeIndex = indexOfComponent(component);
    if (removeIndex != -1) {
      removeTabAt(removeIndex);
      if (newIndex > removeIndex) {
        newIndex--;
      }
    }

    int elementCount = getTabCount();

    if (index > elementCount) {
      throw new ArrayIndexOutOfBoundsException(index + " > " + elementCount); //$NON-NLS-1$

    }

    int selectedIndex = getSelectedIndex();

    if (newIndex == elementCount) {
      myTabs.add(new TabInfo(title != null ? title : Utilities.EMPTY_STRING, icon, component, tip));
    }
    else {
      myTabs.set(newIndex, new TabInfo(title != null ? title : Utilities.EMPTY_STRING, icon, component, tip));
    }
    add(component, tip);

    if (myTabs.size() == 1) {
      setSelectedIndex(0);
    }

    if (selectedIndex >= newIndex) {
      setSelectedIndex(selectedIndex + 1);
    }

    revalidate();
    repaint();
  }

  /**
   * Removes the tab at.
   *
   * @param index the index
   * @see jnpad.ui.tab.ITabbedPane#removeTabAt(int)
   */
  @Override
  public void removeTabAt(int index) {
    checkIndex(index);

    int selectedIndex = getSelectedIndex();
    Component selectedComponent = getSelectedComponent();

    remove(selectedComponent);
    myTabs.remove(index);

    if (selectedIndex > index || // if the selected tab is after the removal
        selectedIndex >= getTabCount()) { // if the selected tab is the last tab
      setSelectedIndex(selectedIndex - 1);
    }
    else if (index == selectedIndex) { // selected index hasn't changed, but the associated tab has
      setSelectedIndex(index >= getTabCount() ? getTabCount() - 1 : index); // [added v0.3]
      //fireStateChanged(); // [removed v0.3]
    }

    revalidate();
    repaint();
  }

  /**
   * Sets the component at.
   *
   * @param index int
   * @param c Component
   * @see jnpad.ui.tab.ITabbedPane#setComponentAt(int, java.awt.Component)
   */
  @Override
  public void setComponentAt(int index, Component c) {
    getTabAt(index).setComponent(c);
  }

  /**
   * Sets the icon at.
   *
   * @param index int
   * @param icon Icon
   * @see jnpad.ui.tab.ITabbedPane#setIconAt(int, javax.swing.Icon)
   */
  @Override
  public void setIconAt(int index, Icon icon) {
    getTabAt(index).setIcon(icon);
  }

  /**
   * Sets the selected component.
   *
   * @param c the new selected component
   * @see jnpad.ui.tab.ITabbedPane#setSelectedComponent(java.awt.Component)
   */
  @Override
  public void setSelectedComponent(Component c) {
    int index = indexOfComponent(c);
    if (index != -1) {
      setSelectedIndex(index);
    }
    else {
      throw new IllegalArgumentException("component not found in tabbed pane"); //$NON-NLS-1$
    }
  }

  /**
   * Sets the selected index.
   *
   * @param index the new selected index
   * @see jnpad.ui.tab.ITabbedPane#setSelectedIndex(int)
   */
  @Override
  public void setSelectedIndex(int index) {
    if (index >= getTabCount() || isSuppressSetSelectedIndex()) {
      return;
    }

    int oldIndex = getSelectedIndex();
    if (oldIndex != index) {
      selected = getTabAt(index);
      layout.show(this, selected.getTooltipText());
      fireStateChanged();
    }
  }

  /**
   * Sets the title at.
   *
   * @param index int
   * @param title String
   * @see jnpad.ui.tab.ITabbedPane#setTitleAt(int, java.lang.String)
   */
  @Override
  public void setTitleAt(int index, String title) {
    getTabAt(index).setTitle(title);
  }

  /**
   * Sets the tool tip text at.
   *
   * @param index int
   * @param toolTipText String
   * @see jnpad.ui.tab.ITabbedPane#setToolTipTextAt(int, java.lang.String)
   */
  @Override
  public void setToolTipTextAt(int index, String toolTipText) {
    getTabAt(index).setTooltipText(toolTipText);
  }

  /**
   * Gets the tool tip text at.
   *
   * @param index the index
   * @return String
   * @see jnpad.ui.tab.ITabbedPane#getToolTipTextAt(int)
   */
  @Override
  public String getToolTipTextAt(int index) {
    return getTabAt(index).getTooltipText();
  }

  /**
   * Checks if is auto request focus.
   * 
   * @return true, if is auto request focus
   */
  @Override
  public boolean isAutoRequestFocus() {
    return _autoRequestFocus;
  }

  /**
   * Sets the auto request focus.
   * 
   * @param autoRequestFocus the new auto request focus
   */
  @Override
  public void setAutoRequestFocus(boolean autoRequestFocus) {
    _autoRequestFocus = autoRequestFocus;
  }

  /**
   * Checks if is suppress set selected index.
   * 
   * @return true, if is suppress set selected index
   */
  @Override
  public boolean isSuppressSetSelectedIndex() {
    return _suppressSetSelectedIndex;
  }

  /**
   * Sets the suppress set selected index.
   * 
   * @param suppressSetSelectedIndex the new suppress set selected index
   */
  @Override
  public void setSuppressSetSelectedIndex(boolean suppressSetSelectedIndex) {
    _suppressSetSelectedIndex = suppressSetSelectedIndex;
  }

  /**
   * Sets the suppress state changed events.
   * 
   * @param suppress the new suppress state changed events
   */
  @Override
  public void setSuppressStateChangedEvents(boolean suppress) {
    _suppressStateChangedEvents = suppress;
  }

  /**
   * Checks if is suppress state changed events.
   * 
   * @return true, if is suppress state changed events
   */
  @Override
  public boolean isSuppressStateChangedEvents() {
    return _suppressStateChangedEvents;
  }

  /**
   * Sets the auto focus on tab hide close.
   * 
   * @param autoFocusOnTabHideClose the new auto focus on tab hide close
   */
  @Override
  public void setAutoFocusOnTabHideClose(boolean autoFocusOnTabHideClose) {
    _autoFocusOnTabHideClose = autoFocusOnTabHideClose;
  }

  /**
   * Checks if is auto focus on tab hide close.
   * 
   * @return true, if is auto focus on tab hide close
   */
  @Override
  public boolean isAutoFocusOnTabHideClose() {
    return _autoFocusOnTabHideClose;
  }

  /**
   * Fire state changed.
   */
  protected void fireStateChanged() {
    if (isSuppressStateChangedEvents())
      return;

    // Lazily create the event:
    if (changeEvent == null)
      changeEvent = new ChangeEvent(this);

    for (ChangeListener l : myListeners) {
      l.stateChanged(changeEvent);
    }
  }

  /**
   * Move selected tab to.
   *
   * @param tabIndex the tab index
   * @see jnpad.ui.tab.ITabbedPane#moveSelectedTabTo(int)
   */
  @Override
  public void moveSelectedTabTo(int tabIndex) {
    int selectedIndex = getSelectedIndex();
    if (selectedIndex == tabIndex ||
        tabIndex < 0 || tabIndex >= getTabCount() ||
        selectedIndex == -1) { // do nothing
      return;
    }

    Component selectedComponent = getComponentAt(selectedIndex);

    boolean old = isAutoRequestFocus();

    boolean shouldChangeFocus = false;
    // we will not let UI to auto request focus so we will have to do it here.
    // if the selected component has focus, we will request it after the tab is moved.
    if (selectedComponent != null) {
      if (GUIUtilities.isAncestorOfFocusOwner(selectedComponent) && isAutoFocusOnTabHideClose()) {
        shouldChangeFocus = true;
      }
    }

    try {
      setSuppressStateChangedEvents(true);
      setAutoRequestFocus(false);

      if (selectedIndex - tabIndex == 1 || tabIndex - selectedIndex == 1) {
        Component frame = getComponentAt(tabIndex);
        String title = getTitleAt(tabIndex);
        String tooltip = getToolTipTextAt(tabIndex);
        Icon icon = getIconAt(tabIndex);
        setSuppressStateChangedEvents(true);
        try {
          if (tabIndex > selectedIndex) {
            insertTab(title, icon, frame, tooltip, selectedIndex);
          }
          else {
            insertTab(title, icon, frame, tooltip, selectedIndex + 1);
          }
        }
        finally {
          setSuppressStateChangedEvents(false);
        }
      }
      else {
        Component frame = getComponentAt(selectedIndex);
        String title = getTitleAt(selectedIndex);
        String tooltip = getToolTipTextAt(selectedIndex);
        Icon icon = getIconAt(selectedIndex);
        setSuppressStateChangedEvents(true);
        try {
          if (tabIndex > selectedIndex) {
            insertTab(title, icon, frame, tooltip, tabIndex + 1);
          }
          else {
            insertTab(title, icon, frame, tooltip, tabIndex);
          }
        }
        finally {
          setSuppressStateChangedEvents(false);
        }
      }

      if (!Platform.isJRE5Above()) {
        // a workaround for Swing bug
        if (tabIndex == getTabCount() - 2) {
          setSelectedIndex(getTabCount() - 1);
        }
      }

      setAutoRequestFocus(old);
      setSelectedIndex(tabIndex);

    }
    finally {
      setSuppressStateChangedEvents(false);
      if (shouldChangeFocus) {
        requestFocusInWindow();
      }
    }
  }

  /**
   * Scroll tab to visible.
   *
   * @param index int
   * @see jnpad.ui.tab.ITabbedPane#scrollTabToVisible(int)
   */
  @Override
  public void scrollTabToVisible(int index) {
    //empty
  }

  /**
   * Gets the tab layout policy.
   *
   * @return int
   * @see jnpad.ui.tab.ITabbedPane#getTabLayoutPolicy()
   */
  @Override
  public int getTabLayoutPolicy() {
    // no importa
    return 0;
  }

  /**
   * Sets the tab layout policy.
   *
   * @param policy int
   * @see jnpad.ui.tab.ITabbedPane#setTabLayoutPolicy(int)
   */
  @Override
  public void setTabLayoutPolicy(int policy) {
    // no importa
  }

  //////////////////////////////////////////////////////////////////////////////
  /**
   * The Class TabInfo.
   */
  public static class TabInfo implements Serializable {
    /** The component. */
    private Component         component;

    /** The icon. */
    private Icon              icon;

    /** The title. */
    private String            title;

    /** The tooltip text. */
    private String            tooltipText;

    /** UID. */
    private static final long serialVersionUID = 2291983360274382291L;

    /**
     * Instantiates a new tab info.
     * 
     * @param title the title
     * @param icon the icon
     * @param component the component
     * @param tooltipText the tooltip text
     */
    TabInfo(String title, Icon icon, Component component, String tooltipText) {
      this.title = title;
      this.icon = icon;
      this.component = component;
      this.tooltipText = tooltipText;
    }

    /**
     * Sets the component.
     * 
     * @param component the new component
     */
    public void setComponent(Component component) {
      this.component = component;
    }

    /**
     * Gets the component.
     * 
     * @return the component
     */
    public Component getComponent() {
      return component;
    }

    /**
     * Sets the icon.
     * 
     * @param icon the new icon
     */
    public void setIcon(Icon icon) {
      this.icon = icon;
    }

    /**
     * Gets the icon.
     * 
     * @return the icon
     */
    public Icon getIcon() {
      return icon;
    }

    /**
     * Sets the title.
     * 
     * @param title the new title
     */
    public void setTitle(String title) {
      this.title = title;
    }

    /**
     * Gets the title.
     * 
     * @return the title
     */
    public String getTitle() {
      return title;
    }

    /**
     * Sets the tooltip text.
     * 
     * @param tooltipText the new tooltip text
     */
    public void setTooltipText(String tooltipText) {
      this.tooltipText = tooltipText;
    }

    /**
     * Gets the tooltip text.
     * 
     * @return the tooltip text
     */
    public String getTooltipText() {
      return tooltipText;
    }

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

}

