package sale;

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

import java.awt.Rectangle;

import javax.swing.*;
import javax.swing.event.*;

import users.*;
import log.*;

import sale.events.*;

import data.DataBasket;
import data.Stock;
import data.Catalog;

import util.*;

/**
  * A single point of sale in a shop.
  *
  * <p>SalesPoints represent single points of sale in a {@link Shop} at which user interaction occurs.
  * Examples for such SalesPoints are cash counters, box offices, ticket vending machines etc.</p>
  *
  * <p>Normally, you will have at least one SalesPoint in your application.</p>
  *
  * <p>Services available at SalesPoints are implemented as {@link SaleProcess processes}. There can be at most
  * one process running at a SalesPoint at any given point of time.</p>
  *
  * <p>SalesPoints are {@link ProcessContext process contexts} to the processes running at them. They provide
  * data and log access, as well as a display and current user. When a SalesPoint is created, a
  * {@link Display display] is attached to it, which will be used by the process.</p>
  *
  * <p>A {@link User user} can be attached to the SalesPoint. Its capabilities will determine what can and cannot
  * be done at the SalesPoint.</p>
  *
  * @author Steffen Zschaler
  * @version 2.0 15/07/1999
  * @since v1.0
  */
public class SalesPoint extends Object implements ProcessContext,
                                                  FormSheetListener,
                                                  SerializableListener {

  /**
    * The process currently running on this SalesPoint, if any.
    *
    * @serial
    */
  protected SaleProcess m_pCurProcess;

  /**
    * The monitor synchronizing process access.
    */
  private transient Object m_oProcessLock;

  /**
    * Return the monitor synchronizing process access.
    *
    * @override Never
    */
  protected final Object getProcessLock() {
    if (m_oProcessLock == null) {
      m_oProcessLock = new Object();
    }

    return m_oProcessLock;
  }

  /**
    * The name of this SalesPoint.
    *
    * @serial
    */
  private String m_sName;

  /**
    * The display currently attached to this SalesPoint, if any. This will be saved/restored by the Shop.
    */
  private transient Display m_dDisplay;

  /**
    * The monitor synchronizing access to the display.
    */
  private transient Object m_oDisplayLock;

  /**
    * Return the monitor synchronizing access to the display.
    *
    * @override Never
    */
  private final Object getDisplayLock() {
    if (m_oDisplayLock == null) {
      m_oDisplayLock = new Object();
    }

    return m_oDisplayLock;
  }

  /**
    * The status display currently attached to this SalesPoint, if any.
    *
    * @serial
    */
  private Display m_dStatus;

  /**
    * The monitor synchronizing access to the status display.
    */
  private transient Object m_oStatusDisplayLock;

  /**
    * Return the monitor synchronizing access to the status display.
    *
    * @override Never
    */
  private final Object getStatusDisplayLock() {
    if (m_oStatusDisplayLock == null) {
      m_oStatusDisplayLock = new Object();
    }

    return m_oStatusDisplayLock;
  }

  /**
    * Flag indicating whether or not the SalesPoint is currently suspended.
    *
    * @serial
    */
  private boolean m_fSuspended = false;
 
  /**
    * The DataBasket currently attached to this SalesPoint, if any.
    *
    * @serial
    */
  private DataBasket m_dbBasket;
  
  /**
    * The monitor synchronizing access to the DataBasket.
    */ 
  private transient Object m_oBasketLock;
  
  /**
    * Return the monitor synchronizing access to the DataBasket.
    *
    * @override Never
    */
  private final Object getBasketLock() {
    if (m_oBasketLock == null) {
      m_oBasketLock = new Object();
    }

    return m_oBasketLock;
  }
  
  /**
    * The User currently attached to this SalesPoint, if any.
    *
    * @serial
    */ 
  private User m_usrUser;
  
  /**
    * The monitor synchronizing access to the User.
    */ 
  private transient Object m_oUserLock;
  
  /**
    * The SalesPoints Framebounds.
    */ 
  private Rectangle m_rSalesPointFrameBounds = null;
  
  /**
    * Return the monitor synchronizing access to the User.
    *
    * @override Never
    */
  private final Object getUserLock() {
    if (m_oUserLock == null) {
      m_oUserLock = new Object();
    }

    return m_oUserLock;
  }

  /**
    * SalesPoint store no data except the default serializable fields. This method exists only for debugging
    * purposes.
    */
  private void writeObject (java.io.ObjectOutputStream oos) throws java.io.IOException {
    util.Debug.print ("Writing SalesPoint: \"" + getName() + "\".", -1);

    oos.defaultWriteObject();

    util.Debug.print ("Finished writing SalesPoint: \"" + getName() + "\".", -1);
  }

  /**
    * Create a new SalesPoint.
    *
    * @param sName the name of the SalesPoint.
    */
  public SalesPoint (String sName) {
    super();

    m_sName = sName;
  }

  /**
    * Return the name of this SalesPoint.
    *
    * @override Never
    */
  public String getName() {
    return m_sName;
  }

  /**
    * Check whether this SalesPoint can be closed. Unless the SalesPoint has been
    * {@link #suspend suspended} before calling <code>canQuit</code> the result of this method may not
    * be reliable.
    *
    * <p>By default, if a process runs on the SalesPoint its {@link SaleProcess#canQuit canQuit}
    * method is called. If no process is running, and <code>fNoPersistence</code> is <code>true</code>,
    * {@link #onCanQuit} is called which opens a MsgForm to ask the user whether he/she really wants to close
    * the SalesPoint. If <code>fNoPersistence</code> is <code>false</code> and no process is running on the
    * SalesPoint, the default implementation always returns <code>true</code>.
    *
    * <p>This method is usually not called directly.</p>
    *
    * @override Sometimes See above for an description of the default implementation.
    *
    * @param fNoPersistence <code>true</code> if the call to <code>canQuit</code> resulted from an
    * explicit call to {@link #quit} or from a call to {@link Shop#shutdown Shop.shutdown (false)}. If
    * <code>fNoPersistence == false</code> you can assume, the state of the SalesPoint will be made persistent
    * before it is closed.
    */
  protected boolean canQuit (boolean fNoPersistence) {
    synchronized (getProcessLock()) {
      if (m_pCurProcess != null) {
        return m_pCurProcess.canQuit (fNoPersistence);
      }
      else {
        if (fNoPersistence) {
          return onCanQuit();
        }
        else {
          return true;
        }
      }
    }
  }

  /**
    * Hook method called to determine whether a SalesPoint with no process running on it can be closed by an
    * explicit quit call. In this method you can assume that the state of the SalesPoint will not be saved and
    * therefore will not be restoreable.
    *
    * @override Sometimes The default implementation opens a {@link sale.stdforms.MsgForm} asking the user if
    * they really want to close the SalesPoint.
    *
    * @return true if the SalesPoint can be closed, false otherwise.
    */
  protected boolean onCanQuit() {
    JDisplayDialog jddConfirmer = new JDisplayDialog();

    final boolean[] abResult = {true};
    final sale.stdforms.MsgForm mf = new sale.stdforms.MsgForm ("Closing \"" + getName() + "\"",
                                                                "Are you sure, you want to close this SalesPoint?");
    mf.removeAllButtons();
    mf.addButton ("Yes", 0, new sale.Action() {
      public void doAction (SaleProcess p, SalesPoint sp) {
        mf.close();
      }
    });

    mf.addButton ("No", 1, new sale.Action() {
      public void doAction (SaleProcess p, SalesPoint sp) {
        abResult[0] = false;
        mf.close();
      }
    });

    jddConfirmer.setVisible (true);
    try {
      jddConfirmer.setFormSheet (mf);
    }
    catch (InterruptedException ie) {
      return false;
    }

    return abResult[0];
  }

  /**
    * Close the SalesPoint.
    *
    * <p>First {@link #suspend suspends} the SalesPoint, then calls {@link #canQuit}. If that returns
    * <code>false</code>, <code>quit</code> will {@link #resume} the SalesPoint and return. Otherwise,
    * it {@link SaleProcess#quit quits} the current process, if any, and
    * {@link Shop#removeSalesPoint removes the SalesPoint from the Shop}.</p>
    *
    * @override Never
    */
  public void quit() {
    SaleProcess p = null;

    synchronized (getProcessLock()) {
      try {
        suspend();
      }
      catch (InterruptedException e) {}

      if (!canQuit (true)) {
        resume();
        return;
      }

      p = m_pCurProcess;
    }

    if (p != null) {
      try {
        // This will resume the process and block until its finished.
        p.quit (true);
      }
      catch (InterruptedException e) {}
    }

    Shop.getTheShop().removeSalesPoint (this);
  }

  // logging
  /**
    * Hook method called when the SalesPoint is added to a Shop. You can write a log entry here.
    *
    * @override Sometimes The default implementation does nothing.
    *
    * @see Shop#addSalesPoint
    * @see Log
    * @see LogEntry
    */
  protected void logSalesPointOpened() {}

  /**
    * Hook method called when the SalesPoint is removed from a Shop. You can write a log entry here.
    *
    * @override Sometimes The default implementation does nothing.
    *
    * @see Shop#removeSalesPoint
    * @see Log
    * @see LogEntry
    */
  protected void logSalesPointClosed() {}

  // Process management

  /**
    * Start a process on this SalesPoint.
    *
    * <p>The process will run in the context of this SalesPoint, and with the
    * DataBasket attached to this SalesPoint.</p>
    *
    * <p>Although there can only be one process at a time running on this
    * SalesPoint, no exception is thrown if a process already runs. Instead,
    * the new process is silently ignored.</p>
    *
    * @override Never
    *
    * @param p the process to be run.
    */
  public final void runProcess (SaleProcess p) {
    synchronized (getProcessLock()) {
      if ((m_pCurProcess == null) && (!m_fSuspended)) {
        m_pCurProcess = p;

        p.attach ((ProcessContext) this);
        p.attach (getBasket());

        p.start();
      }
    }
  }

  /**
    * Get the process currently running on this SalesPoint, if any.
    *
    * @override Never
    */
  public final SaleProcess getCurrentProcess() {
    synchronized (getProcessLock()) {
      return m_pCurProcess;
    }
  }

  /**
    * Suspend the SalesPoint.
    *
    * <p>If a process is running on the SalesPoint, it is {@link SaleProcess#suspend suspended}.
    * The method will only return when the SalesPoint has been properly suspended.</p>
    *
    * @override Never
    *
    * @exception InterruptedException if an interrupt occurred while waiting for the SalesPoint to be
    * suspended.
    */
  public void suspend()
    throws InterruptedException {
    synchronized (getProcessLock()) {

      m_fSuspended = true;

      if (m_pCurProcess != null) {
        m_pCurProcess.suspend();
      }
    }
  }

  /**
    * Resume the SalesPoint.
    *
    * <p>If a process is running on the SalesPoint, it is {@link SaleProcess#resume resumed}.</p>
    *
    * @override Never
    */
  public void resume() {
    synchronized (getProcessLock()) {

      if (m_pCurProcess != null) {
        m_pCurProcess.resume();
      }

      m_fSuspended = false;
    }
  }

  // User management
  /**
    * Attach a user to this SalesPoint.
    * 
    * <p>The user attached to a SalesPoint can be accessed by processes running 
    * on the SalesPoint an can be used to determine capabilities etc.</p>
    *
    * @override Never
    *
    * @param usr the user to be attached.
    * @return the user that was previously attached to this SalesPoint, if any.
    */
  public User attach (User usr) {
    synchronized (getUserLock()) {
      User usrOld = m_usrUser;
      
      m_usrUser = usr;
      
      return usrOld;
    }
  }
  
  /**
    * Detach any user currently attached to this SalesPoint.
    *
    * @override Never
    *
    * @return the user that was previously attached to this SalesPoint, if any.
    */
  public User detachUser() {
    return attach ((User) null);
  }

  /**
    * Get the user currently attached to this SalesPoint.
    *
    * @override Never
    *
    * @return the user currently attached to this SalesPoint, if any.
    */
  public User getUser() {
    synchronized (getUserLock()) {
      return m_usrUser;
    }
  }
    
  // DataBasket management
  /**
    * Attach a DataBasket to this SalesPoint.
    * 
    * @override Never
    *
    * @param db the DataBasket to be attached.
    * @return the DataBasket that was previously attached to this SalesPoint, if any.
    */
  public DataBasket attach (DataBasket db) {
    synchronized (getBasketLock()) {
      DataBasket dbOld = m_dbBasket;
      
      m_dbBasket = db;
      
      return dbOld;
    }
  }
  
  /**
    * Detach any DataBasket currently attached to this SalesPoint.
    *
    * @override Never
    *
    * @return the DataBasket that was previously attached to this SalesPoint, if any.
    */
  public DataBasket detachBasket() {
    return attach ((DataBasket) null);
  }
  
  /**
    * Get the DataBasket currently attached to this SalesPoint.
    *
    * @override Never
    *
    * @return the DataBasket currently attached to this SalesPoint, if any.
    */
  public DataBasket getBasket() {
    synchronized (getBasketLock()) {
      return m_dbBasket;
    }
  }
  
  // display management
  /**
    * Attach a status display to the SalesPoint.
    *
    * <p>This display can be used to give status information about the SalesPoint. It can also
    * be used to trigger background processes for the SalesPoint. It should not be used to trigger
    * processes on the SalesPoint.</p>
    *
    * <p>If the given display is not <code>null</code>, it must be {@link Display#isUseableDisplay useable}.</p>
    *
    * @override Never
    *
    * @param dStatus the new status display for this SalesPoint.
    *
    * @return the previous status display, if any.
    */
  protected Display attachStatusDisplay (Display dStatus) {
    synchronized (getStatusDisplayLock()) {
      if (m_dStatus != null) {
        m_dStatus.closeFormSheet();
        m_dStatus.setMenuSheet (null);
      }

      Display dReturn = m_dStatus;
      m_dStatus = dStatus;

      setStatusFormSheet (getDefaultStatusFormSheet());
      setStatusMenuSheet (getDefaultStatusMenuSheet());

      return dReturn;
    }
  }

  /**
    * Detach the current status display.
    *
    * @override Never
    *
    * @return the previous status display, if any.
    */
  protected Display detachStatusDisplay() {
    return attachStatusDisplay (null);
  }

  /**
    * Set a FormSheet in the SalesPoint's status display.
    *
    * <p>Status display FormSheet's are always nonmodal, which is why this method returns
    * immediately after setting the FormSheet and can never throw an InterruptedException.</p>
    *
    * @override Never
    *
    * @param fs the FormSheet to be set.
    */
  protected void setStatusFormSheet (FormSheet fs) {
    synchronized (getStatusDisplayLock()) {
      if (m_dStatus == null) {
        return;
      }

      if (fs != null) {
        fs.setWaitResponse (false);
        fs.attach (this);
      }

      try {
        m_dStatus.setFormSheet (fs);
      }
      catch (InterruptedException e) {}
    }
  }

  /**
    * Set a MenuSheet in the SalesPoint's status display.
    *
    * @override Never
    *
    * @param ms the MenuSheet to be set.
    */
  protected void setStatusMenuSheet (MenuSheet ms) {
    synchronized (getStatusDisplayLock()) {
      if (m_dStatus == null) {
        return;
      }

      if (ms != null) {
        ms.attach (this);
      }

      m_dStatus.setMenuSheet (ms);
    }
  }

  /**
    * Get the default status MenuSheet for this SalesPoint.
    *
    * <p>Unless you specify differently through an explicit call to {@link #setStatusMenuSheet}, the Framework
    * will use this MenuSheet for the SalesPoint's status display.</p>
    *
    * @override Sometimes The default implementation returns <code>null</code> to indicate no MenuSheet.
    *
    * @see #attachStatusDisplay
    */
  protected MenuSheet getDefaultStatusMenuSheet() {
    return null;
  }

  /**
    * Get the default status FormSheet for this SalesPoint.
    *
    * <p>Unless you specify differently through an explicit call to {@link #setStatusFormSheet}, the Framework
    * will use this FormSheet for the SalesPoint's status display.</p>
    *
    * @override Sometimes The default implementation returns an empty FormSheet.
    *
    * @see #attachStatusDisplay
    */
  protected FormSheet getDefaultStatusFormSheet() {

    FormSheetContentCreator fscc = new FormSheetContentCreator() {
      protected void createFormSheetContent (final FormSheet fs) {

        fs.setComponent (new JPanel());

        fs.removeAllButtons();
      }
    };

    return new FormSheet (getName(), fscc, false);
  }

  /**
    * Internal communication method called by Shop to attach a display during restoration of the Shop 
    * from a stream.
    *
    * @override Never
    *
    * @param d the display just loaded.
    */
  void attachLoadedDisplay (Display d) {
    synchronized (getDisplayLock()) {
      if (hasUseableDisplay (null)) {
        detachDisplay();
      }
      
      m_dDisplay = d;
    }
  }
  
  /**
    * Attach a new display to the SalesPoint.
    *
    * <p>Any Form- or MenuSheets displayed on the current display will be removed, and the SalesPoint's
    * default sheets will be set on the new display.</p>
    *
    * @override Never
    *
    * @param d the new display
    *
    * @return the previously attached display, if any.
    *
    * @see #getDefaultFormSheet
    * @see #getDefaultMenuSheet
    */
  public Display attach (Display d) {
    synchronized (getDisplayLock()) {
      if (hasUseableDisplay (null)) {
        m_dDisplay.removeFormSheetListener (this);

        try {
          m_dDisplay.setFormSheet (null);
        }
        catch (InterruptedException e) {}
        m_dDisplay.setMenuSheet (null);
      }

      Display dOld = m_dDisplay;

      m_dDisplay = d;

      // We can't use the previous FormSheet on the new display, as it will have been cancelled by the
      // setFormSheet call.
      if (hasUseableDisplay (null)) {
        m_dDisplay.addFormSheetListener (this);
        setDefaultSheets();
      }

      return dOld;
    }
  }

  /**
    * Detach the current display.
    *
    * <p>Any Form- or MenuSheet on the current display will be removed before detaching the display.</p>
    *
    * @override Never
    *
    * @return the detached display, if any.
    */
  public Display detachDisplay() {
    return attach ((Display) null);
  }

  /**
    * Return the display of this SalesPoint.
    *
    * @return the display of this SalesPoint.
    */
  public Display getDisplay() {
    return m_dDisplay;
  }
  
  /**
    * Set the default Form- and MenuSheet on the currently attached display.
    *
    * @override Never
    *
    * @see #getDefaultMenuSheet
    * @see #getDefaultFormSheet
    */
  private void setDefaultSheets() {
    synchronized (getDisplayLock()) {
      if (hasUseableDisplay (null)) {
        FormSheet fs = getDefaultFormSheet();

        if (fs != null) {
          fs.setWaitResponse (false);
          fs.attach (this);
        }

        try {
          m_dDisplay.setFormSheet (fs);
        }
        catch (InterruptedException e) {}

        MenuSheet ms = getDefaultMenuSheet();

        if (ms != null) {
          ms.attach (this);
        }

        m_dDisplay.setMenuSheet (ms);
      }
    }
  }

  /**
    * Get the default FormSheet for this SalesPoint.
    *
    * <p>The default FormSheet will be displayed whenever there is a current user (and, thus, a display),
    * but no process is running and no other FormSheet is being displayed.</p>
    *
    * @override Always The default implementation returns a FormSheet that simply states the name of the
    * SalesPoint.
    *
    * @return the default FormSheet, if any.
    */
  protected FormSheet getDefaultFormSheet() {
    return new sale.stdforms.MsgForm (getName(), "This is the default FormSheet of SalesPoint " + getName());
  }

  /**
    * Get the default MenuSheet for this SalesPoint.
    *
    * <p>The default MenuSheet will be displayed whenever there is a current user (and, thus, a display),
    * but no process is running.</p>
    *
    * @override Always The default implementation returns <code>null</code> indicating no MenuSheet.
    *
    * @return the default MenuSheet, if any.
    */
  protected MenuSheet getDefaultMenuSheet() {
    return null;
  }

  // ProcessContext methods
  /**
    * Allow a process to set a FormSheet on the SalesPoint's current display.
    *
    * <p>The process launching the FormSheet as well as this SalesPoint will be attached to the
    * FormSheet prior to displaying it. Thus, Actions triggered by the FormSheet will run in the
    * correct context and will be able to access the process and the SalesPoint.</p>
    *
    * <p>If the FormSheet requests that the Framework wait for it being closed,
    * <code>setFormSheet()</code> will block until the FormSheet was closed or an interrupt occured.</p>
    *
    * @override Never
    *
    * @param p the process that wants to display the FormSheet.
    * @param fs the FormSheet to be displayed.
    *
    * @exception InterruptedException if an interrupt occurred while waiting for the FormSheet to be
    * closed.
    *
    * @see sale.Action
    * @see FormSheet#waitResponse
    */
  public void setFormSheet (SaleProcess p, FormSheet fs)
    throws InterruptedException {

    if (fs != null) {
      fs.attach (p);
      fs.attach (this);
    }

    Display d;
    synchronized (getDisplayLock()) {
      d = m_dDisplay;
    }

    // not in synchronized, as this call might block, and we don't want dead locks.
    d.setFormSheet (fs);
  }
  
  /**
    * Sets the Framebounds of the SalesPoints assoziated Display (JDisplayFrame).
    *
    * <p>Example:<p>
    * <code>sp.setSalesPointFrameBounds (new Rectangle (10,10,200,200));<code>
    *
    * This moves the SalesPointFrame to Position (10,10) with a size of (200,200).
    *
    * @override Sometimes
    */
  public void setSalesPointFrameBounds (Rectangle r) {
    if (getDisplay() == null) {
      m_rSalesPointFrameBounds = r;
    }
    else {
      m_rSalesPointFrameBounds = r;
      if (Shop.getTheShop() != null) {
        if (Shop.getTheShop().getShopState() == Shop.RUNNING) {
            
          // nicht sehr schön - vielmehr sollte hier die Schnittstelle von Display
          // erweitert werden, um unabhängig von der Art des Displays zu sein.
          // NOCH ZU ÄNDERN !!! - NICHT VERGESSEN
          ((JDisplayFrame)getDisplay()).setBounds (getSalesPointFrameBounds());
          ((JDisplayFrame)getDisplay()).hide();
          ((JDisplayFrame)getDisplay()).show();
        }
      }
    }
  }
  
  /**
    * Returns the Framebounds of the SalesPoints assoziated Display(JDisplayFrame).
    *
    * @override Sometimes
    */
  public Rectangle getSalesPointFrameBounds () {
    return m_rSalesPointFrameBounds;
  }

  /**
    * Allow a process to pop up a FormSheet on the SalesPoint's current display.
    *
    * <p>The process launching the FormSheet as well as this SalesPoint will be attached to the
    * FormSheet prior to displaying it. Thus, Actions triggered by the FormSheet will run in the
    * correct context and will be able to access the process and the SalesPoint.</p>
    *
    * <p>If the FormSheet requests that the Framework wait for it being closed,
    * <code>popUpFormSheet</code> will block until the FormSheet was closed or an interrupt occured.</p>
    *
    * @override Never
    *
    * @param p the process that wants to display the FormSheet.
    * @param fs the FormSheet to be displayed.
    *
    * @exception InterruptedException if an interrupt occurred while waiting for the FormSheet to be
    * closed.
    *
    * @see sale.Action
    * @see FormSheet#waitResponse
    */
  public void popUpFormSheet (SaleProcess p, FormSheet fs)
    throws InterruptedException {

    if (fs != null) {
      fs.attach (p);
      fs.attach (this);
    }

    Display d;
    synchronized (getDisplayLock()) {
      d = m_dDisplay;
    }

    d.popUpFormSheet (fs);
  }

  /**
    * Allow a process to set a MenuSheet on the SalesPoint's current display.
    *
    * <p>The process setting the MenuSheet as well as this SalesPoint will be attached to the
    * MenuSheet prior to displaying it. Thus, Actions triggered by the MenuSheet will run in the
    * correct context and will be able to access the process and the SalesPoint.</p>
    *
    * @override Never
    *
    * @param p the process that wants to display the MenuSheet.
    * @param ms the MenuSheet to be displayed.
    *
    * @see sale.Action
    */
  public void setMenuSheet (SaleProcess p, MenuSheet ms) {

    if (ms != null) {
      ms.attach (p);
      ms.attach (this);
    }

    synchronized (getDisplayLock()) {
      m_dDisplay.setMenuSheet (ms);
    }
  }

  /**
    * True, if the SalesPoint currently has a display and this display is useable.
    *
    * @override Never
    *
    * @param p the process querying, unused.
    *
    * @see Display#isUseableDisplay
    */
  public boolean hasUseableDisplay (SaleProcess p) {
    synchronized (getDisplayLock()) {
      return ((m_dDisplay != null) &&
              (m_dDisplay.isUseableDisplay()));
    }
  }

  /**
    * Log the given Loggable.
    *
    * <p>The given loggable object will be logged into the global log file unless you override this method.</p>
    *
    * @override Sometimes
    *
    * @exception IOException if an error occurs while trying to write the log data.
    *
    * @param p the SalesProcess demanding logging, unused.
    * @param la the object to be logged.
    */
  public void log (SaleProcess p, Loggable la)
    throws IOException {
    Shop.getTheShop().log (la);
  }

  /**
    * Get the current user for the given process. This is the user, if any, 
    * previously attached with the {@link #attach(User)} method.
    *
    * @override Never
    *
    * @param p the process querying, unused.
    *
    * @return the current user
    *
    * @see #getUser
    */
  public final User getCurrentUser (SaleProcess p) {
    return getUser();
  }

  /**
    * Return a Stock for a given name.
    *
    * @override Sometimes
    *
    * @param sName the name of the Stock.
    */
  public Stock getStock (String sName) {
    return Shop.getTheShop().getStock (sName);
  }

  /**
    * Return a Catalog for a given name.
    *
    * @override Sometimes
    *
    * @param sName the name of the Catalog.
    */
  public Catalog getCatalog (String sName) {
    return Shop.getTheShop().getCatalog (sName);
  }

  /**
    * Notification that a process started on a SalesPoint.
    *
    * <p>This will remove all SalesPoint owned Form- and MenuSheets from the display, as to
    * make room for the process' sheets.</p>
    *
    * @override Never
    *
    * @param p the process that was just launched.
    */
  public void processStarted (SaleProcess p) {
    synchronized (getDisplayLock()) {
      if (hasUseableDisplay (null)) {
        m_dDisplay.removeFormSheetListener (this);

        try {
          m_dDisplay.setFormSheet (null);
        }
        catch (InterruptedException e) {}
        m_dDisplay.setMenuSheet (null);
      }
    }
  }

  /**
    * Notification that a process finished running on this SalesPoint.
    *
    * <p>This will restore the SalesPoint's default sheets.</p>
    *
    * @override Never
    */
  public void processFinished (SaleProcess p) {
    synchronized (getProcessLock()) {
      m_pCurProcess = null;
    }

    synchronized (getDisplayLock()) {
      if (hasUseableDisplay (null)) {
        m_dDisplay.addFormSheetListener (this);
        setDefaultSheets();
      }
    }
  }

  // FormSheetListener interface methods
  /**
    * Dummy interface to conform by the FormSheetListener interface.
    *
    * @override Never
    */
  public void formSheetSet (FormSheetEvent e) {}

  /**
    * Implemented to make sure there always is a FormSheet.
    *
    * @override Never
    */
  public void formSheetRemoved (FormSheetEvent e) {
    if (e.isExplicit()) {
      synchronized (getDisplayLock()) {
        if (hasUseableDisplay (null)) {
          FormSheet fs = getDefaultFormSheet();

          if (fs != null) {
            fs.setWaitResponse (false);
            fs.attach (this);
          }

          try {
            m_dDisplay.setFormSheet (fs);
          }
          catch (InterruptedException ex) {}
        }
      }
    }
  }

  /**
    * Return a String description of this SalesPoint: the name.
    *
    * @override Sometimes
    */
  public String toString() {
    return getName();
  }
}