package com.srbenoit.log;

import java.io.File;
import java.io.IOException;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.ResourceBundle;
import java.util.logging.FileHandler;
import java.util.logging.Handler;
import java.util.logging.LogRecord;
import java.util.logging.Logger;

/**
 * A custom log entry handler that writes log records to a series of files and also has the ability
 * to retain log entries for a period of time, and provide access to the retained entries.
 */
public class LogHandler extends Handler {

    /** the resource bundle containing localized strings */
    private final transient ResourceBundle res;

    /** a file handler used to write log files */
    private transient FileHandler fileHandler = null;

    /** a list of retained log records */
    private final transient List<LogRecord> records;

    /** the maximum number of (the most recent) log records to retain */
    private transient int maxRecsToRetain = 0;

    /**
     * Constructs a new <code>BekenHandler</code>.
     *
     * @param  resources  the resource bundle containing localized strings
     */
    public LogHandler(final ResourceBundle resources) {

        super();

        this.res = resources;
        this.records = new ArrayList<LogRecord>(20);
    }

    /**
     * Configures the handler based on logging properties. This may add a file handler if so
     * configured and the logger has no file handler already.
     *
     * @param  props  the properties from which to get log configuration
     * @param  log    a log to which to write diagnostic messages
     */
    public void configure(final LogProperties props, final Logger log) {

        String path;
        int limit;
        int count;
        boolean append;
        File file;
        String msg;

        if ((this.fileHandler == null) && (props != null)) {
            setLevel(props.getLogLevel());

            path = props.getLogFilePath();
            count = props.getLogFileCount();

            if ((path != null) && (count > 0)) {
                limit = props.getLogFileSizeLimit();
                append = props.isLogFileAppend();

                file = new File(path);
                file.mkdirs();

                try {
                    this.fileHandler = new FileHandler(path, limit, count, append);
                    this.fileHandler.setFormatter(new LogFormatter());
                } catch (IOException e) {
                    msg = this.res.getString(LogRes.CANT_OPEN_LOG);
                    log.warning(MessageFormat.format(msg, path, e.getLocalizedMessage()));
                } catch (IllegalArgumentException e) {
                    msg = this.res.getString(LogRes.BAD_PARAMS);
                    log.warning(MessageFormat.format(msg, e.getLocalizedMessage()));
                }

                msg = this.res.getString(LogRes.LOGGING_STARTED);
                log.info(MessageFormat.format(msg, props.getLogFilePath()));
            }
        }
    }

    /**
     * Determines whether the handler retains log entries or not, and if so, how many log entries
     * are retained.
     *
     * @param  maxToRetain  the maximum number of log entries to retain, or 0 to disable log entry
     *                      retention (the most recent log entries are retained and when this
     *                      retention level is reached, each new log message will cause the oldest
     *                      retained log message to be discarded)
     */
    public void setRetainLogEntries(final int maxToRetain) {

        this.maxRecsToRetain = maxToRetain;

        if (maxToRetain == 0) {
            this.records.clear();
        } else {

            while (this.records.size() > maxToRetain) {
                this.records.remove(maxToRetain);
            }
        }
    }

    /**
     * Determines the number of retained log entries currently stored in the handler.
     *
     * @return  the number of log entries retained
     */
    public int size() {

        return this.records.size();
    }

    /**
     * Clears all stored log entries, if any.
     */
    public void clear() {

        this.records.clear();
    }

    /**
     * Gets a particular log record from the list of retained records.
     *
     * @param   index  the index of the record to retrieve
     * @return  the log record, or null if the index did not map to a valid log record
     */
    public LogRecord get(final int index) {

        LogRecord rec;

        if ((index >= 0) && (index < this.records.size())) {
            rec = this.records.get(index);
        } else {
            rec = null;
        }

        return rec;
    }

    /**
     * Gets all retained log records.
     *
     * @return  the log records list, or null if records are not being retained
     */
    public LogRecord[] getAll() {

        LogRecord[] array;

        array = new LogRecord[this.records.size()];
        this.records.toArray(array);

        return array;
    }

    /**
     * Closes the handler, releasing all stored resources.
     */
    @Override public void close() {

        if (this.fileHandler != null) {
            this.fileHandler.close();
        }

        this.records.clear();
    }

    /**
     * Flushes any buffered output. Since this class does not output its log entries, this method
     * does nothing.
     */
    @Override public void flush() {

        if (this.fileHandler != null) {
            this.fileHandler.flush();
        }
    }

    /**
     * Publishes a log record. This causes the record to be appended to the list of stored records
     * if this handler is currently configured to retain log entries. Otherwise, the record is
     * ignored.
     *
     * @param  record  the log record to publish
     */
    @Override public void publish(final LogRecord record) {

        if ((this.maxRecsToRetain > 0) && (record != null)) {
            this.records.add(record);

            while (this.records.size() > this.maxRecsToRetain) {
                this.records.remove(this.maxRecsToRetain);
            }
        }

        if (this.fileHandler != null) {
            this.fileHandler.publish(record);
        }
    }
}
