/*
 *  This file is part of the Heritrix web crawler (crawler.archive.org).
 *
 *  Licensed to the Internet Archive (IA) by one or more individual 
 *  contributors. 
 *
 *  The IA licenses this file to You under the Apache License, Version 2.0
 *  (the "License"); you may not use this file except in compliance with
 *  the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package org.archive.io;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
import java.util.logging.FileHandler;
import java.util.logging.Formatter;
import java.util.logging.LogRecord;

import org.archive.util.FileUtils;


/**
 * FileHandler with support for rotating the current file to
 * an archival name with a specified integer suffix, and
 * provision of a new replacement FileHandler with the current
 * filename.
 *
 * @author gojomo
 */
public class GenerationFileHandler extends FileHandler {
    private LinkedList<String> filenameSeries = new LinkedList<String>();
    private boolean shouldManifest = false;

    /**
     * @return Returns the filenameSeries.
     */
    public List<String> getFilenameSeries() {
        return filenameSeries;
    }

    /**
     * Constructor.
     * @param pattern
     * @param append
     * @param shouldManifest
     * @throws IOException
     * @throws SecurityException
     */
    public GenerationFileHandler(String pattern, boolean append,
            boolean shouldManifest)
    throws IOException, SecurityException {
        super(pattern, append);
        filenameSeries.addFirst(pattern);
        this.shouldManifest = shouldManifest;
    }

    /**
     * @param filenameSeries
     * @param shouldManifest
     * @throws IOException
     */
    public GenerationFileHandler(LinkedList<String> filenameSeries,
            boolean shouldManifest)
    throws IOException {
        super((String)filenameSeries.getFirst(), false); // Never append in this case
        this.filenameSeries = filenameSeries;
        this.shouldManifest = shouldManifest;
    }

    /**
     * Move the current file to a new filename with the storeSuffix in place
     * of the activeSuffix; continuing logging to a new file under the
     * original filename.
     *
     * @param storeSuffix Suffix to put in place of <code>activeSuffix</code>
     * @param activeSuffix Suffix to replace with <code>storeSuffix</code>.
     * @return GenerationFileHandler instance.
     * @throws IOException
     */
    public GenerationFileHandler rotate(String storeSuffix,
            String activeSuffix)
    throws IOException {
        return rotate(storeSuffix, activeSuffix, false);
    }
    
    public GenerationFileHandler rotate(String storeSuffix,
            String activeSuffix, boolean mergeOld) throws IOException {
        close();
        String filename = (String) filenameSeries.getFirst();
        if (!filename.endsWith(activeSuffix)) {
            throw new FileNotFoundException("Active file does not have"
                    + " expected suffix");
        }
        String storeFilename = filename.substring(0, filename.length()
                - activeSuffix.length())
                + storeSuffix;
        File activeFile = new File(filename);
        File storeFile = new File(storeFilename);
        FileUtils.moveAsideIfExists(storeFile);

        if (mergeOld) {
            File fileToAppendTo = new File(filenameSeries.getLast());
            for (int i = filenameSeries.size() - 2; i >= 0; i--) {
                File f = new File(filenameSeries.get(i));
                FileUtils.appendTo(fileToAppendTo, f);
                f.delete();
            }
            filenameSeries.clear();
            filenameSeries.add(filename);
            if (!fileToAppendTo.renameTo(storeFile)) {
                throw new IOException("Unable to move " + fileToAppendTo + " to "
                        + storeFilename);
            }
        } else {
            if (!activeFile.renameTo(storeFile)) {
                throw new IOException("Unable to move " + filename + " to "
                        + storeFilename);
            }
        }
        filenameSeries.add(1, storeFilename);
        GenerationFileHandler newGfh = new GenerationFileHandler(
                filenameSeries, shouldManifest);
        newGfh.setFormatter(this.getFormatter());
        return newGfh;
    }

    /**
     * @return True if should manifest.
     */
    public boolean shouldManifest() {
        return this.shouldManifest;
    }

    /**
     * Constructor-helper that rather than clobbering any existing 
     * file, moves it aside with a timestamp suffix. 
     * 
     * @param filename
     * @param append
     * @param shouldManifest
     * @return
     * @throws SecurityException
     * @throws IOException
     */
    public static GenerationFileHandler makeNew(String filename, boolean append, boolean shouldManifest) throws SecurityException, IOException {
        FileUtils.moveAsideIfExists(new File(filename));
        return new GenerationFileHandler(filename, append, shouldManifest);
    }

    @Override
    public void publish(LogRecord record) {
        // when possible preformat outside synchronized superclass method
        // (our most involved UriProcessingFormatter can cache result)
        Formatter f = getFormatter(); 
        if(!(f instanceof Preformatter)) {
            super.publish(record);
        } else {
            try {
                ((Preformatter)f).preformat(record); 
                super.publish(record);
            } finally {
                ((Preformatter)f).clear();
            }
        }
    }
//
//    TODO: determine if there's another way to have this optimization without
//    negative impact on log-following (esp. in web UI)
//    /**
//     * Flush only 1/100th of the usual once-per-record, to reduce the time
//     * spent holding the synchronization lock. (Flush is primarily called in
//     * a superclass's synchronized publish()). 
//     * 
//     * The eventual close calls a direct flush on the target writer, so all 
//     * rotates/ends will ultimately be fully flushed. 
//     * 
//     * @see java.util.logging.StreamHandler#flush()
//     */
//    @Override
//    public synchronized void flush() {
//        flushCount++;
//        if(flushCount==100) {
//            super.flush();
//            flushCount=0; 
//        }
//    }
//    int flushCount;     
    
}