package de.nplusc.izc.tools.baseTools;


import java.awt.event.ActionEvent;
import java.util.ArrayList;
import java.util.Enumeration;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.ListSelectionModel;
import javax.swing.event.TableColumnModelListener;
import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel;

/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

/**
 *
 * @author LH
 */
public class HidableTableColumnModel implements TableColumnModel 
{
    // Attribute
    //----------------------------------------------------------------------------------------------
 
    protected ArrayList<TableColumn> m_allColumns    = null;
    protected TableColumnModel       m_delegateModel = null;    
    
    // Methoden
    //----------------------------------------------------------------------------------------------
 
     public HidableTableColumnModel( TableColumnModel delegateModel )
        throws NullPointerException
    {
        super();
        if (delegateModel == null)
            throw new NullPointerException("Cannot have null table column model");
        
        m_delegateModel = delegateModel;
        m_allColumns    = new ArrayList<>( m_delegateModel.getColumnCount() );
        
        Enumeration<TableColumn> columns = m_delegateModel.getColumns();
        while (columns.hasMoreElements())
        {
            m_allColumns.add( columns.nextElement() );
        } //while
    } //Konstruktor(TableColumnModel)
    
     public void setColumnVisible( int viewIndex, boolean visible )
    {
        this.setColumnVisible(
                m_allColumns.get(viewIndex),
                visible);
    } //void setColumnVisible(int, boolean)
    
    /**
     * @brief blendet die genannte Spalte aus oder ein
     * 
     * Diese Methode setzt die genannte Spalte auf sichtbar (einblenden) oder
     * unsichtbar (ausblenden), je nach Angabe.<br>
     * Wenn die Spalte nicht in einem der Modelle (dieses und das Basismodell)
     * enthalten ist, geschieht nichts.
     * 
     * @note Listener werden Ereignisse für Spalten-Entfernen und -Hinzufügen
     *       erhalten.
     *       
     * @param column    Spaltenobjekt, das ein-/ausgeblendet werden soll
     * @param visible   Angabe ob die Spalte ein- oder ausgeblendet wird
     */
    public void setColumnVisible( TableColumn column, boolean visible )
    {
        if ( ! visible)
        {
            m_delegateModel.removeColumn(column);
        } //if verbergen
        else
        {
            final int visibleColumns   = m_delegateModel.getColumnCount();
            final int invisibleColumns = m_allColumns.size();
                  int idxVisible       = 0;
            
            for (int idxInvisible = 0; idxInvisible < invisibleColumns; idxInvisible++)
            {
                final TableColumn visibleColumn = ( idxVisible < visibleColumns
                                                  ? m_delegateModel.getColumn(idxVisible)
                                                  : null);
                final TableColumn testColumn    = m_allColumns.get(idxInvisible);
                
                if (testColumn.equals(column))
                {
                    if ( ! column.equals(visibleColumn))
                    {
                        m_delegateModel.addColumn(column);
                        m_delegateModel.moveColumn(
                                m_delegateModel.getColumnCount() -1, 
                                idxVisible);
                    } //if ! sichtbare Spalte
                    return;
                } //if 
                
                if (testColumn.equals(visibleColumn))
                    idxVisible++;
            } //for
        } //if zeigen
    } //void setColumnVisible(TableColumn, boolean)
    
    /**
     * @brief zeigt alle Spalten im Modell an
     * 
     * Diese Methode blendet alle Spalte ein.
     */
    public void setAllColumnsVisible() 
    {
        final int totalSize = m_allColumns.size();
        
        for (int idxColumn = 0; idxColumn < totalSize; idxColumn++)
        {
            final TableColumn visibleColumn = ( idxColumn < m_delegateModel.getColumnCount()
                                              ? m_delegateModel.getColumn(idxColumn)
                                              : null);
            final TableColumn invisibleColumn = m_allColumns.get(idxColumn);
            
            if ( ! invisibleColumn.equals(visibleColumn))
            {
                m_delegateModel.addColumn(invisibleColumn);
                m_delegateModel.moveColumn(
                        m_delegateModel.getColumnCount() -1, 
                        idxColumn);
            } //if ungleich
        } //for
    } //void setAllColumnsVisible() 
    
    /**
     * @brief Getter für Spalte anhand des Modell-Indizes
     * 
     * Ermittelt das Spaltenobjekt aus der Gesamtmenge der Spalten (auch jene, die nicht
     * sichtbar sind), das dem gegebenen Modellindex (vom Tabellenmodell) entspricht.
     * 
     * @param modelIndex    Index des Tabellenmodells der Spalte, die erfragt ist
     * @return              (erste) gefundene Spalte oder <code>null</code>
     */
    public TableColumn getColumnByModelIndex( int modelIndex )
    {
        for (int idxColumn = 0; idxColumn < m_allColumns.size(); idxColumn++)
        {
            final TableColumn column = m_allColumns.get(idxColumn);
            if (column.getModelIndex() == modelIndex)
                return column;
        } //for
        
        return null;
    } //TableColumn getColumnByModelIndex(int)
    
    /**
     * @brief gibt an, ob Spalte sichtbar ist, oder nicht
     * 
     * Diese Methode gibt an, ob die gegebene Spalte sichtbar ist, oder nicht. Sprich
     * ob sie ausgeblendet wurde (in diesem Fall lautet die Rückgabe <code>false</code>).
     * 
     * @param column    Spaltenobjekt
     * @return          <code>true</code> wenn diese Spalte eingeblendet ist
     */
    public boolean isColumnVisible( TableColumn column ) 
    {
        for (int i = 0; i < m_delegateModel.getColumnCount(); i++)
        {
            if (m_delegateModel.getColumn(i).equals(column))
                return true;
        } //for
        
        return false;
    } //boolean isColumnVisible(TableColumn)
    
    /**
     * @brief Getter für Anzahl der Spalten
     * 
     * Gibt an, wie viele Spalten dieses Modell hat. Über den Parameter lässt sich
     * steuern, ob nur die sichtbaren, oder auch die ausgeblendeten Spalten
     * gezählt werden.
     * 
     * @param visibleOnly   wenn <code>true</code>, werden nur die sichtbaren
     *                      Spalten gezählt
     * @return              Anzahl der Spalten, je nach Parameter alle oder nur die
     *                      sichtbaren     
     *
     * @see getColumnCount()                                       
     */
    public int getColumnCount( boolean visibleOnly )
    {
        if (visibleOnly)
            return m_delegateModel.getColumnCount();
        else
            return m_allColumns.size();
    } //int getColumnCount(boolean)
    
    /**
     * @brief gibt eine Aufzählung der Spalten zurück
     * 
     * Diese Methode gibt eine Aufzählung der Spalten des Modells zurück. Über den
     * Parameter lässt sich steuern, ob alle Spalten (<code>= false</code>) oder nur
     * die sichtbaren (<code>= true</code>) angegeben werden sollen.<br>
     * Bei <code>true</code> wird das delegierte Modell befragt.
     * 
     * @param visibleOnly   wenn <code>true</code>, werden nur die sichtbaren
     *                      Spalten zurückgegeben
     * @return              Aufzählung über die Spalten, je nach Parameter alle oder nur die
     *                      sichtbaren     
     *
     * @see getColumns()                                       
     */
    public Enumeration<TableColumn> getColumns( boolean visibleOnly )
    {
        if (visibleOnly)
        {
            return m_delegateModel.getColumns();
        } //if sichtbare
        else
        {
            return new Enumeration<TableColumn>()
            {
                private int m_index = 0;
                
                @Override
                public TableColumn nextElement()
                {
                    return m_allColumns.get(m_index++);
                } //TableColumn nextElement()
            
                @Override
                public boolean hasMoreElements()
                {
                    return (m_index < m_allColumns.size());
                } //boolean hasMoreElements()
            };
        } //if alle
    } //Enumeration<TableColumn> getColumns(boolean)
    
    /**
     * @brief Getter für Index der ersten Spalte mit gegebenen
     *        Identifikator
     *        
     * Diese Methode ermittelt den (Anzeige-)Index der ersten Spalte mit dem
     * gegebenen Identifikator. Es wird über die <code>equals()</code>-Methode
     * verglichen.<br>
     * Über den zweiten Parameter kann man steuern, ob nur alle sichtbaren oder auch
     * die ausgeblendeten Spalten geprüft werden.
     * 
     * @param identifier    Identifikations-Objekt der gesuchten Spalte  
     * @param visibleOnly   Angabe ob nur die sichtbaren Spalten durchsucht werden
     * @return              Index der gefundenen Spalte oder eine Ausnahme
     * 
     * @throws IllegalArgumentException
     *  Wenn der Identifikator <code>null</code> ist, oder keine Spalte mit
     *  dem gegebenen Identifikator gefunden wurde.
     *  
     * @see getColumnIndex(Object) 
     */
    public int getColumnIndex( Object identifier, boolean visibleOnly )
        throws IllegalArgumentException
    {
        if (identifier == null)
            throw new IllegalArgumentException("Identifiert cannot be null");
        
        Enumeration<TableColumn> columns = this.getColumns(visibleOnly);
        int                      index   = 0;
        
        while (columns.hasMoreElements())
        {
            final Object id = columns.nextElement().getIdentifier();
            if (identifier.equals(id))
                return index;
            
            index++;
        } //while
        
        throw new IllegalArgumentException("Identifier "+identifier+" not found");
    } //int getColumnIndex(Object, boolean)
 
    /**
     * @brief Getter für Spalte am Index
     * 
     * Diese Methode ermittelt die Spalte am gegebenen (Anzeige-)Index. Über den
     * zweiten Parameter lässt sich steuern, ob alle sichtbaren oder auch die
     * ausgeblendeten Spalten betrachtet werden.
     * 
     * @param columnIndex   Index der gesuchten Spalte. Der Index entspricht dem
     *                      Anzeigeindex in der Tabelle
     * @param visibleOnly   Angabe ob nur sichtbare Spalten geprüft werden
     * @return              gefundene Spalte
     * 
     * @exception IndexOutOfBoundsException
     *  Wenn der gegeben Index außerhalb des gültigen Bereichs liegt.
     *  
     * @see getColumn(int)
     */
    public TableColumn getColumn( int columnIndex, boolean visibleOnly ) 
    {
        if (visibleOnly)
            return m_delegateModel.getColumn(columnIndex);
        else
            return m_allColumns.get(columnIndex);
    } //TableColumn getColumn(int, boolean)
 
    /**
     * @brief Methode für bequeme Aktionen
     * 
     * Diese Methode erstellt (bei jedem Aufruf neu) eine Menge von Aktionen, die für jede
     * der gesetzen Spalten (auch unsichtbare) eine selektierte Aktion darstellt, um
     * sie ein- und auszublenden:
     * @code
     *  HidableTableColumnModel cModel  = ...;
     *  JPopupMenu              popup   = new JPopupMenu("Hide Menu");
     *  Action[]                actions = cModel.createColumnActions();
     *  for (Action act : actions)
     *  {
     *      popup.add(new JCheckBoxMenuItem(act));
     *  } //for
     *  myTable.setComponentPopupMenu(popup);
     * @endcode
     * 
     * @note Die Aktionen sind <i>nicht</i> serialisierbar!
     * 
     * @return  Feld mit Aktionen, pro Spalte eine eigene
     */
    @SuppressWarnings("serial")
    public Action[] createColumnActions()
    {
        final Action[] actions = new Action[ m_allColumns.size() ];
        
        for (int i = 0; i < m_allColumns.size(); i++)
        {
            final TableColumn col = m_allColumns.get(i);
            final Action      act = new AbstractAction()
            {
                final private TableColumn internalColumn = col;
                
                {
                    this.putValue(
                            SELECTED_KEY, 
                            HidableTableColumnModel.this.isColumnVisible(col));
                    this.putValue(NAME, 
                            col.getHeaderValue());
                }
                
                @Override
                public void actionPerformed( ActionEvent event )
                {
                    HidableTableColumnModel.this.setColumnVisible(
                            internalColumn,
                            ((Boolean) this.getValue(SELECTED_KEY)).booleanValue());
                } //void actionPerformed(ActionEvent)
            };
            
            actions[i] = act;
        } //for
        
        return actions;
    } //Action[] createColumnActions()
    
 
    // Table Column Model Methoden
    //----------------------------------------------------------------------------------------------
 
    /**
     * @brief fügt neue Spalte hinzu
     * 
     * Diese Methode fügt die gegebene Spalte dem Modell an letzter Stelle hinzu.
     * 
     * @param column    neues Spaltenobjekt
     */
    @Override
    public void addColumn( TableColumn column ) 
    {
        m_allColumns.add(column);
        m_delegateModel.addColumn(column);
    } //void addColumn(TableColumn)
 
    /**
     * @brief entfernt die Spalte aus dem Modell
     * 
     * Diese Methode entfernt die gegebene Spalte aus dem Modell.
     * 
     * @param column    zu entfernende Spalte
     */
    @Override
    public void removeColumn( TableColumn column ) 
    {
        m_allColumns.remove(column);
        m_delegateModel.removeColumn(column);
    } //void removeColumn(TableColumn)
    
    /**
     * @brief verschiebt Spalte innerhalb der Tabelle
     * 
     * Diese Methode verschiebt die Spalte an Stelle <code>oldIndex</code> nach
     * <code>newIndex</code>. Verwendet werden hier die Anzeigeindizes.
     * 
     * @param oldIndex  Anzeigeindex der Spalte, die verschoben werden soll
     * @param newIndex  Anzeigeindex des Ortes, an den die Spalte verschoben
     *                  wird
     *                  
     * @throws IllegalArgumentException
     *  Wenn einer der Indizes nicht im gültigen bereich liegt.                  
     */
    @Override
    public void moveColumn( int oldIndex, int newIndex )
        throws IndexOutOfBoundsException
    {
        final int size = m_delegateModel.getColumnCount();
        if (   oldIndex < 0 || oldIndex >= size
            || newIndex < 0 || newIndex >= size)
        {
            throw new IndexOutOfBoundsException(
                    "At least one index is invalid: '"
                   +oldIndex
                   +"' or '"
                   +newIndex
                   +"' (valid range is [0, "
                   +size
                   +"])");
        } //if Fehler
        
        final TableColumn oldColumn = m_delegateModel.getColumn(oldIndex);
        final TableColumn newColumn = m_delegateModel.getColumn(newIndex);
        final int         idxOld    = m_allColumns.indexOf(oldColumn);
        final int         idxNew    = m_allColumns.indexOf(newColumn);
        
        if (oldIndex != newIndex)
        {
            m_allColumns.remove(idxOld);
            m_allColumns.add(idxNew, oldColumn);
        } //if
        
        m_delegateModel.moveColumn(oldIndex, newIndex);
    } //void moveColumn(int, int)
 
    /**
     * @brief fügt Listener hinzu
     * 
     * Der Listener wird an das delegierte Modell hinzugefügt.
     * 
     * @param listener  neuer Listener
     */
    @Override
    public void addColumnModelListener( TableColumnModelListener listener )
    {
        m_delegateModel.addColumnModelListener(listener);
    } //void addColumnModelListener(TableColumnModelListener)
 
    /**
     * @brief entfernt Listener
     * 
     * Der Listener wird vom delegierten Modell entfernt.
     * 
     * @param listener  Listener der entfernt wird
     */
    @Override
    public void removeColumnModelListener( TableColumnModelListener listener )
    {
        m_delegateModel.removeColumnModelListener(listener);
    } //void removeColumnModelListener(TableColumnModelListener)
 
    /**
     * @brief Getter für Spalte am gegebenen Index
     * 
     * Diese Methode ermittelt die Spalte am gegeben Index. Verwendet werden
     * nur sichtbare Spalten.
     * 
     * @param columnIndex   Index der gesuchten Spalte. Verwendet wird hier
     *                      der Anzeigeindex der Tabelle.
     * @return              gefundene Spalte
     * 
     * @exception IndexOutOfBoundsException
     *  Wenn der gegeben Index außerhalb des gültigen Bereichs liegt.
     *  
     * @see getColumn(int, boolean)
     */
    @Override
    public TableColumn getColumn( int columnIndex )
    {
        return this.getColumn(columnIndex, true);
    } //TableColumn getColumn(int)
 
    /**
     * @brief Getter für Spaltenzahl
     * 
     * Gibt die Anzahl der sichtbaren (!) Spalten zurück.
     * @return  Anzahl der sichtbaren Spalten
     * 
     * @see getColumnCount(boolean)
     */
    @Override
    public int getColumnCount()
    {
        return this.getColumnCount(true);
    } //int getColumnCount()
 
    /**
     * @brief ermittelt Index der Spalte durch Identifikator
     * 
     * Diese Methode ermittelt den Index der Spalte über alle sichtbaren
     * Spalten. Dazu wird intern #getColumnIndex(Object, boolean) verwendet.
     * 
     * @param identifier    Identifikations-Objekt der gesuchten Spalte  
     * @return              Index der gefundenen Spalte oder eine Ausnahme
     * 
     * @throws IllegalArgumentException
     *  Wenn der Identifikator <code>null</code> ist, oder keine Spalte mit
     *  dem gegebenen Identifikator gefunden wurde.
     *  
     * @see getColumnIndex(Object, boolean) 
     */
    @Override
    public int getColumnIndex( Object identifier ) throws IllegalArgumentException
    {
        return this.getColumnIndex(identifier, true);
    } //int getColumnIndexObject)
 
    /**
     * @brief Getter für Spalte an X-Position
     * 
     * Diese Methode ermittelt über das delegierte Modell den Spaltenindex
     * an der gegebenen X-Position.
     * 
     * @param position  erfragte X-Position, meist Pixelwerte
     * @return          Index der Spalte an der Position
     */
    @Override
    public int getColumnIndexAtX( int position )
    {
        return m_delegateModel.getColumnIndexAtX(position);
    } //int getColumnIndexAtX(int)
 
    /**
     * @brief Getter für Spaltenabstand
     * 
     * Gibt den Abstand der Spalten zurück. Dies wird vom delegierten
     * Modell verwaltet.
     * 
     * @return  Abstand der Spalten in Pixeln
     * 
     * @see setColumnMargin(int)
     */
    @Override
    public int getColumnMargin()
    {
        return m_delegateModel.getColumnMargin();
    } //int getColumnMargin()
 
    /**
     * @brief gibt an, ob Spaltenselektion erlaubt ist
     * 
     * Diese Methode gibt zurück, ob die Selektion ganzer
     * Spalten erlaubt ist, oder nicht.
     * 
     * @return  <code>true</code> wenn Spaltenselektion erlaubt ist
     * 
     * @see setColumnSelectionAllowed(boolean)
     */
    @Override
    public boolean getColumnSelectionAllowed()
    {
        return m_delegateModel.getColumnSelectionAllowed();
    } //boolean getColumnSelectionAllowed()
 
    /**
     * @brief Getter für alle Spalten
     * 
     * Diese Methode gibt eine Aufzählung über alle sichtbaren
     * Spalten zurück.
     * 
     * @return  Aufzählung mit allen sichtbaren Spalten
     * 
     * @see getColumns(boolean)
     */
    @Override
    public Enumeration<TableColumn> getColumns()
    {
        return this.getColumns(true);
    } //Enumeration<TableColumn> getColumns()
 
    /**
     * @brief ermittelt die Anzahl der selektierten Spalten
     * 
     * Ermittelt die Anzahl der selektierten Spalten über das 
     * delegierte Modell.
     * 
     * @return Anzahl selektierter Spalten
     */
    @Override
    public int getSelectedColumnCount()
    {
        return m_delegateModel.getSelectedColumnCount();
    } //int getSelectedColumnCount()
 
    /**
     * @brief ermittelt Indizes der selektierten Spalten
     * 
     * Diese Methode ermittelt die Indizes der selektierten
     * Spalten über das delegierte Modell.
     * 
     * @return  Feld mit den Indizes der selektierten Spalten, oder
     *          leeres Array 
     */
    @Override
    public int[] getSelectedColumns()
    {
        return m_delegateModel.getSelectedColumns();
    } //int[] getSelectedColumns()
 
    /**
     * @brief Getter für Selektionsmodell
     * 
     * Gibt das Selektionsmodell der delegierten Modells zurück.
     * 
     * @return  Selektionsmodell
     */
    @Override
    public ListSelectionModel getSelectionModel()
    {
        return m_delegateModel.getSelectionModel();
    } //ListSelectionModel getSelectionModel()
 
    /**
     * @brief ermittelt vollständige Breite der Spalten
     * 
     * Diese Methode ermittelt (über das delegierte Modell) die vollständige
     * Breite der Spalten in Pixeln.
     * 
     * @return  Breite aller (sichtbaren) Spalten in Pixeln
     */
    @Override
    public int getTotalColumnWidth()
    {
        return m_delegateModel.getTotalColumnWidth();
    } //int getTotalColumnWidth()
 
    /**
     * @brief Setter für Spaltenabstand
     * 
     * Übernimmt den neuen Spaltenabstand in Pixeln, welcher an das 
     * delegierte Modell weitergetragen wird.
     * 
     * @param newMargin neuer Spaltenabstand
     * 
     * @see getColumnMargin()
     */
    @Override
    public void setColumnMargin( int newMargin )
    {
        m_delegateModel.setColumnMargin(newMargin);
    } //void setColumnMargin(int)
 
    /**
     * @brief erlaubt/verbietet Spaltenselektion
     * 
     * Über diese Methode kann man die Selektion ganzer Spalten
     * erlauben, bzw. verbieten.
     * 
     * @param flag  Angabe ob Selektion erlaubt ist, oder nicht
     * 
     * @see getColumnSelectionAllowed()
     */
    @Override
    public void setColumnSelectionAllowed( boolean flag )
    {
        m_delegateModel.setColumnSelectionAllowed(flag);
    } //void setColumnSelectionAllowed(boolean)
 
    /**
     * @brief Setter für Selektionsmodell
     * 
     * Übernimmt das neue Selektionsmodell für das delegierte Spaltenmodell.
     * 
     * @param model neues Selektionsmodell
     * 
     * @see getSelectionModel()
     */
    @Override
    public void setSelectionModel( ListSelectionModel model )
    {
        m_delegateModel.setSelectionModel(model);
    } //void setSelectionModel(ListSelectionModel)
    
} //class HidableTableColumnModel