package data.swing;

import data.*;
import data.events.*;

import util.*;
import util.swing.*;

import java.util.*;
import java.io.*;

/**
  * A {@link javax.swing.table.TableModel} that models the contents of a {@link Stock}, representing each
  * {@link StockItem} as an individual record.
  *
  * @author Steffen Zschaler
  * @version 2.0 23/08/1999
  * @since v2.0
  */
public class StoringStockTableModel extends util.swing.AbstractTableModel implements HelpableListener,
                                                                                     StockChangeListener,
                                                                                     Serializable {

  /**
    * The Stock that is being modelled.
    *
    * @serial
    */
  protected Stock m_stModel;

  /**
    * The DataBasket used to determine visibility.
    *
    * @serial
    */
  protected DataBasket m_dbBasket;

  /**
    * The Comparator that defines the sorting order of records in the model. It compares
    * {@link StockItem StockItems}.
    *
    * @serial
    */
  protected Comparator m_cmpComparator = new NaturalComparator();

  /**
    * The internal model. A list of StockItems.
    *
    * @serial
    */
  protected List m_lItems;

  /**
    * Create a new StoringStockTableModel.
    *
    * @param st the Stock to be modelled.
    * @param db the DataBasket to be used to determine visibility.
    * @param cmp a Comparator defining the sort order of the records. If <code>null</code>, records are ordered
    * according to the natural ordering of the StockItems.
    * @param ted a TableEntryDescriptor that can split individual StockItems into a table's cells.
    */
  public StoringStockTableModel (Stock st,
                                 DataBasket db,
                                 Comparator cmp,
                                 TableEntryDescriptor ted) {
    super (ted);

    m_stModel = st;
    m_dbBasket = db;

    if (cmp != null) {
      m_cmpComparator = cmp;
    }

    listenerList = new ListenerHelper (this);

    updateModel();
  }

  /**
    * Get the record at the given index.
    *
    * @param row the index for which to retrieve the record. Element of [0, {@link #getRowCount}).
    * @return the {@link StockItem} to be displayed at the given index. May return <code>null</code> if
    * there is no record at the indicated position.
    *
    * @override Never
    */
  public Object getRecord (int row) {
    ((ListenerHelper) listenerList).needModelUpdate();

    if ((row > -1) && (row < m_lItems.size())) {
      return m_lItems.get (row);
    }
    else {
      return null;
    }
  }

  /**
    * Get the number of records in this model.
    *
    * @override Never
    */
  public int getRowCount() {
    ((ListenerHelper) listenerList).needModelUpdate();

    return m_lItems.size();
  }

  // HelpableListener interface methods

  /**
    * Subscribe as a listener to the model. If the modelled {@link Stock} is a {@link ListenableStock},
    * subscribe as a listener.
    *
    * @override Never
    */
  public void subscribe() {
    if (m_stModel instanceof ListenableStock) {
      ((ListenableStock) m_stModel).addStockChangeListener (this);
    }
  }

  /**
    * Un-Subscribe as a listener from the model. If the modelled {@link Stock} is a {@link ListenableStock},
    * un-subscribe as a listener.
    *
    * @override Never
    */
  public void unsubscribe() {
    if (m_stModel instanceof ListenableStock) {
      ((ListenableStock) m_stModel).removeStockChangeListener (this);
    }
  }

  /**
    * Update the internal model based on the modelled {@link Stock}.
    *
    * @override Never
    */
  public void updateModel() {
    List lItems = new LinkedList();

    for (Iterator i = m_stModel.iterator (m_dbBasket, false); i.hasNext();) {
      lItems.add (i.next());
    }

    Collections.sort (lItems, m_cmpComparator);

    m_lItems = lItems;
  }

  // StockChangeListener interface methods

  /**
    * Update the internal model and inform any listeners according to the received event.
    *
    * <p>This method is public as an implementation detail and must not be called directly.</p>
    *
    * @override Never
    */
  public synchronized void addedStockItems (StockChangeEvent e) {
    if (e.getBasket() == m_dbBasket) {
      checkAdd (e);
    }
  }

  /**
    * Update the internal model and inform any listeners according to the received event.
    *
    * <p>This method is public as an implementation detail and must not be called directly.</p>
    *
    * @override Never
    */
  public void commitAddStockItems (StockChangeEvent e) {
    if (e.getBasket() != m_dbBasket) {
      checkAdd (e);
    }
  }

  /**
    * Update the internal model and inform any listeners according to the received event.
    *
    * <p>This method is public as an implementation detail and must not be called directly.</p>
    *
    * @override Never
    */
  public void rollbackAddStockItems (StockChangeEvent e) {
    if (e.getBasket() == m_dbBasket) {
      checkRemove (e);
    }
  }

  /**
    * Update the internal model and inform any listeners according to the received event.
    *
    * <p>This method is public as an implementation detail and must not be called directly.</p>
    *
    * @override Never
    */
  public void canRemoveStockItems (StockChangeEvent e) throws VetoException {}

  /**
    * Update the internal model and inform any listeners according to the received event.
    *
    * <p>This method is public as an implementation detail and must not be called directly.</p>
    *
    * @override Never
    */
  public void noRemoveStockItems (StockChangeEvent e) {}

  /**
    * Update the internal model and inform any listeners according to the received event.
    *
    * <p>This method is public as an implementation detail and must not be called directly.</p>
    *
    * @override Never
    */
  public void removedStockItems (StockChangeEvent e) {
    checkRemove (e);
  }

  /**
    * Update the internal model and inform any listeners according to the received event.
    *
    * <p>This method is public as an implementation detail and must not be called directly.</p>
    *
    * @override Never
    */
  public void commitRemoveStockItems (StockChangeEvent e) {}

  /**
    * Update the internal model and inform any listeners according to the received event.
    *
    * <p>This method is public as an implementation detail and must not be called directly.</p>
    *
    * @override Never
    */
  public void rollbackRemoveStockItems (StockChangeEvent e) {
    checkAdd (e);
  }

  /**
    * Update the internal model and inform any listeners according to the received event.
    *
    * <p>This method is public as an implementation detail and must not be called directly.</p>
    *
    * @override Never
    */
  public void canEditStockItems (StockChangeEvent e) throws VetoException {}

  /**
    * Update the internal model and inform any listeners according to the received event.
    *
    * <p>This method is public as an implementation detail and must not be called directly.</p>
    *
    * @override Never
    */
  public void noEditStockItems (StockChangeEvent e) {}

  /**
    * Update the internal model and inform any listeners according to the received event.
    *
    * <p>This method is public as an implementation detail and must not be called directly.</p>
    *
    * @override Never
    */
  public void editingStockItems (StockChangeEvent e) {
    if (e.getBasket() != m_dbBasket) {
      checkRemove (e);
    }
  }

  /**
    * Update the internal model and inform any listeners according to the received event.
    *
    * <p>This method is public as an implementation detail and must not be called directly.</p>
    *
    * @override Never
    */
  public void commitEditStockItems (StockChangeEvent e) {
    if (e.getBasket() != m_dbBasket) {
      checkAdd (e);
    }
    else {
      checkUpdate (e);
    }
  }

  /**
    * Update the internal model and inform any listeners according to the received event.
    *
    * <p>This method is public as an implementation detail and must not be called directly.</p>
    *
    * @override Never
    */
  public void rollbackEditStockItems (StockChangeEvent e) {
    if (e.getBasket() != m_dbBasket) {
      checkAdd (e);
    }
    else {
      checkUpdate (e);
    }
  }

  /**
    * Internal helper method. Check where, if at all, the indicated StockItems have been added with respect to
    * the internal model.
    *
    * @override Never
    */
  protected void checkAdd (StockChangeEvent e) {
    updateModel();

    if (m_stModel instanceof CountingStock) {
      fireTableDataChanged(); // for CountingStocks, we cannot clearly identify the rows that changed!
    }
    else {
      int nIdx1 = Integer.MAX_VALUE;
      int nIdx2 = -1;

      for (Iterator i = e.getAffectedItems(); i.hasNext();) {
        int nIdx = m_lItems.indexOf (i.next());

        if (nIdx < nIdx1) {
          nIdx1 = nIdx;
        }

        if (nIdx > nIdx2) {
          nIdx2 = nIdx;
        }
      }

      if (nIdx2 > -1) {
        fireTableRowsInserted (nIdx1, nIdx2);
      }
    }
  }

  /**
    * Internal helper method. Check from where, if at all, the indicated StockItems have been removed with
    * respect to the internal model.
    *
    * @override Never
    */
  protected void checkRemove (StockChangeEvent e) {
    int nIdx1 = Integer.MAX_VALUE;
    int nIdx2 = -1;

    if (!(m_stModel instanceof CountingStock)) {
      for (Iterator i = e.getAffectedItems(); i.hasNext();) {
        int nIdx = m_lItems.indexOf (i.next());

        if (nIdx < nIdx1) {
          nIdx1 = nIdx;
        }

        if (nIdx > nIdx2) {
          nIdx2 = nIdx;
        }
      }
    }

    updateModel();

    if (m_stModel instanceof CountingStock) {
      fireTableDataChanged(); // for CountingStocks, we cannot clearly identify the rows that changed!
    }
    else if (nIdx2 > -1) {
      fireTableRowsDeleted (nIdx1, nIdx2);
    }
  }

  /**
    * Internal helper method. Check for an update in the indicated StockItems.
    *
    * @override Never
    */
  protected void checkUpdate (StockChangeEvent e) {
    checkRemove (e);
    checkAdd (e);
  }
}