//   Copyright 2012 Georg-August-Universität Göttingen, Germany
//
//   Licensed 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 de.ugoe.cs.autoquest.httpmonitor;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.text.DecimalFormat;
import java.util.logging.Level;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;

import de.ugoe.cs.autoquest.plugin.http.logdata.HttpExchange;
import de.ugoe.cs.autoquest.plugin.http.logdata.ObjectFactory;
import de.ugoe.cs.util.console.Console;

/**
 * <p>
 * dumps messages to a log file in the provided base log directory. The name of each finished log
 * file starts with the "httpmonitor_" followed by an index of the log file. An unfinished log file
 * has no index yet. A log file is finished if
 * <ul>
 *   <li>the session is closed by a timeout</li>
 *   <li>the HTTP monitor or proxy are normally shut down</li>
 *   <li>on startup an unfinished log file is detected.</li>
 *   <li>the {@link #MAXIMUM_LOG_FILE_SIZE} is reached</li>
 * </ul>
 * </p>
 * 
 * @author Patrick Harms
 * @version 1.0
 * 
 */
public class HttpMonitorOutputWriter implements HttpMonitorComponent, HttpMonitorExchangeHandler {
    
    /**
     * the maximum size of an individual log file
     */
    private static final int MAXIMUM_LOG_FILE_SIZE = 50000000;

    /**
     * the default log base directory if none is provided through the constructor 
     */
    private static final String DEFAULT_LOG_FILE_BASE_DIR = "logs";

    /**
     * the currently used log file base directory
     */
    private File logFileBaseDir;

    /**
     * the log file into which all messages are currently written
     */
    private File logFile;

    /**
     * an output writer to be used for writing into the log file
     */
    private PrintWriter outputWriter;

    /**
     * the time stamp of the last action taken on this writer (such as logging a message)
     */
    private long lastUpdate;
    
    /**
     * <p>
     * initializes the writer with the log file base directory.
     * </p>
     * 
     * @param logFileBaseDir the log file base directory, or null if the default directory shall
     *                       be taken
     */
    public HttpMonitorOutputWriter(String logFileBaseDir) {
        if (logFileBaseDir == null) {
            this.logFileBaseDir = new File(DEFAULT_LOG_FILE_BASE_DIR);
        }
        else {
            this.logFileBaseDir = new File(logFileBaseDir);
        }
        
        lastUpdate = System.currentTimeMillis();
    }

    /* (non-Javadoc)
     * @see de.ugoe.cs.autoquest.htmlmonitor.HttpMonitorComponent#init()
     */
    @Override
    public synchronized void init() throws HttpMonitorException {
        if (outputWriter != null) {
            throw new IllegalStateException("already initialized. Call close() first");
        }
        
        synchronized (HttpMonitorOutputWriter.class) {
            try {
                if (!logFileBaseDir.exists()) {
                    if (!logFileBaseDir.mkdirs()) {
                        throw new HttpMonitorException("log file directory " + logFileBaseDir +
                                                       " can not be created");
                    }
                }
                else if (!logFileBaseDir.isDirectory()) {
                    throw new HttpMonitorException("log file directory " + logFileBaseDir +
                                                   " already exists as a file");
                }
                
                logFile = new File(logFileBaseDir, getLogFileName(-1));
                
                if (logFile.exists()) {
                    rotateLogFile();
                }
            
                createLogWriter();
            }
            catch (IOException e) {
                throw new HttpMonitorException("could not open logfile " + logFile, e);
            }
        }
        
        lastUpdate = System.currentTimeMillis();
    }

    /**
     * <p>
     * used to calculate a log file name. If the provided index is smaller 0, then no index
     * is added to the file name. A filename is e.g. "httpmonitor_001.log". 
     * </p>
     *
     * @param index the index of the log file or -1 one, if no index shall be added
     * 
     * @return the file name as described
     */
    private String getLogFileName(int index) {
        String result = "httpmonitor";
        
        if (index >= 0) {
            result += "_" + new DecimalFormat("000" ).format(index);
        }
        
        result += ".log";
        
        return result;
    }

    /* (non-Javadoc)
     * @see de.ugoe.cs.autoquest.htmlmonitor.HttpMonitorComponent#start()
     */
    @Override
    public synchronized void start() throws IllegalStateException, HttpMonitorException {
        lastUpdate = System.currentTimeMillis();
    }

    /* (non-Javadoc)
     * @see HttpMonitorMessageListener#handleHttpMessage()
     */
    @Override
    public synchronized void handleHttpExchange(HttpExchange httpExchange) {
        if (outputWriter == null) {
            throw new IllegalStateException("not initialized. Call init() first");
        }
        
        try {
            JAXBContext jaxbContext =
                JAXBContext.newInstance(HttpExchange.class.getPackage().getName());
            Marshaller marshaller = jaxbContext.createMarshaller();

            marshaller.setProperty(Marshaller.JAXB_FRAGMENT, Boolean.TRUE);
            marshaller.marshal(new ObjectFactory().createHttpExchange(httpExchange), outputWriter);
        }
        catch (JAXBException e) {
            Console.traceln(Level.WARNING, "could not dump exchange to log file: " + e);
            Console.logException(e);
        }

        outputWriter.flush();
        
        try {
            considerLogRotate();
        }
        catch (IOException e) {
            throw new IllegalStateException("could not perform log rotation: " + e, e);
        }
        
        lastUpdate = System.currentTimeMillis();
    }

    /**
     * <p>
     * checks, if the log file exceeded the {@link #MAXIMUM_LOG_FILE_SIZE}. If so, the current
     * log file is closed, the next log file name is determined and this new file is opened for
     * writing. 
     * </p>
     */
    private synchronized void considerLogRotate() throws IOException {
        if (logFile.length() > MAXIMUM_LOG_FILE_SIZE) {
            closeLogWriter();
            rotateLogFile();
            createLogWriter();
        }
    }

    /**
     * <p>
     * renames the current log file to a new log file with the next available index. It further
     * sets the current log file to the default name, i.e. without index.
     * </p>
     */
    private void rotateLogFile() {
        File clientLogDir = logFile.getParentFile();
        File checkFile;

        int logFileIndex = -1;
        do {
            logFileIndex++;
            
            checkFile = new File(clientLogDir, getLogFileName(logFileIndex));
        }
        while (checkFile.exists());
    
        if (!logFile.renameTo(checkFile)) {
            Console.printerrln("could not rename log file " + logFile + " to " + checkFile +
                               ". Will not perform log rotation.");
        }
        else {
            logFileIndex++;
            logFile = new File(clientLogDir, getLogFileName(-1));
        }
    }

    /**
     * <p>
     * instantiates a writer to be used for writing messages into the log file.
     * </p>
     */
    private void createLogWriter() throws IOException {
        FileOutputStream fis = new FileOutputStream(logFile);
        outputWriter = new PrintWriter(new OutputStreamWriter(fis, "UTF-8"));
        outputWriter.println("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
        outputWriter.println
            ("<session xmlns=\"http://autoquest.informatik.uni-goettingen.de/httpexchange\" >");
    }

    /**
     * <p>
     * closed the current writer if it is open.
     * </p>
     */
    private void closeLogWriter() {
        if (outputWriter != null) {
            outputWriter.println("</session>");
            outputWriter.flush();
            outputWriter.close();
            outputWriter = null;
        }
    }

    /* (non-Javadoc)
     * @see de.ugoe.cs.autoquest.htmlmonitor.HttpMonitorComponent#stop()
     */
    @Override
    public synchronized void stop() {
        closeLogWriter();
        rotateLogFile();

        lastUpdate = System.currentTimeMillis();
    }

    /**
     * <p>
     * return the time stamp of the last activity that happened on this writer.
     * </p>
     *
     * @return as described
     */
    public long getLastUpdate() {
        return lastUpdate;
    }
}
