/*
 * Copyright (C) The Apache Software Foundation. All rights reserved.
 *
 * This software is published under the terms of the Apache Software
 * License version 1.1, a copy of which has been included with this
 * distribution in the LICENSE.APL file.  */

package org.apache.log4j;

import java.text.SimpleDateFormat;
import java.io.IOException;
import java.io.File;
import java.util.Calendar;

import org.apache.log4j.helpers.LogLog;
import org.apache.log4j.spi.LoggingEvent;

/**
 * Appender that rolls over automatically, always writing to a log file 
 * dated with today's date before the extension. Unlike 
 * DailyRollingFileAppender, this class does not rely on the application
 * being running at rollover time, as the "current" filename is always
 * unambiguous.
 * 
 * @author Jon Skeet <skeet@pobox.com>
 */
public class DatedFileAppender extends FileAppender
{
    /** Formatter to use for dates in filename; generated from datePattern */
    private SimpleDateFormat dateFormat;
    
    /** Date pattern to use for formatting dates, as used in SimpleDateFormat */
    private String datePattern;
    
    /** 
     * Default date pattern - allows for easy sorting and is less likely to cause
     * confusion than ddMMyyyy or MMddyyyy
     */
    public static final String DEFAULT_DATE_PATTERN = "yyyyMMdd";
    
    /** When to roll the log */
    private long nextMidnight=0;

	/** Base of filename (ie everything before the extension) */
	protected String filenameBody=null;
	
    /** File extension to add after date (includes .) */
    protected String extension=null;

    /**
     * The only supported constructor - any calls to other superconstructors 
     * could try to open the file before we necessarily have the right
     * date pattern.
     */
    public DatedFileAppender ()
    {
        super();
        setDatePattern (DEFAULT_DATE_PATTERN);
    }
    
    /**
     * Overrides FileAppender.setFile(). FileAppender only
     * ever knows about the dated filename.
     */
    public void setFile (String filename, boolean append)
        throws IOException
    {
        String val = filename.trim();
        String datedFilename = constructInternalFilename (val);
        super.setFile (datedFilename, append);
    }

    /**
     The <b>DatePattern</b> takes a string in the same format as
     expected by {@link SimpleDateFormat}. This options determines the
     rollover schedule.
     */
    public void setDatePattern(String pattern) 
    {
        datePattern = pattern;
        dateFormat = new SimpleDateFormat ("yyyyMMdd");
    }
  
    /** Returns the value of the <b>DatePattern</b> option. */
    public String getDatePattern() 
    {
        return datePattern;
    }

    /**
     * Constructs the internal filename from a "normal" one. A filename of
     * <code>body.ext</code> is translated to <code>body-date.ext</code>. 
     * If there is no
     */
    private synchronized String constructInternalFilename (String filename)
        throws IOException
    {
        File f = new File (filename);
        
        // Separate the name from the parent directory structure.
        // This is needed in case someone specifies a file like:
        // /var/log/foo.bar/asd in which case we want to create
        // /var/log/foo.bar/asd-xxxxxx.log, not
        // /var/log/foo-xxxxxx.bar/asd
        String parent = f.getParent();
        String name = f.getName();
        
        int extIndex = name.lastIndexOf (".");
        // Just make the code simple...
        if (extIndex==-1)
        {
            extIndex=name.length();
            name=name+".log";
        }
        extension = name.substring (extIndex);
        if (extension.equals ("."))
            extension=".log";
        name = name.substring (0, extIndex);
        if (parent==null)
            this.filenameBody = new File (name).getCanonicalPath();
        else
            this.filenameBody = new File (parent, name).getCanonicalPath();

        // Now get the date bit
        return constructInternalFilename();
    }
    
    /**
     * Constructs the internal filename, using the current date.
     * This also works out the next midnight.
     */
    private synchronized String constructInternalFilename()
    {
        // Work out the current midnight
        Calendar midnight = Calendar.getInstance();
        midnight.set(Calendar.HOUR_OF_DAY, 0);
        midnight.set(Calendar.MINUTE, 0);
        
        String dateString = dateFormat.format (midnight.getTime());
        
        // Work out the next midnight
        midnight.add (Calendar.HOUR, 24);
        nextMidnight = midnight.getTime().getTime();
        
        return filenameBody+"-"+dateString+extension;
    }

    /**
     * Overrides FileAppender.subAppend() to check for rollover.
     */
    protected synchronized void subAppend (LoggingEvent event)
    {
        long now = System.currentTimeMillis();
        if (now > nextMidnight)
        {
            try
            {
                rollover();
            }
            catch (IOException e)
            {
                LogLog.error("rollover() failed.", e);
            }
        }
        super.subAppend (event);
    }
    
    /**
     * Rolls over, closing the current log (if there is one),
     * working out the new filename, opening the file, and replaying
     * any startup events.
     */
    private synchronized void rollover()
        throws IOException
    {
        String datedFilename = constructInternalFilename();
        super.setFile (datedFilename, fileAppend);
        LogLog.debug ("Rolled log to "+datedFilename);
    }
}
