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

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.EventQueue;
import java.io.IOException;
import java.io.StreamTokenizer;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Stack;
import java.util.logging.Level;
import java.util.logging.Logger;

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

import jnpad.GUIUtilities;
import jnpad.JNPadFrame;
import jnpad.action.ActionManager;
import jnpad.action.JNPadActions;
import jnpad.config.Config;
import jnpad.config.Controlable;
import jnpad.ui.JNPadSplitPane;
import jnpad.util.Utilities;

/*
  (a): active 
  (s): selected
  
   --- Viewer -----------------------------------------------------        
  |  --- BufferSet1(a) ------------------------------------------  |
  | |  - Buffer1(a,s) ------------  - Buffer2 -----------------  | |
  | | |             |             ||                           | | |
  | | |             | EditPane2(a)||        EditPane4(s)       | | |
  | | |             |             ||                           | | |
  | | | EditPane1   |-------------||---------------------------| | |
  | | |    (a,s)    |             ||                           | | |
  | | |             | EditPane3(a)||        EditPane5          | | |
  | | |             |             ||                           | | |
  | |  ---------------------------  ---------------------------  | |
  |  ------------------------------------------------------------  |
  |                                                                |
  |  --- BufferSet2 ---------------------------------------------  |
  | |  - Buffer3 -----------------  - Buffer4(s) --------------  | |
  | | |             |             ||             |             | | |
  | | | EditPane6(s)|             ||             |             | | |
  | | |             |             ||             |             | | |
  | |  -------------|  EditPane8  ||EditPane9(s) |  EditPane10 | | |
  | | |             |             ||             |             | | |
  | | |  EditPane7  |             ||             |             | | |
  | | |             |             ||             |             | | |
  | |  ---------------------------  ---------------------------  | |
  |  ------------------------------------------------------------  |
   ----------------------------------------------------------------
  
  - Viewer: 
      ActiveBufferSet         : BufferSet1
      BufferSets              : [BufferSet1,BufferSet2]
      
  - Buffer: 
      ActiveBuffer            : Buffer1
      Buffers                 : [Buffer1,Buffer2]
      GlobalBuffers           : [Buffer1,Buffer2,Buffer3,Buffer4]
      SelectedBuffers         : [Buffer1,Buffer4]
  
  - EditPane:
      ActiveEditPane          : EditPane1
      ActiveEditPanes         : [EditPane1,EditPane2,EditPane3]
      SelectedEditPanes       : [EditPane1,EditPane4]
      GlobalSelectedEditPanes : [EditPane1,EditPane4,EditPane6,EditPane9]
      EditPanes               : [EditPane1,EditPane2,EditPane3,EditPane4,EditPane5]
      GlobalEditPanes         : [EditPane1,EditPane2,EditPane3,EditPane4,EditPane5,
                                 EditPane6,EditPane7,EditPane8,EditPane9,EditPane10]
  
  
  jNPad ------ Viewer ------ BufferSet ------ Buffer ------ EditPane ------ EditTextArea
        1    1      1       1..n     1     1..n     1    1..n       1     1

*/

/**
 * The Class Viewer.
 *
 * @version 0.3
 * @since jNPad 0.1
 */
public abstract class Viewer extends JPanel implements Controlable, IView {
  /** The buffer set. */
  protected BufferSet         _bufferSet;

  /** The jNPad frame. */
  protected JNPadFrame        _jNPad;

  private boolean             _suppressStateChangedEvents = false;

  /** Logger */
  private final static Logger LOGGER                      = Logger.getLogger(Viewer.class.getName());

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

  /**
   * Instantiates a new viewer.
   *
   * @param jNPad the frame
   */
  protected Viewer(JNPadFrame jNPad) {
    super(new BorderLayout());
    _jNPad = jNPad;
  }
  
  /**
   * Gets the active buffer set.
   *
   * @return the active buffer set
   */
  public BufferSet getActiveBufferSet() {
    return _bufferSet;
  }

  /**
   * Sets the active buffer set.
   *
   * @param bufferSet the new active buffer set
   */
  void setActiveBufferSet(BufferSet bufferSet) {
    if (_bufferSet != bufferSet) {
      _bufferSet = bufferSet;
      for (BufferSet bufferset : getBufferSets()) {
        for (Buffer eview : bufferset) {
          eview.repaintGutter();
        }
      }
      handleStateChanged(new ChangeEvent(this));
    }
  }
  
  /**
   * Creates the.
   *
   * @param bufferSet the buffer set
   * @param buffer the buffer
   * @return the buffer
   */
  protected static Buffer create(BufferSet bufferSet, Buffer buffer) {
    return buffer.create(bufferSet);
  }

  /**
   * Gets the buffer sets.
   *
   * @return the buffer sets
   */
  public BufferSet[] getBufferSets() {
    if (splitPane == null) {
      return new BufferSet[] { _bufferSet };
    }
    List<BufferSet> list = new ArrayList<BufferSet>();
    lookForBufferSets(list, splitPane);
    BufferSet[] array = new BufferSet[list.size()];
    list.toArray(array);
    return array;
  }

  /**
   * Look for buffer sets.
   *
   * @param list the list
   * @param comp the comp
   */
  private static void lookForBufferSets(List<BufferSet> list, Component comp) {
    if (comp instanceof BufferSet) {
      list.add((BufferSet) comp);
    }
    else if (comp instanceof JSplitPane) {
      JSplitPane split = (JSplitPane) comp;
      lookForBufferSets(list, split.getLeftComponent());
      lookForBufferSets(list, split.getRightComponent());
    }
  }
  
  /**
   * Gets the active buffer set index.
   *
   * @return the active buffer set index
   */
  public int getActiveBufferSetIndex() {
    BufferSet[] bufferSets = getBufferSets();
    for (int i = 0; i < bufferSets.length; i++) {
      if (_bufferSet == bufferSets[i]) {
        return i;
      }
    }
    return -1;
  }

  /**
   * Gets the active buffer.
   *
   * @return the active buffer
   */
  public Buffer getActiveBuffer() {
    return _bufferSet.getSelectedBuffer();
  }
  
  /**
   * Gets the buffers.
   *
   * @return the buffers
   */
  public Buffer[] getBuffers() {
    int len = getBufferCount();
    Buffer[] result = new Buffer[len];
    for (int i = 0; i < len; i++)
      result[i] = getBufferAt(i);
    return result;
  }

  /**
   * Gets the selected buffers.
   *
   * @return the selected buffers
   */
  public Buffer[] getSelectedBuffers() {
    List<Buffer> list = new ArrayList<Buffer>();
    for (BufferSet bufferSet : getBufferSets()) {
      list.add(bufferSet.getSelectedBuffer());
    }
    Buffer[] array = new Buffer[list.size()];
    list.toArray(array);
    return array;
  }
  
  /**
   * Gets the global buffers.
   *
   * @return the global buffers
   */
  public Buffer[] getGlobalBuffers() {
    List<Buffer> list = new ArrayList<Buffer>();
    for (BufferSet bufferSet : getBufferSets()) {
      for (Buffer buffer : bufferSet) {
        list.add(buffer);
      }
    }
    Buffer[] array = new Buffer[list.size()];
    list.toArray(array);
    return array;
  }
  
  /**
   * Gets the active edit pane.
   *
   * @return the active edit pane
   */
  public EditPane getActiveEditPane() {
    return getActiveBuffer().getSelectedEditPane();
  }

  /**
   * Gets the active edit panes.
   *
   * @return the active edit panes
   */
  public EditPane[] getActiveEditPanes() {
    return getActiveBuffer().getEditPanes();
  }
  
  /**
   * Gets the selected edit panes.
   *
   * @return the selected edit panes
   */
  public EditPane[] getSelectedEditPanes() {
    int len = getBufferCount();
    EditPane[] result = new EditPane[len];
    for (int i = 0; i < len; i++)
      result[i] = getBufferAt(i).getSelectedEditPane();
    return result;
  }

  /**
   * Gets the global selected edit panes.
   *
   * @return the global selected edit panes
   */
  public EditPane[] getGlobalSelectedEditPanes() {
    List<EditPane> list = new ArrayList<EditPane>();
    for (BufferSet eviews : getBufferSets()) {
      for (Buffer eview : eviews) {
        list.add(eview.getSelectedEditPane());
      }
    }
    EditPane[] array = new EditPane[list.size()];
    list.toArray(array);
    return array;
  }

  /**
   * Gets the edits the panes.
   *
   * @return the edits the panes
   */
  public EditPane[] getEditPanes() {
    List<EditPane> list = new ArrayList<EditPane>();
    for (Buffer eview : getActiveBufferSet()) {
      Collections.addAll(list, eview.getEditPanes());
    }
    EditPane[] array = new EditPane[list.size()];
    list.toArray(array);
    return array;
  }
  
  /**
   * Gets the global edit panes.
   *
   * @return the global edit panes
   */
  public EditPane[] getGlobalEditPanes() {
    List<EditPane> list = new ArrayList<EditPane>();
    for (BufferSet eviews : getBufferSets()) {
      for (Buffer eview : eviews) {
        Collections.addAll(list, eview.getEditPanes());
      }
    }
    EditPane[] array = new EditPane[list.size()];
    list.toArray(array);
    return array;
  }

  /**
   * Gets the active text area.
   *
   * @return the active text area
   */
  public JNPadTextArea getActiveTextArea() {
    return getActiveBuffer().getSelectedTextArea();
  }

  /**
   * Gets the active text areas.
   *
   * @return the active text areas
   */
  public JNPadTextArea[] getActiveTextAreas() {
    return getActiveBuffer().getTextAreas();
  }

  /**
   * Gets the selected text areas.
   *
   * @return the selected text areas
   */
  public JNPadTextArea[] getSelectedTextAreas() {
    int len = getBufferCount();
    JNPadTextArea[] result = new JNPadTextArea[len];
    for (int i = 0; i < len; i++)
      result[i] = getBufferAt(i).getSelectedTextArea();
    return result;
  }

  /**
   * Gets the global selected text areas.
   *
   * @return the global selected text areas
   */
  public JNPadTextArea[] getGlobalSelectedTextAreas() {
    List<JNPadTextArea> list = new ArrayList<JNPadTextArea>();
    for (BufferSet eviews : getBufferSets()) {
      for (Buffer eview : eviews) {
        list.add(eview.getSelectedTextArea());
      }
    }
    JNPadTextArea[] array = new JNPadTextArea[list.size()];
    list.toArray(array);
    return array;
  }
  
  /**
   * Gets the text areas.
   *
   * @return the text areas
   */
  public JNPadTextArea[] getTextAreas() {
    List<JNPadTextArea> list = new ArrayList<JNPadTextArea>();
    for (Buffer eview : getActiveBufferSet()) {
      for (EditPane epane : eview.getEditPanes()) {
        list.add(epane.getTextArea());
      }
    }
    JNPadTextArea[] array = new JNPadTextArea[list.size()];
    list.toArray(array);
    return array;
  }
  
  /**
   * Gets the global text panes.
   *
   * @return the global text panes
   */
  public JNPadTextArea[] getGlobalTextAreas() {
    List<JNPadTextArea> list = new ArrayList<JNPadTextArea>();
    for (BufferSet eviews : getBufferSets()) {
      for (Buffer eview : eviews) {
        for (EditPane epane : eview.getEditPanes()) {
          list.add(epane.getTextArea());
        }
      }
    }
    JNPadTextArea[] array = new JNPadTextArea[list.size()];
    list.toArray(array);
    return array;
  }

  /**
   * Next buffer set.
   */
  public void nextBufferSet() {
    BufferSet[] bufferSets = getBufferSets();

    int index = getActiveBufferSetIndex();

    if (index > -1 && index < bufferSets.length - 1) {
      setActiveBufferSet(bufferSets[++index]);
      _bufferSet.focusOnSelectedComponent();
    }
    else if (index == bufferSets.length - 1) {
      setActiveBufferSet(bufferSets[0]);
      _bufferSet.focusOnSelectedComponent();
    }
  }

  /**
   * Previous buffer set.
   */
  public void previousBufferSet() {
    BufferSet[] bufferSets = getBufferSets();

    int index = getActiveBufferSetIndex();

    if (index > 0 && index < bufferSets.length) {
      setActiveBufferSet(bufferSets[--index]);
      _bufferSet.focusOnSelectedComponent();
    }
    else if (index == 0) {
      setActiveBufferSet(bufferSets[bufferSets.length - 1]);
      _bufferSet.focusOnSelectedComponent();
    }
  }
  
  /**
   * Configure.
   *
   * @param cfg the cfg
   * @see jnpad.config.Configurable#configure(int)
   */
  public void configure(final int cfg) {
    for (BufferSet eviews : getBufferSets()) {
      eviews.configure(cfg);
    }
  }

  /**
   * Update controls.
   *
   * @see jnpad.config.Updatable#updateControls()
   */
  public void updateControls() {
    updateControls(CTRLS_ALL);
  }

  /**
   * Update controls.
   *
   * @param ctrls the ctrls
   * @see jnpad.config.Updatable#updateControls(int)
   */
  public void updateControls(final int ctrls) {
    if ((ctrls & CTRLS_SPLITTING) != 0) {
      final boolean b = isSplitted();
      ActionManager.INSTANCE.setEnabled(JNPadActions.ACTION_NAME_UNSPLIT_VIEWER, b);
      ActionManager.INSTANCE.setEnabled(JNPadActions.ACTION_NAME_UNSPLIT_VIEWER_CURRENT, b);
      ActionManager.INSTANCE.setEnabled(JNPadActions.ACTION_NAME_NEXT_BUFFER_SET, b);
      ActionManager.INSTANCE.setEnabled(JNPadActions.ACTION_NAME_PREVIOUS_BUFFER_SET, b);
    }
  }
  
  /**
   * 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 (BufferSet eviews : getBufferSets())
      eviews.setActiveLineVisible(b);
    Config.ACTIVE_LINE_VISIBLE.setValue(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 (BufferSet eviews : getBufferSets())
      eviews.setLineNumbersVisible(b);
    Config.GUTTER_VISIBLE.setValue(b);
  }

  /**
   * Sets the line wrap.
   *
   * @param b the new line wrap
   * @see jnpad.text.IView#setLineWrap(boolean)
   */
  @Override
  public void setLineWrap(boolean b) {
    for (BufferSet eviews : getBufferSets())
      eviews.setLineWrap(b);
    Config.TEXT_LINE_WRAP.setValue(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 (BufferSet eviews : getBufferSets())
      eviews.setRightMarginLineVisible(b);
    Config.TEXT_RIGHT_MARGIN_LINE_VISIBLE.setValue(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 (BufferSet eviews : getBufferSets())
      eviews.setMarkStripVisible(b);
    Config.MARKER_VISIBLE.setValue(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 (BufferSet eviews : getBufferSets())
      eviews.setOccurrencesHighlighterVisible(b);
    Config.OCCURRENCES_HIGHLIGHTER_VISIBLE.setValue(b);
  }

  /**
   * Sets the bracket highlighter visible.
   *
   * @param b the new bracket highlighter visible
   * @see jnpad.text.IView#setBracketHighlighterVisible(boolean)
   * @since 0.3
   */
  @Override
  public void setBracketHighlighterVisible(boolean b) {
    for (BufferSet eviews : getBufferSets())
      eviews.setBracketHighlighterVisible(b);
    Config.BRACKET_HIGHLIGHTER_VISIBLE.setValue(b);
  }
  
  /**
   * Gets the buffer count.
   *
   * @return the buffer count
   */
  public int getBufferCount() {
    return _bufferSet.getBufferCount();
  }

  /**
   * Gets the buffer at.
   *
   * @param index the index
   * @return the buffer at
   */
  public Buffer getBufferAt(int index) {
    return _bufferSet.getBufferAt(index);
  }
  
  /**
   * Next buffer.
   */
  public void nextBuffer() {
    _bufferSet.nextTab();
  }

  /**
   * Previous buffer.
   */
  public void previousBuffer() {
    _bufferSet.previousTab();
  }

  /**
   * Index of.
   *
   * @param c the c
   * @return the int
   */
  public int indexOf(Component c) {
    return _bufferSet.indexOfComponent(c);
  }

  /**
   * Adds the.
   *
   * @param buffer the buffer
   * @return the buffer
   */
  public abstract Buffer add(Buffer buffer);

  /**
   * Removes the buffer at.
   *
   * @param index the index
   */
  public void removeBufferAt(int index) {
    for (BufferSet bufferSet : getBufferSets()) {
      bufferSet.removeTabAt(index);
    }
  }
  
  /**
   * Sets the active buffer.
   *
   * @param buffer the new active buffer
   */
  public void setActiveBuffer(Buffer buffer) {
    for (BufferSet bufferSet : getBufferSets()) {
      bufferSet.setSelectedComponent(buffer);
    }
  }

  /**
   * Sets the active index.
   *
   * @param index the new active index
   */
  public void setActiveIndex(int index) {
    for (BufferSet bufferSet : getBufferSets()) {
      bufferSet.setSelectedIndex(index);
    }
  }

  /**
   * Gets the active index.
   * 
   * @return the active index
   */
  public int getActiveIndex() {
    return _bufferSet.getSelectedIndex();
  }
  
  /**
   * Focus on active buffer.
   */
  public void focusOnActiveBuffer() {
    Buffer activeBuffer = getActiveBuffer();
    if (activeBuffer != null) {
      GUIUtilities.requestFocus(activeBuffer);
    }
  }

  /**
   * Sets the component icon at.
   *
   * @param index the new component icon at
   */
  public abstract void setComponentIconAt(int index);

  /**
   * Sets the tool tip text at.
   *
   * @param index the index
   * @param toolTipText the tool tip text
   */
  public void setToolTipTextAt(int index, String toolTipText) {
    for (BufferSet eviews : getBufferSets()) {
      eviews.setToolTipTextAt(index, toolTipText);
    }
  }
  
  /**
   * Gets the tool tip text at.
   *
   * @param index the index
   * @return the tool tip text at
   */
  public String getToolTipTextAt(int index) {
    return _bufferSet.getToolTipTextAt(index);
  }

  /**
   * Sets the title at.
   *
   * @param index the index
   * @param title the title
   */
  public void setTitleAt(int index, String title) {
    for (BufferSet eviews : getBufferSets()) {
      eviews.setTitleAt(index, title);
    }
  }

  /**
   * Sets the suppress state changed events.
   *
   * @param suppress the new suppress state changed events
   */
  public void setSuppressStateChangedEvents(boolean suppress) {
    _suppressStateChangedEvents = suppress;
    for (BufferSet eviews : getBufferSets()) {
      eviews.setSuppressStateChangedEvents(suppress);
    }
  }

  /**
   * Handle state changed.
   *
   * @param e the e
   */
  protected abstract void handleStateChanged(final ChangeEvent e);

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

  /**
   * Adds the change listener.
   *
   * @param l the ChangeListener
   */
  public void addChangeListener(ChangeListener l) {
    for (BufferSet eviews : getBufferSets()) {
      eviews.addChangeListener(l);
    }
  }

  /**
   * Removes the change listener.
   *
   * @param l the ChangeListener
   */
  public void removeChangeListener(ChangeListener l) {
    for (BufferSet eviews : getBufferSets()) {
      eviews.removeChangeListener(l);
    }
  }

  /**
   * Sets the main content.
   *
   * @param c the new main content
   */
  protected void setMainContent(Component c) {
    if (mainContent != null) {
      remove(mainContent);
    }
    mainContent = c;
    add(mainContent, BorderLayout.CENTER);
    if (c instanceof JSplitPane) {
      splitPane = (JSplitPane) c;
    }
    else {
      splitPane = null;
      _bufferSet = (BufferSet) c;
    }
    revalidate();
    repaint();
  }
  
  // --- splitting ---
  JSplitPane          splitPane;
  Component           mainContent;
  String              lastSplitConfig;
  int                 lastIndex;

  /**
   * Checks if is splitted.
   *
   * @return true, if is splitted
   */
  public boolean isSplitted() {
    return (splitPane != null);
  }

  /**
   * Creates the buffer set.
   *
   * @param oldBufferSet the old buffer set
   * @return the buffer set
   */
  protected abstract BufferSet createBufferSet(BufferSet oldBufferSet);
  
  /**
   * Split horizontally.
   *
   * @return the buffer set
   */
  public BufferSet splitHorizontally() {
    return split(JSplitPane.VERTICAL_SPLIT);
  }

  /**
   * Split vertically.
   *
   * @return the buffer set
   */
  public BufferSet splitVertically() {
    return split(JSplitPane.HORIZONTAL_SPLIT);
  }
  
  /**
   * Split.
   *
   * @param orientation the orientation
   * @return the buffer set
   */
  public BufferSet split(int orientation) {
    BufferSet oldBufferSet = _bufferSet;
    BufferSet newBufferSet = createBufferSet(oldBufferSet);

    JComponent oldParent = (JComponent) oldBufferSet.toComponent().getParent();

    final JSplitPane newSplitPane = new JNPadSplitPane(orientation);

    int parentSize = orientation == JSplitPane.VERTICAL_SPLIT ? oldBufferSet.toComponent().getHeight() : oldBufferSet.toComponent().getWidth();
    final int dividerPosition = (int) ((parentSize - newSplitPane.getDividerSize()) * 0.5);
    newSplitPane.setDividerLocation(dividerPosition);

    if (oldParent instanceof JSplitPane) {
      JSplitPane oldSplitPane = (JSplitPane) oldParent;
      int dividerPos = oldSplitPane.getDividerLocation();

      Component left = oldSplitPane.getLeftComponent();

      if (left == oldBufferSet) {
        oldSplitPane.setLeftComponent(newSplitPane);
      }
      else {
        oldSplitPane.setRightComponent(newSplitPane);

      }
      newSplitPane.setLeftComponent(oldBufferSet.toComponent());
      newSplitPane.setRightComponent(newBufferSet.toComponent());

      oldSplitPane.setDividerLocation(dividerPos);
    }
    else {
      splitPane = newSplitPane;

      newSplitPane.setLeftComponent(oldBufferSet.toComponent());
      newSplitPane.setRightComponent(newBufferSet.toComponent());

      setMainContent(newSplitPane);
    }
    
    EventQueue.invokeLater(new Runnable() {
      public void run() {
        newSplitPane.setDividerLocation(dividerPosition);
      }
    });
    
    newBufferSet.focusOnSelectedComponent();

    return newBufferSet;
  }
  
  /**
   * Unsplit.
   */
  public void unsplit() {
    if (splitPane != null) {
      saveLastConfig();
      setMainContent(_bufferSet.toComponent());
      splitPane = null;
      _bufferSet.focusOnSelectedComponent();
    }
    else {
      GUIUtilities.beep();
    }
  }
  
  /**
   * Unsplit current.
   */
  public void unsplitCurrent() {
    if (splitPane != null) {
      saveLastConfig();

      // find first split pane parenting current edit pane
      Component comp = _bufferSet.toComponent();
      while (!(comp instanceof JSplitPane) && comp != null) {
        comp = comp.getParent();
      }

      JComponent parent = comp == null ? null : (JComponent) comp.getParent();
      if (parent instanceof JSplitPane) {
        JSplitPane parentSplit = (JSplitPane) parent;
        int pos = parentSplit.getDividerLocation();
        if (parentSplit.getLeftComponent() == comp) {
          parentSplit.setLeftComponent(_bufferSet.toComponent());
        }
        else {
          parentSplit.setRightComponent(_bufferSet.toComponent());
        }
        parentSplit.setDividerLocation(pos);
        parent.revalidate();
      }
      else {
        setMainContent(_bufferSet.toComponent());
        splitPane = null;
      }
      _bufferSet.focusOnSelectedComponent();
    }
    else {
      GUIUtilities.beep();
    }
  }
  
  /**
   * Restore split.
   */
  public void restoreSplit() {
    if (lastSplitConfig == null)
      GUIUtilities.beep();
    else
      setLastConfig();
  }

  /**
   * Save last config.
   */
  private void saveLastConfig() {
    lastSplitConfig = getSplitConfig();
    lastIndex = getActiveBufferSetIndex();
  }

  /**
   * Sets the last config.
   */
  private void setLastConfig() {
    setSplitConfig(lastSplitConfig);
    try {
      getBufferSets()[lastIndex].focusOnSelectedComponent();
      
    }
    catch (Exception ex) {
      //ignored
    }
  }
  
  /**
   * Sets the split config.
   *
   * @param splitConfig the new split config
   */
  private void setSplitConfig(String splitConfig) {
    try {
      Component comp = restoreSplitConfig(splitConfig);
      setMainContent(comp);
    }
    catch (Exception ex) {
      LOGGER.log(Level.WARNING, ex.getMessage(), ex);
    }
  }

  /**
   * Restore split config.
   *
   * @param splitConfig the split config
   * @return the component
   * @throws IOException Signals that an I/O exception has occurred.
   */
  private Component restoreSplitConfig(String splitConfig) throws IOException {
    if (Utilities.isEmptyString(splitConfig)) {
      return _bufferSet.toComponent();
    }

    Stack<Object> stack = new Stack<Object>();
    
    //System.out.println(splitConfig);

    // we create a stream tokenizer for parsing a simple
    // stack-based language
    StreamTokenizer st = new StreamTokenizer(new StringReader(splitConfig));
    st.whitespaceChars(0, ' ');
    /* all printable ASCII characters */
    st.wordChars('#', '~');
    st.commentChar('!');
    st.quoteChar('"');
    st.eolIsSignificant(false);

    loop: while (true) {
      switch (st.nextToken()) {
        case StreamTokenizer.TT_EOF:
          break loop;
        case StreamTokenizer.TT_WORD:
          if (st.sval.equals("vertical") || st.sval.equals("horizontal")) { //$NON-NLS-1$ //$NON-NLS-2$
            int orientation = st.sval.equals("vertical") ? JSplitPane.VERTICAL_SPLIT : JSplitPane.HORIZONTAL_SPLIT; //$NON-NLS-1$
            int divider = (Integer) stack.pop();

            Object obj1 = stack.pop();
            Object obj2 = stack.pop();
            if (obj1 instanceof BufferSet) {
              BufferSet b1 = (BufferSet) obj1;
              obj1 = _bufferSet = createBufferSet(b1);
            }
            if (obj2 instanceof BufferSet) {
              BufferSet b2 = (BufferSet) obj2;
              obj2 = createBufferSet(b2);
            }

            stack.push(splitPane = new JNPadSplitPane(orientation, (Component)obj1, (Component)obj2));
            splitPane.setDividerLocation(divider);
          }
          else if(st.sval.equals("BufferSet")) { //$NON-NLS-1$
            stack.push(_bufferSet);
          }
          break;
        case StreamTokenizer.TT_NUMBER:
          stack.push((int) st.nval);
          break;
      }
    }

    Object obj = stack.peek();

    return (Component)obj;
  }

  /**
   * Gets the split config.
   *
   * @return the split config
   */
  public String getSplitConfig() {
    StringBuilder sb = new StringBuilder();

    if (splitPane != null) {
      appendToSplitConfig(splitPane, sb);
    }
    else {
      appendToSplitConfig(sb, _bufferSet.toComponent());
    }

    //System.out.println(sb);
    
    return sb.toString();
  }  
  
  /**
   * Append to split config.
   *
   * @param splitPane the split pane
   * @param sb the StringBuilder
   */
  private static void appendToSplitConfig(JSplitPane splitPane, StringBuilder sb) {
    Component right = splitPane.getRightComponent();
    appendToSplitConfig(sb, right);

    sb.append(' ');

    Component left = splitPane.getLeftComponent();
    appendToSplitConfig(sb, left);

    sb.append(' ');
    sb.append(splitPane.getDividerLocation());
    sb.append(' ');
    sb.append(splitPane.getOrientation() == JSplitPane.VERTICAL_SPLIT ? 
        "vertical" : "horizontal"); //$NON-NLS-1$ //$NON-NLS-2$
  }

  /**
   * Append to split config.
   *
   * @param sb the StringBuilder
   * @param component the component
   */
  private static void appendToSplitConfig(StringBuilder sb, Component component) {
    if(component instanceof JSplitPane) {
      // the component is a JSplitPane
      appendToSplitConfig((JSplitPane)component,sb);
    }
    else {
      sb.append("BufferSet"); //$NON-NLS-1$
    }
  }
  // ---
  
}
