package log;

import java.io.*;

/**
  * Represents a log file.
  *
  * <p>There is one global writable log file in the whole system. An instance
  * of {@link LogCreator} is used to create the global log file as well as any local
  * log file.</p>
  *
  * <p>Anything loggable must suit the {@link Loggable} interface and must be able to
  * generate an instance of {@link LogEntry}</p>
  *
  * <p>To read in log files see {@link LogInputStream}.</p>
  *
  * @author Steffen Zschaler
  * @version 1.0
  * @since v1.0
  */
public class Log extends Object implements LogContext {

  /**
    * The log files output stream.
    *
    * @see Log#changeOutputStream
    * @see Log#log
    */
  protected ObjectOutputStream ooOutput = null;

  /**
    * Construct a new log file.
    *
    * @param os the outputstream to write to.
    */
  public Log (OutputStream os) {
    super();

    try {
      changeOutputStream (os);
    }
    catch (IOException ioe) {}
  }

  /**
    * Closes this Log file.
    *
    * @exception IOException if an error occurred while closing the underlying stream.
    *
    * @see Log#closeGlobalLog
    *
    * @override Never
    */
  public synchronized void closeLog()
    throws IOException {
    changeOutputStream (null);
  }

  /**
    * Called by the garbage collector on an object when garbage collection
    * determines that there are no more references to the object.
    *
    * <p>Disposes of this log file. If this is the global log file, calls
    * {@link #closeGlobalLog()}, else calls {@link #closeLog()}.</p>
    *
    * @exception IOException if an error occurs while closing the underlying stream.
    *
    * @see Log#closeLog
    * @see Log#closeGlobalLog
    *
    * @override Never
    */
  protected void finalize()
    throws IOException {
    if (this == theGlobalLog)
      closeGlobalLog();
    else
      closeLog();
  }

  /**
    * Change this log's outputstream.
    *
    * <p>If an outputstream exists it is closed prior to setting the new outputstream.</p>
    *
    * @param os the new output stream.
    *
    * @exception IOException if an error occured while closing the old stream.
    *
    * @see Log#setGlobalOutputStream
    *
    * @override Never
    */
  public synchronized void changeOutputStream (OutputStream os)
    throws IOException {
    if (ooOutput != null) {
      logCloseLog();

      ooOutput.close();
    }

    if (os != null) {
      os.write (0); // provide for appended logs
      ooOutput = new ObjectOutputStream (os);
    }
    else {
      ooOutput = null;
    }

    if (ooOutput != null) {
      logOpenLog();
    }
  }

  /**
    * Add a log entry when closing the log file.
    *
    * <p>Currently does nothing. You can override this to write a log entry when
    * the log file is being closed.</p>
    *
    * @see Log#closeLog
    *
    * @override Sometimes Override this method if you want to add a log entry when the log file is closed.
    */
  protected void logCloseLog() {}

  /**
    * Add a log entry when opening the log file.
    *
    * <p>Currently does nothing. You can override this to write a log entry when
    * the log file is being opened.</p>
    *
    * @see Log#changeOutputStream
    *
    * @override Sometimes Override this method if you want to add a log entry when the log file is opened.
    */
  protected void logOpenLog() {}

  /**
    * Adds one entry to the log file. Calls l.getLogData().
    *
    * @param l the event to be logged.
    * @see Loggable
    *
    * @exception LogNoOutputStreamException if no OutputStream has been
    * specified.
    *
    * @exception IOException if an IOException occurs when writing to the
    * stream.
    *
    * @override Never
    */
  public synchronized void log (Loggable l)
    throws LogNoOutputStreamException,
           IOException {
    if (ooOutput == null)
      throw new LogNoOutputStreamException("on Log.log ( "+l+" )");

    LogEntry le = l.getLogData();

    ooOutput.writeObject (le);
  }

  ////////////////////////////////////////////////////////////////////////////////////////////////////////////
  // STATIC PART
  ////////////////////////////////////////////////////////////////////////////////////////////////////////////

  /**
    * The global log file to create a <i>Singleton</i>.
    */
  private static Log theGlobalLog = null;

  /**
    * The global Log creator.
    */
  private static LogCreator theLogCreator = null;
  static {
    theLogCreator = new LogCreator() {
      public Log createLog (OutputStream os) {
        return new Log (os);
      }
    };
  }

  /**
    * Reference to the global output stream.
    *
    * <p><STRONG>Read Only</STRONG></p>
    *
    * @see Log#setGlobalOutputStream
    */
  protected static OutputStream theGlobalOutputStream = null;

  /**
    * Returns the current global log file.
    *
    * <p>If no log file exists, creates it using the Outputstream as specified
    * by {@link #setGlobalOutputStream}</p>
    *
    * @see #setGlobalOutputStream
    * @see #closeGlobalLog
    *
    * @exception LogNoOutputStreamException if <code>setGlobalOutputStream()</code>
    * has not been called yet.
    */
  public synchronized static Log getGlobalLog() throws LogNoOutputStreamException {
    if (theGlobalOutputStream == null)
      throw new LogNoOutputStreamException ("On Log.getGlobalLog()");

    if (theGlobalLog == null)
      theGlobalLog = createLog (theGlobalOutputStream);

    return theGlobalLog;
  }

  /**
    * Create a new Log file using the current Log creator.
    *
    * <p>You should prefer calling this method to directly creating a new Log
    * file as this method will provide an easy interface for adapting to new
    * log classes.</p>
    *
    * @param os the OutputStream to be used.
    */
  public static Log createLog (OutputStream os) {
    return theLogCreator.createLog (os);
  }

  /**
    * Change the Log creator.
    *
    * <p>Call to provide support for descended Log classes.</p>
    *
    * @param lc the log creator to be used when creating log files.
    *
    * @see Log#getGlobalLog
    */
  public static void setLogCreator (LogCreator lc) {
    theLogCreator = lc;
  }

  /**
    * Closes the global log file if any log file was open.
    *
    * <p>If no log file exists no exception is thrown.
    * Closes the log file <B>and</B> it's OutputStream.</p>
    *
    * @exception IOException if an error occurs while closing the underlying stream.
    *
    * @see Log#getGlobalLog
    * @see Log#setGlobalOutputStream
    */
  public synchronized static void closeGlobalLog()
    throws IOException {
    if (theGlobalLog != null) {
      theGlobalLog.closeLog();

      theGlobalLog = null;
      theGlobalOutputStream = null;
    }
  }

  /**
    * Changes the current OutputStream of the global log file.
    *
    * <p>This method <strong>must</strong> be called at least once before any global log
    * operation takes place.</p>
    *
    * <p>If an OutputStream exists it will be closed automatically. To close
    * the entire global log file use {@link #closeGlobalLog()}.</p>
    *
    * @param newOutputStream the new global output stream
    *
    * @exception IOException if an error occurs while closing the original stream.
    *
    * @see #getGlobalLog
    * @see #closeGlobalLog
    * @see #changeOutputStream
    */
  public synchronized static void setGlobalOutputStream (OutputStream newOutputStream)
    throws IOException {
    OutputStream os = theGlobalOutputStream ;

    theGlobalOutputStream = newOutputStream;

    if (theGlobalLog != null) {
      theGlobalLog.changeOutputStream (newOutputStream);
    }
    else {
      if (os != null) {
        os.close();
      }
    }
  }
}