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

import java.awt.Component;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.MouseEvent;
import java.util.ConcurrentModificationException;
import java.util.Iterator;
import java.util.NoSuchElementException;

import javax.swing.Icon;
import javax.swing.JPopupMenu;
import javax.swing.JTabbedPane;
import javax.swing.SwingUtilities;

import jnpad.config.Config;
import jnpad.text.BufferSet;
import jnpad.text.Buffer;
import jnpad.text.Viewer;
import jnpad.ui.icon.CompositeIcon;
import jnpad.ui.plaf.JNPadTabbedPaneUI;
import jnpad.ui.plaf.LAFUtils;
import jnpad.ui.tab.AbstractTabbedPane;
import jnpad.ui.tab.CloseButtonTabComponent;

/**
 * The Class JNPadTabbedPane.
 *
 * @version 0.3
 * @since   jNPad v0.1
 */
public class JNPadTabbedPane extends AbstractTabbedPane implements BufferSet {
  private JNPadTabbedViewer viewer;
  private JNPadFrame        jNPad;

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

  /**
   * Instantiates a new jNPad tabbed pane.
   *
   * @param viewer the viewer
   */
  public JNPadTabbedPane(JNPadTabbedViewer viewer) {
    this.viewer = viewer;
    this.jNPad = viewer.getJNPad();

    if (LAFUtils.isJNPadLAF() && Config.TAB_ROUNDED.getValue()) {
      setUI(new JNPadTabbedPaneUI());
    }
    
    setFocusable(false);
    setAutoRequestFocus(false);
    setAutoFocusOnTabHideClose(false);

    setTabLayoutPolicy(Config.TAB_LAYOUT_POLICY.getValue());

    setTabPlacement(Config.TAB_PLACEMENT.getValue());
  }

  /**
   * Gets the viewer.
   *
   * @return the viewer
   * @see jnpad.text.BufferSet#getViewer()
   */
  @Override
  public Viewer getViewer() {
    return viewer;
  }
  
  /**
   * Process mouse event.
   *
   * @param e the MouseEvent
   * @see jnpad.ui.tab.AbstractTabbedPane#processMouseEvent(java.awt.event.MouseEvent)
   */
  @Override
  public void processMouseEvent(MouseEvent e) {
    try {
      if (getTabCount() > 0 && (getTabLayoutPolicy() == WRAP_TAB_LAYOUT) && isCloseEnabled(e)) {
        int i = getUI().tabForCoordinate(this, e.getX(), e.getY());
        if ( (i > -1) && (i < getTabCount())) {
          doClose(i);
          return;
        }
      }
    }
    catch (Exception ex) {
      //empty
    }
    super.processMouseEvent(e);
  }
  
  /**
   * Handle mouse clicked.
   *
   * @param e the MouseEvent
   * @see jnpad.ui.tab.AbstractTabbedPane#handleMouseClicked(java.awt.event.MouseEvent)
   */
  @Override
  protected void handleMouseClicked(MouseEvent e) {
    int i = getSelectedIndex();
    if ( (i > -1) && (getTabCount() > i)) {
      Rectangle bounds = getUI().getTabBounds(this, i);
      if (bounds.contains(e.getPoint())) {
        if (SwingUtilities.isLeftMouseButton(e) && e.getClickCount() == 2) {
          expand();
        }
        else if (SwingUtilities.isRightMouseButton(e)) {
          showPopupMenu(e);
        }
      }
      else if (SwingUtilities.isLeftMouseButton(e) && e.getClickCount() == 2) {
        jNPad.newFile();
      }
    }
    else if (SwingUtilities.isLeftMouseButton(e) && e.getClickCount() == 2) {
      jNPad.newFile();
    }
  }
  
  /**
   * Close.
   *
   * @param i int
   * @see jnpad.ui.tab.AbstractTabbedPane#close(int)
   */
  @Override
  protected void close(int i) {
    if ( (i > -1) && (i < getTabCount())) {
      doClose(i);
    }
  }
  
  /**
   * Do close.
   *
   * @param i int
   */
  private void doClose(int i) {
    String path = ( (Buffer) getComponentAt(i)).getFilePath();
    jNPad.closeFile(path, true);
  }

  /**
   * Expand.
   *
   * @see jnpad.ui.tab.AbstractTabbedPane#expand()
   */
  @Override
  protected void expand() {
    //empty
  }

  /**
   * Show popup menu.
   *
   * @param e MouseEvent
   * @see jnpad.ui.tab.AbstractTabbedPane#showPopupMenu(java.awt.event.MouseEvent)
   */
  @Override
  protected void showPopupMenu(final MouseEvent e) {
    JPopupMenu popupMenu = jNPad.createTabbedPopupMenu();
    popupMenu.show(e.getComponent(), e.getX(), e.getY());
  }

  /**
   * Gets the tool tip text.
   *
   * @param e the MouseEvent
   * @return the tool tip text
   * @see javax.swing.JTabbedPane#getToolTipText(java.awt.event.MouseEvent)
   */
  @Override
  public String getToolTipText(MouseEvent e) {
    String str = super.getToolTipText(e);
    if (getTabLayoutPolicy() == WRAP_TAB_LAYOUT) {
      if ( (str != null) && (isReadOnlyCloseIcon(e.getPoint()))) {
        str = JNPadBundle.getString("JNPadTabbedPane.close2", str); //$NON-NLS-1$
      }
      else if ( (str != null) && (isCloseIcon(e.getPoint()))) {
        str = JNPadBundle.getString("JNPadTabbedPane.close1", str); //$NON-NLS-1$
      }
    }
    return str;
  }

  /**
   * Checks if is close enabled.
   *
   * @param e the MouseEvent
   * @return true, if is close enabled
   */
  private boolean isCloseEnabled(MouseEvent e) {
    return (e.getID() == MouseEvent.MOUSE_PRESSED)
        && (!SwingUtilities.isRightMouseButton(e))
        && (isCloseIcon(e.getPoint()))
        && (!e.isShiftDown())
        && (!e.isAltDown())
        && (!e.isControlDown());
  }

  /**
   * Gets the composite icon.
   *
   * @param p the Point
   * @return the composite icon
   */
  private CompositeIcon getCompositeIcon(Point p) {
    if (getUI() != null) {
      int i = getUI().tabForCoordinate(this, p.x, p.y);
      if ( (i > -1) && (getTabCount() > i)) {
        Icon icon = getIconAt(i);
        if (icon instanceof CompositeIcon) {
          return (CompositeIcon) icon;
        }
      }
    }
    return null;
  }

  /**
   * Gets the icon.
   *
   * @param p the Point
   * @return the icon
   */
  private Icon getIcon(Point p) {
    CompositeIcon compositeIcon = getCompositeIcon(p);
    if (compositeIcon != null) {
      return compositeIcon.findHit(p);
    }
    return null;
  }

  /**
   * Checks if is read only close icon.
   *
   * @param p the Point
   * @return true, if is read only close icon
   */
  private boolean isReadOnlyCloseIcon(Point p) {
    Icon icon = getIcon(p);
    return (icon == JNPadTabbedViewer.iiTabReadOnlyClose);
  }

  /**
   * Checks if is close icon.
   *
   * @param p the Point
   * @return true, if is close icon
   */
  private boolean isCloseIcon(Point p) {
    Icon icon = getIcon(p);
    return
        (icon == JNPadTabbedViewer.iiTabGenericClose) ||
        (icon == JNPadTabbedViewer.iiTabModifiedClose) ||
        (icon == JNPadTabbedViewer.iiTabModifiedReadOnlyClose) ||
        (icon == JNPadTabbedViewer.iiTabReadOnlyClose);
  }

  /**
   * Gets the selected buffer.
   *
   * @return the selected buffer
   * @see jnpad.text.BufferSet#getSelectedBuffer()
   */
  @Override
  public Buffer getSelectedBuffer() {
    return (Buffer) getSelectedComponent();
  }

  /**
   * Gets the buffer at.
   *
   * @param index the index
   * @return the buffer at
   * @see jnpad.text.BufferSet#getBufferAt(int)
   */
  @Override
  public Buffer getBufferAt(int index) {
    return (Buffer) getComponentAt(index);
  }

  /**
   * Gets the buffer count.
   *
   * @return the buffer count
   * @see jnpad.text.BufferSet#getBufferCount()
   */
  @Override
  public int getBufferCount() {
    return getTabCount();
  }
  
  /**
   * Sets the active line visible.
   *
   * @param b the new active line visible
   * @see jnpad.text.IView#setActiveLineVisible(boolean)
   */
  @Override
  public void setActiveLineVisible(boolean b) {
    for (int i = 0; i < getBufferCount(); i++) {
      getBufferAt(i).setActiveLineVisible(b);
    }
  }

  /**
   * Sets the line numbers visible.
   *
   * @param b the new line numbers visible
   * @see jnpad.text.IView#setLineNumbersVisible(boolean)
   */
  @Override
  public void setLineNumbersVisible(boolean b) {
    for (int i = 0; i < getBufferCount(); i++) {
      getBufferAt(i).setLineNumbersVisible(b);
    }
  }

  /**
   * Sets the line wrap.
   *
   * @param b the new line wrap
   * @see jnpad.text.IView#setLineWrap(boolean)
   */
  @Override
  public void setLineWrap(boolean b) {
    for (int i = 0; i < getBufferCount(); i++) {
      getBufferAt(i).setLineWrap(b);
    }
  }

  /**
   * Sets the right margin line visible.
   *
   * @param b the new right margin line visible
   * @see jnpad.text.IView#setRightMarginLineVisible(boolean)
   */
  @Override
  public void setRightMarginLineVisible(boolean b) {
    for (int i = 0; i < getBufferCount(); i++) {
      getBufferAt(i).setRightMarginLineVisible(b);
    }
  }
  
  /**
   * Sets the mark strip visible.
   *
   * @param b the new mark strip visible
   * @see jnpad.text.IView#setMarkStripVisible(boolean)
   */
  @Override
  public void setMarkStripVisible(boolean b) {
    for (int i = 0; i < getBufferCount(); i++) {
      getBufferAt(i).setMarkStripVisible(b);
    }
  }

  /**
   * Sets the occurrences highlighter visible.
   *
   * @param b the new occurrences highlighter visible
   * @see jnpad.text.IView#setOccurrencesHighlighterVisible(boolean)
   */
  @Override
  public void setOccurrencesHighlighterVisible(boolean b) {
    for (int i = 0; i < getBufferCount(); i++) {
      getBufferAt(i).setOccurrencesHighlighterVisible(b);
    }
  }

  /**
   * Sets the bracket highlighter visible.
   *
   * @param b the new bracket highlighter visible
   * @see jnpad.text.IView#setBracketHighlighterVisible(boolean)
   */
  @Override
  public void setBracketHighlighterVisible(boolean b) {
    for (int i = 0; i < getBufferCount(); i++) {
      getBufferAt(i).setBracketHighlighterVisible(b);
    }
  }
  
  /**
   * Insert tab.
   *
   * @param title the title
   * @param icon the icon
   * @param c the component
   * @param tip the tooltip
   * @param index the index
   * @see javax.swing.JTabbedPane#insertTab(java.lang.String, javax.swing.Icon, java.awt.Component, java.lang.String, int)
   */
  @Override
  public void insertTab(String title, Icon icon, Component c, String tip, int index) {
    super.insertTab(title, icon, c, tip, index);
    if (Config.TAB_CLOSE_BUTTON.getValue() && getTabLayoutPolicy() == JTabbedPane.SCROLL_TAB_LAYOUT) {
      setTabComponentAt(index, new CloseButtonTabComponent(this));
    }
  }
  
  /**
   * Iterator.
   *
   * @return Iterator
   * @see java.lang.Iterable#iterator()
   */
  @Override
  public Iterator<Buffer> iterator() {
    return new Itr();
  }
  
  /////////////////////////////////////////////////////////////////////////////
  /**
   * The Class Itr.
   */
  private class Itr implements Iterator<Buffer> {
    int pos     = 0;
    int lastRet = -1;

    /**
     * Checks for next.
     *
     * @return boolean
     * @see java.util.Iterator#hasNext()
     */
    @Override
    public boolean hasNext() {
      return pos < getTabCount();
    }

    /**
     * Next.
     *
     * @return the buffer
     * @see java.util.Iterator#next()
     */
    @Override
    public Buffer next() {
      try {
        Buffer next = getBufferAt(pos);
        lastRet = pos++;
        return next;
      }
      catch (IndexOutOfBoundsException e) {
        throw new NoSuchElementException();
      }
    }

    /**
     * Removes the.
     *
     * @see java.util.Iterator#remove()
     */
    @Override
    public void remove() {
      if (lastRet == -1)
        throw new IllegalStateException();
      try {
        removeTabAt(lastRet);
        if (lastRet < pos)
          pos--;
        lastRet = -1;
      }
      catch (IndexOutOfBoundsException e) {
        throw new ConcurrentModificationException();
      }
    }
  }
  /////////////////////////////////////////////////////////////////////////////
  
}
