package data.filters;

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

import util.*;

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

/**
  * <i>Abstract</i> superclass of all Stock filters. By using a Stock filter you can present partial views of
  * a Stock to certain parts of your application, e.g., to the GUI elements. However, you cannot use this Stock
  * as a replacement for a 'real' Stock, e.g., as an item in another Stock.
  *
  * <p>The concrete filter condition is implemented by subclassing either {@link CountingStockFilter} or
  * {@link StoringStockFilter} and overriding some method. The concrete semantics is documented with the
  * concrete subclass of AbstractStockFilter.</p>
  *
  * @author Steffen Zschaler
  * @version 2.0 19/08/1999
  * @since v2.0
  */
public abstract class AbstractStockFilter extends Object implements ListenableStock,
                                                                    StockChangeListener {

  /**
    * The Stock that gets filtered.
    *
    * @serial
    */
  protected Stock m_stSource;

  /**
    * The list of listeners of this Stock.
    *
    * @serial
    */
  protected ListenerHelper m_lhListeners = new ListenerHelper();

  /**
    * After reading the default serializable fields of the class, re-establish the listener link to our
    * source.
    *
    * @override Never
    */
  private void readObject (ObjectInputStream ois) throws IOException, ClassNotFoundException {
    ois.defaultReadObject();

    if (m_stSource instanceof ListenableStock) {
      ((ListenableStock) m_stSource).addStockChangeListener (this);
    }
  }

  /**
    * Create a new AbstractStockFilter. May only be called by subclasses.
    *
    * @param stSource the Stock to be filtered.
    */
  protected AbstractStockFilter (Stock stSource) {
    super();

    m_stSource = stSource;

    if (stSource instanceof ListenableStock) {
      ((ListenableStock) stSource).addStockChangeListener (this);
    }
  }

  /**
    * Add the given item to the source Stock.
    *
    * @override Never
    */
  public void add (StockItem si, DataBasket db) {
    m_stSource.add (si, db);
  }

  /**
    * Add the given Stock to the source Stock.
    *
    * @override Never
    */
  public void addStock (Stock st, DataBasket db, boolean fRemove) {
    m_stSource.addStock (st, db, fRemove);
  }

  /**
    * Returns <code>(countItems (sKey, db) >= 0)</code>.
    *
    * @override Sometimes
    */
  public boolean contains (String sKey, DataBasket db) {
    return (countItems (sKey, db) >= 0);
  }

  /**
    * Remove the given item from the source Stock.
    *
    * @override Never
    */
  public StockItem remove (String sKey, DataBasket db)
    throws VetoException {
    if (contains (sKey, db)) {
      return m_stSource.remove (sKey, db);
    }
    else {
      return null;
    }
  }

  /**
    * Remove the given item from the source Stock.
    *
    * @override Never
    */
  public StockItem remove (StockItem si, DataBasket db)
    throws VetoException {
    if (contains (si, db)) {
      return m_stSource.remove (si, db);
    }
    else {
      return null;
    }
  }

  /**
    * Create an iterator that will return all items that match the condition.
    *
    * @override Never
    */
  public Iterator iterator (final DataBasket db, final boolean fForEdit) {
    class I implements Iterator {
      private Iterator m_iKeys;
      private Iterator m_iItems;

      public I() {
        super();

        m_iKeys = keySet (db).iterator();
      }

      public boolean hasNext() {
        return findNext();
      }

      public Object next() {
        if (!findNext()) {
          throw new NoSuchElementException ("No more elements in Stock.");
        }

        return m_iItems.next();
      }

      public void remove() {
        if (m_iItems == null) {
          throw new IllegalStateException();
        }

        m_iItems.remove();
      }

      private boolean findNext() {
        if (m_iItems == null) {
          if (m_iKeys.hasNext()) {
            m_iItems = get ((String) m_iKeys.next(), db, fForEdit);
          }
          else {
            return false;
          }
        }

        while ((m_iItems.hasNext()) ||
               (m_iKeys.hasNext())) {
          if (m_iItems.hasNext()) {
            return true;
          }

          m_iItems = get ((String) m_iKeys.next(), db, fForEdit);
        }

        return false;
      }
    }

    return new I();
  }

  /**
    * Get a filtered key set.
    *
    * @override Never
    */
  public Set keySet (DataBasket db) {
    Set stKeys = m_stSource.keySet (db);

    for (Iterator i = stKeys.iterator(); i.hasNext();) {
      String sKey = (String) i.next();

      if (!contains (sKey, db)) {
        stKeys.remove (sKey);
      }
    }

    return stKeys;
  }

  /**
    * Calculate the total value of the Stock, evaluating only items that match the condition.
    *
    * @override Never
    */
  public Value sumStock (DataBasket db, CatalogItemValue civ, Value vInit) {
    Set stKeys = keySet (db);

    for (Iterator i = stKeys.iterator(); i.hasNext();) {
      String sKey = (String) i.next();

      try {
        vInit.addAccumulating (civ.getValue (getCatalog (db).get (sKey, db, false)).multiply (countItems (sKey, db)));
      }
      catch (VetoException ex) {}
    }

    return vInit;
  }

  /**
    * Fill the source Stock.
    *
    * @override Never
    */
  public Value fillStockWithValue (DataBasket db, Value vTarget, StockFromValueCreator sfvc) {
    return m_stSource.fillStockWithValue (db, vTarget, sfvc);
  }

  /**
    * Calculate the size of the source Stock, considering only items that match the condition.
    *
    * @override Never
    */
  public int size (DataBasket db) {
    Set stKeys = keySet (db);
    int nSize = 0;

    for (Iterator i = stKeys.iterator(); i.hasNext();) {
      nSize += countItems ((String) i.next(), db);
    }

    return nSize;
  }

  /**
    * Get the source Stock's Catalog.
    *
    * @override Never
    */
  public Catalog getCatalog (DataBasket db) {
    return m_stSource.getCatalog (db);
  }

  // StockItem interface methods

  /**
    * Get the source Stock's Stock.
    *
    * @override Never
    */
  public Stock getStock() {
    return m_stSource.getStock();
  }

  /**
    * Get the source Stock's associated item.
    *
    * @override Never
    */
  public CatalogItem getAssociatedItem (DataBasket db) {
    return m_stSource.getAssociatedItem (db);
  }

  // Nameable interface methods
  /**
    * Attach the NameContext to the source Stock.
    *
    * @override Never
    */
  public NameContext attach (NameContext nc) {
    return m_stSource.attach (nc);
  }

  /**
    * Detach the current NameContext from the source Stock.
    *
    * @override Never
    */
  public NameContext detachNC() {
    return m_stSource.detachNC();
  }

  /**
    * Set the source Stock's name.
    *
    * @override Never
    */
  public void setName (String sName, DataBasket db)
    throws NameContextException {
    m_stSource.setName (sName, db);
  }

  /**
    * Get the source Stock's name.
    *
    * @override Never
    */
  public String getName() {
    return m_stSource.getName();
  }

  /**
    * Register the listener with the source Stock.
    *
    * @override Never
    */
  public void addPropertyChangeListener (PropertyChangeListener pcl) {
    m_stSource.addPropertyChangeListener (pcl);
  }

  /**
    * Un-Register the listener with the source Stock.
    *
    * @override Never
    */
  public void removePropertyChangeListener (PropertyChangeListener pcl) {
    m_stSource.removePropertyChangeListener (pcl);
  }

  /**
    * Register the listener with the source Stock.
    *
    * @override Never
    */
  public void addNameListener (PropertyChangeListener pcl) {
    m_stSource.addNameListener (pcl);
  }

  /**
    * Un-Register the listener with the source Stock.
    *
    * @override Never
    */
  public void removeNameListener (PropertyChangeListener pcl) {
    m_stSource.removeNameListener (pcl);
  }

  /**
    * Compare the source Stock to the object.
    *
    * @override Never
    */
  public int compareTo (Object o) {
    return m_stSource.compareTo (o);
  }

  /**
    * @override Always
    */
  public abstract Object clone();

  // ListenableStock interface methods
  /**
    * Register a listener that will receive events when the Stock's contents change.
    *
    * @override Never
    */
  public void addStockChangeListener (StockChangeListener scl) {
    m_lhListeners.add (StockChangeListener.class, scl);
  }

  /**
    * Un-Register a listener that received events when the Stock's contents changed.
    *
    * @override Never
    */
  public void removeStockChangeListener (StockChangeListener scl) {
    m_lhListeners.remove (StockChangeListener.class, scl);
  }

  // StockChangeListener interface methods

  /**
    * Receive the event from the source Stock, translate and propagate it to any listeners.
    *
    * @override Never
    */
  public void addedStockItems (StockChangeEvent e) {
    Set stItems = new HashSet();
    for (Iterator i = e.getAffectedItems(); i.hasNext();) {
      StockItem si = (StockItem) i.next();

      if (contains (si, e.getBasket())) {
        stItems.add (si);
      }
    }

    fireStockItemsAdded (new StockFilterEvent (this, e.getAffectedKey(), stItems, e.getBasket()));
  }

  /**
    * Receive the event from the source Stock, translate and propagate it to any listeners.
    *
    * @override Never
    */
  public void commitAddStockItems (StockChangeEvent e) {
    Set stItems = new HashSet();
    for (Iterator i = e.getAffectedItems(); i.hasNext();) {
      StockItem si = (StockItem) i.next();

      if (contains (si, e.getBasket())) {
        stItems.add (si);
      }
    }

    fireStockItemsAddCommit (new StockFilterEvent (this, e.getAffectedKey(), stItems, e.getBasket()));
  }

  /**
    * Receive the event from the source Stock, translate and propagate it to any listeners.
    *
    * @override Never
    */
  public void rollbackAddStockItems (StockChangeEvent e) {
    Set stItems = new HashSet();
    for (Iterator i = e.getAffectedItems(); i.hasNext();) {
      stItems.add (i.next());
    }

    fireStockItemsAddRollback (new StockFilterEvent (this, e.getAffectedKey(), stItems, e.getBasket()));
  }

  /**
    * Receive the event from the source Stock, translate and propagate it to any listeners.
    *
    * @override Never
    */
  public void canRemoveStockItems (StockChangeEvent e)
    throws VetoException {
    Set stItems = new HashSet();
    for (Iterator i = e.getAffectedItems(); i.hasNext();) {
      StockItem si = (StockItem) i.next();

      if (contains (si, e.getBasket())) {
        stItems.add (si);
      }
    }

    fireCanRemoveStockItems (new StockFilterEvent (this, e.getAffectedKey(), stItems, e.getBasket()));
  }

  /**
    * Receive the event from the source Stock, translate and propagate it to any listeners.
    *
    * @override Never
    */
  public void noRemoveStockItems (StockChangeEvent e) {
    Set stItems = new HashSet();
    for (Iterator i = e.getAffectedItems(); i.hasNext();) {
      StockItem si = (StockItem) i.next();

      if (contains (si, e.getBasket())) {
        stItems.add (si);
      }
    }

    fireStockItemsNoRemove (new StockFilterEvent (this, e.getAffectedKey(), stItems, e.getBasket()));
  }

  /**
    * Receive the event from the source Stock, translate and propagate it to any listeners.
    *
    * @override Never
    */
  public void removedStockItems (StockChangeEvent e) {
    Set stItems = new HashSet();
    for (Iterator i = e.getAffectedItems(); i.hasNext();) {
      stItems.add (i.next());
    }

    fireStockItemsRemoved (new StockFilterEvent (this, e.getAffectedKey(), stItems, e.getBasket()));
  }

  /**
    * Receive the event from the source Stock, translate and propagate it to any listeners.
    *
    * @override Never
    */
  public void commitRemoveStockItems (StockChangeEvent e) {
    Set stItems = new HashSet();
    for (Iterator i = e.getAffectedItems(); i.hasNext();) {
      stItems.add (i.next());
    }

    fireStockItemsRemoveCommit (new StockFilterEvent (this, e.getAffectedKey(), stItems, e.getBasket()));
  }

  /**
    * Receive the event from the source Stock, translate and propagate it to any listeners.
    *
    * @override Never
    */
  public void rollbackRemoveStockItems (StockChangeEvent e) {
    Set stItems = new HashSet();
    for (Iterator i = e.getAffectedItems(); i.hasNext();) {
      StockItem si = (StockItem) i.next();

      if (contains (si, e.getBasket())) {
        stItems.add (si);
      }
    }

    fireStockItemsRemoveRollback (new StockFilterEvent (this, e.getAffectedKey(), stItems, e.getBasket()));
  }

  /**
    * Receive the event from the source Stock, translate and propagate it to any listeners.
    *
    * @override Never
    */
  public void canEditStockItems (StockChangeEvent e) throws VetoException {
    Set stItems = new HashSet();
    for (Iterator i = e.getAffectedItems(); i.hasNext();) {
      StockItem si = (StockItem) i.next();

      if (contains (si, e.getBasket())) {
        stItems.add (si);
      }
    }

    fireCanEditStockItems (new StockFilterEvent (this, e.getAffectedKey(), stItems, e.getBasket()));
  }

  /**
    * Receive the event from the source Stock, translate and propagate it to any listeners.
    *
    * @override Never
    */
  public void noEditStockItems (StockChangeEvent e) {
    Set stItems = new HashSet();
    for (Iterator i = e.getAffectedItems(); i.hasNext();) {
      StockItem si = (StockItem) i.next();

      if (contains (si, e.getBasket())) {
        stItems.add (si);
      }
    }

    fireStockItemsNoEdit (new StockFilterEvent (this, e.getAffectedKey(), stItems, e.getBasket()));
  }

  /**
    * Receive the event from the source Stock, translate and propagate it to any listeners.
    *
    * @override Never
    */
  public void editingStockItems (StockChangeEvent e) {
    Set stItems = new HashSet();
    for (Iterator i = e.getAffectedItems(); i.hasNext();) {
      StockItem si = (StockItem) i.next();

      if (contains (si, e.getBasket())) {
        stItems.add (si);
      }
    }

    fireEditingStockItems (new StockFilterEvent (this, e.getAffectedKey(), stItems, e.getBasket()));
  }

  /**
    * Receive the event from the source Stock, translate and propagate it to any listeners.
    *
    * @override Never
    */
  public void commitEditStockItems (StockChangeEvent e) {
    Set stItems = new HashSet();
    for (Iterator i = e.getAffectedItems(); i.hasNext();) {
      StockItem si = (StockItem) i.next();

      if (contains (si, e.getBasket())) {
        stItems.add (si);
      }
    }

    fireStockItemsEditCommit (new StockFilterEvent (this, e.getAffectedKey(), stItems, e.getBasket()));
  }

  /**
    * Receive the event from the source Stock, translate and propagate it to any listeners.
    *
    * @override Never
    */
  public void rollbackEditStockItems (StockChangeEvent e) {
    Set stItems = new HashSet();
    for (Iterator i = e.getAffectedItems(); i.hasNext();) {
      StockItem si = (StockItem) i.next();

      if (contains (si, e.getBasket())) {
        stItems.add (si);
      }
    }

    fireStockItemsEditRollback (new StockFilterEvent (this, e.getAffectedKey(), stItems, e.getBasket()));
  }

  /**
    * Fire an event to any listeners.
    *
    * @override Never
    */
  protected void fireStockItemsAdded (StockChangeEvent e) {
    Object[] listeners = m_lhListeners.getListenerList();

    for (int i = listeners.length-2; i>=0; i-=2) {
      if (listeners[i] == StockChangeListener.class) {

        ((StockChangeListener) listeners[i+1]).addedStockItems (e);
      }
    }
  }

  /**
    * Fire an event to any listeners.
    *
    * @override Never
    */
  protected void fireStockItemsAddCommit (StockChangeEvent e) {
    Object[] listeners = m_lhListeners.getListenerList();

    for (int i = listeners.length-2; i>=0; i-=2) {
      if (listeners[i] == StockChangeListener.class) {

        ((StockChangeListener) listeners[i+1]).commitAddStockItems (e);
      }
    }
  }

  /**
    * Fire an event to any listeners.
    *
    * @override Never
    */
  protected void fireStockItemsAddRollback (StockChangeEvent e) {
    Object[] listeners = m_lhListeners.getListenerList();

    for (int i = listeners.length-2; i>=0; i-=2) {
      if (listeners[i] == StockChangeListener.class) {

        ((StockChangeListener) listeners[i+1]).rollbackAddStockItems (e);
      }
    }
  }

  /**
    * Fire an event to any listeners.
    *
    * @override Never
    */
  protected void fireStockItemsNoRemove (StockChangeEvent e) {
    Object[] listeners = m_lhListeners.getListenerList();

    for (int i = listeners.length-2; i>=0; i-=2) {
      if (listeners[i] == StockChangeListener.class) {

        ((StockChangeListener) listeners[i+1]).noRemoveStockItems (e);
      }
    }
  }

  /**
    * Fire an event to any listeners.
    *
    * @override Never
    */
  protected void fireStockItemsRemoved (StockChangeEvent e) {
    Object[] listeners = m_lhListeners.getListenerList();

    for (int i = listeners.length-2; i>=0; i-=2) {
      if (listeners[i] == StockChangeListener.class) {

        ((StockChangeListener) listeners[i+1]).removedStockItems (e);
      }
    }
  }

  /**
    * Fire an event to any listeners.
    *
    * @override Never
    */
  protected void fireStockItemsRemoveCommit (StockChangeEvent e) {
    Object[] listeners = m_lhListeners.getListenerList();

    for (int i = listeners.length-2; i>=0; i-=2) {
      if (listeners[i] == StockChangeListener.class) {

        ((StockChangeListener) listeners[i+1]).commitRemoveStockItems (e);
      }
    }
  }

  /**
    * Fire an event to any listeners.
    *
    * @override Never
    */
  protected void fireStockItemsRemoveRollback (StockChangeEvent e) {
    Object[] listeners = m_lhListeners.getListenerList();

    for (int i = listeners.length-2; i>=0; i-=2) {
      if (listeners[i] == StockChangeListener.class) {

        ((StockChangeListener) listeners[i+1]).rollbackRemoveStockItems (e);
      }
    }
  }

  /**
    * Fire an event to any listeners.
    *
    * @override Never
    */
  protected void fireCanRemoveStockItems (StockChangeEvent e)
    throws VetoException {
    Object[] listeners = m_lhListeners.getListenerList();

    for (int i = listeners.length-2; i>=0; i-=2) {
      if (listeners[i] == StockChangeListener.class) {
        ((StockChangeListener) listeners[i+1]).canRemoveStockItems (e);
      }
    }
  }

  /**
    * Fire an event to any listeners.
    *
    * @override Never
    */
  protected void fireCanEditStockItems (StockChangeEvent e)
    throws VetoException {
    Object[] listeners = m_lhListeners.getListenerList();

    for (int i = listeners.length-2; i>=0; i-=2) {
      if (listeners[i] == StockChangeListener.class) {
        ((StockChangeListener) listeners[i+1]).canEditStockItems (e);
      }
    }
  }

  /**
    * Fire an event to any listeners.
    *
    * @override Never
    */
  protected void fireStockItemsNoEdit (StockChangeEvent e) {
    Object[] listeners = m_lhListeners.getListenerList();

    for (int i = listeners.length-2; i>=0; i-=2) {
      if (listeners[i] == StockChangeListener.class) {

        ((StockChangeListener) listeners[i+1]).noEditStockItems (e);
      }
    }
  }

  /**
    * Fire an event to any listeners.
    *
    * @override Never
    */
  protected void fireEditingStockItems (StockChangeEvent e) {
    Object[] listeners = m_lhListeners.getListenerList();

    for (int i = listeners.length-2; i>=0; i-=2) {
      if (listeners[i] == StockChangeListener.class) {

        ((StockChangeListener) listeners[i+1]).editingStockItems (e);
      }
    }
  }

  /**
    * Fire an event to any listeners.
    *
    * @override Never
    */
  protected void fireStockItemsEditCommit (StockChangeEvent e) {
    Object[] listeners = m_lhListeners.getListenerList();

    for (int i = listeners.length-2; i>=0; i-=2) {
      if (listeners[i] == StockChangeListener.class) {

        ((StockChangeListener) listeners[i+1]).commitEditStockItems (e);
      }
    }
  }

  /**
    * Fire an event to any listeners.
    *
    * @override Never
    */
  protected void fireStockItemsEditRollback (StockChangeEvent e) {
    Object[] listeners = m_lhListeners.getListenerList();

    for (int i = listeners.length-2; i>=0; i-=2) {
      if (listeners[i] == StockChangeListener.class) {

        ((StockChangeListener) listeners[i+1]).rollbackEditStockItems (e);
      }
    }
  }
}