package org.jboss.logging.jdk.handlers;

import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.util.logging.ErrorManager;
import java.util.logging.Formatter;
import java.util.logging.LogRecord;

/**
 * A base handler that outputs log messages to a Writer
 *
 * @author Scott.Stark@jboss.org
 * @version $Revision: 2786 $
 */
public class WriterHandler extends HandlerSkeleton
{
   /**
    * Immediate flush means that the underlying writer or output stream
    * will be flushed at the end of each append operation. Immediate
    * flush is slower but ensures that each append request is actually
    * written. If <code>immediateFlush</code> is set to
    * <code>false</code>, then there is a good chance that the last few
    * logs events are not actually written to persistent media if and
    * when the application crashes.
    * <p/>
    * <p>The <code>immediateFlush</code> variable is set to
    * <code>true</code> by default.
    */
   protected boolean immediateFlush = true;
   /**
    * Do we do bufferedIO?
    */
   protected boolean bufferedIO = false;
   /**
    * Determines the size of IO buffer be. Default is 8K.
    */
   protected int bufferSize = 8 * 1024;

   private OutputStream msgOutput;
   private Writer msgWriter;
   /**
    * Has the
    */
   private boolean wroteHeader;

   public WriterHandler()
   {
      super();
   }

   public WriterHandler(OutputStream output, Formatter formatter)
   {
      setFormatter(formatter);
      setOutputStream(output);
   }

   /**
    * If the <b>ImmediateFlush</b> option is set to
    * <code>true</code>, the appender will flush at the end of each
    * write. This is the default behavior. If the option is set to
    * <code>false</code>, then the underlying stream can defer writing
    * to physical medium to a later time.
    * <p/>
    * <p>Avoiding the flush operation at the end of each append results in
    * a performance gain of 10 to 20 percent. However, there is safety
    * tradeoff involved in skipping flushing. Indeed, when flushing is
    * skipped, then it is likely that the last few log events will not
    * be recorded on disk when the application exits. This is a high
    * price to pay even for a 20% performance gain.
    * @param value 
    */
   public void setImmediateFlush(boolean value)
   {
      immediateFlush = value;
   }

   /**
    * @return value of the <b>ImmediateFlush</b> option.
    */
   public boolean getImmediateFlush()
   {
      return immediateFlush;
   }

   public boolean isBufferedIO()
   {
      return bufferedIO;
   }

   /**
    * The <b>BufferedIO</b> option takes a boolean value. It is set to
    * <code>false</code> by default. If true, then <code>File</code>
    * will be opened and the resulting {@link java.io.Writer} wrapped
    * around a {@link java.io.BufferedWriter}.
    * <p/>
    * BufferedIO will significatnly increase performance on heavily
    * loaded systems.
    * @param bufferedIO 
    */
   public void setBufferedIO(boolean bufferedIO)
   {
      this.bufferedIO = bufferedIO;
      if (bufferedIO)
      {
         immediateFlush = false;
      }
   }

   public int getBufferSize()
   {
      return bufferSize;
   }

   /**
    * Set the size of the IO buffer.
    * @param bufferSize 
    */
   public void setBufferSize(int bufferSize)
   {
      this.bufferSize = bufferSize;
   }

   public void setEncoding(String encoding)
      throws SecurityException, UnsupportedEncodingException
   {
      super.setEncoding(encoding);
      if (msgOutput == null)
      {
         return;
      }
      // Replace the current writer with a writer for the new encoding.
      flush();
      if (encoding == null)
      {
         msgWriter = new OutputStreamWriter(msgOutput);
      }
      else
      {
         msgWriter = new OutputStreamWriter(msgOutput, encoding);
      }
   }

   public synchronized void flush()
   {
      if (msgWriter != null)
      {
         try
         {
            msgWriter.flush();
         }
         catch (IOException e)
         {
            reportError("Failed to flush writer", e, ErrorManager.FLUSH_FAILURE);
         }
      }
   }

   public synchronized void close()
   {
      if (msgWriter != null)
      {
         try
         {
            if (!wroteHeader)
            {
               msgWriter.write(getFormatter().getHead(this));
               wroteHeader = true;
            }
            msgWriter.write(getFormatter().getTail(this));
            msgWriter.flush();
            msgWriter.close();
         }
         catch (Exception ex)
         {
            // We don't want to throw an exception here, but we
            // report the exception to any registered ErrorManager.
            reportError(null, ex, ErrorManager.CLOSE_FAILURE);
         }
         msgWriter = null;
         msgOutput = null;
      }

   }

   public void publish(LogRecord record)
   {
      if(checkEntryConditions(record) == false)
      {
         return;
      }
      subPublish(record);
   }

   protected boolean checkEntryConditions(LogRecord record)
   {
      boolean canWrite = super.isLoggable(record);
      if( canWrite )
      {
         canWrite = msgWriter != null;
      }
      return canWrite;
   }

   /**
    * Actual writing occurs here.
    * Most subclasses of WriterHandler will need to
    * override this method.
    * @param record 
    */
   protected void subPublish(LogRecord record)
   {
      Formatter fmt = getFormatter();
      String msg = fmt.format(record);
      synchronized (this)
      {
         try
         {
            msgWriter.write(msg);
         }
         catch (IOException e)
         {
            reportError("Failed to publish recored", e, ErrorManager.WRITE_FAILURE);
         }
         if (this.immediateFlush)
         {
            flush();
         }
      }
   }

   /**
    * Change the output stream.
    * <p/>
    * If there is a current output stream then the <tt>Formatter</tt>'s
    * tail string is written and the stream is flushed and closed.
    * Then the output stream is replaced with the new output stream.
    *
    * @param out New output stream.  May not be null.
    * @throws SecurityException if a security manager exists and if
    *                           the caller does not have <tt>LoggingPermission("control")</tt>.
    */
   protected synchronized void setOutputStream(OutputStream out)
   {
      if (out == null)
      {
         throw new NullPointerException("The out argument cannot be null");
      }
      close();
      msgOutput = out;
      wroteHeader = false;
      String encoding = getEncoding();
      if (encoding == null)
      {
         msgWriter = new OutputStreamWriter(msgOutput);
      }
      else
      {
         try
         {
            msgWriter = new OutputStreamWriter(msgOutput, encoding);
         }
         catch (UnsupportedEncodingException ex)
         {
            // This shouldn't happen.  The setEncoding method
            // should have validated that the encoding is OK.
            throw new Error("Unexpected exception " + ex);
         }
      }
      if (bufferedIO)
      {
         msgWriter = new BufferedWriter(msgWriter, bufferSize);
      }
   }


}
