//   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.htmlmonitor;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;

import de.ugoe.cs.util.console.Console;

/**
 * <p>
 * The log manager handles different {@link HtmlMonitorOutputWriter}s to perform the logging
 * of recorded messages. It initializes a new writer if the first message for a specific
 * client is received and it closes a writer if for a specific period of time no further message
 * of the same client was received. The writers themselves consider log rotation. For handling
 * messages, the {@link HtmlMonitorMessageListener} mechanism provided by the
 * {@link HtmlMonitorServer} is used.
 * </p>
 * 
 * @author Patrick Harms
 */
class HtmlMonitorLogManager implements HtmlMonitorComponent, HtmlMonitorMessageListener {
    
    /**
     * the timeout after which a writer of an inactive client is closed
     */
    private static final int SESSION_TIMEOUT = 10 * 60 * 1000;
    
    /**
     * the directory into which the log files shall be written
     */
    private String logFileBaseDir;

    /**
     * the mapping of client ids to the respective writers.
     */
    private Map<String, HtmlMonitorOutputWriter> writers;

    /**
     * a timer used to detect client timouts
     */
    private Timer logFileMonitorTimer;

    /**
     * <p>
     * initializes the log manager with the directory into which the log files shall be stored
     * </p>
     *
     * @param logFileBaseDir the directory into which the log files shall be stored
     */
    HtmlMonitorLogManager(String logFileBaseDir) {
        this.logFileBaseDir = logFileBaseDir;
    }

    /* (non-Javadoc)
     * @see de.ugoe.cs.autoquest.htmlmonitor.HtmlMonitorComponent#init()
     */
    @Override
    public synchronized void init() throws IllegalStateException, HtmlMonitorException {
        if (writers != null) {
            throw new IllegalStateException("already initialized");
        }
        
        writers = new HashMap<String, HtmlMonitorOutputWriter>();
        logFileMonitorTimer = new Timer();
    }

    /* (non-Javadoc)
     * @see de.ugoe.cs.autoquest.htmlmonitor.HtmlMonitorComponent#start()
     */
    @Override
    public synchronized void start() throws IllegalStateException, HtmlMonitorException {
        if (writers == null) {
            throw new IllegalStateException("not initialized");
        }
        
        logFileMonitorTimer.schedule
            (new LogFileMonitorTimerTask(), SESSION_TIMEOUT / 2, SESSION_TIMEOUT / 2);
    }

    /* (non-Javadoc)
     * @see de.ugoe.cs.autoquest.htmlmonitor.HtmlMonitorComponent#stop()
     */
    @Override
    public synchronized void stop() {
        if (writers != null) {
            logFileMonitorTimer.cancel();
            
            for (HtmlMonitorOutputWriter writer : writers.values()) {
                writer.stop();
            }
        }
        
        writers = null;
    }

    /* (non-Javadoc)
     * @see HtmlMonitoringListener#handleEvents(HtmlClientInfos, HtmlEvent[])
     */
    @Override
    public void handleMessage(HtmlClientInfos clientInfos,
                              HtmlGUIElement  guiStructure,
                              HtmlEvent[]     events)
    {
        HtmlMonitorOutputWriter writer =
            writers.get(clientInfos.getClientId() + clientInfos.getUrl());
        
        try {
            if (writer == null) {
                synchronized (this) {
                    writer = writers.get(clientInfos.getClientId() + clientInfos.getUrl());
                    if (writer == null) {
                        writer = new HtmlMonitorOutputWriter
                            (logFileBaseDir, clientInfos.getWebAppId(),
                             clientInfos.getClientId(), clientInfos.getUserAgent());

                        writer.init();
                        writer.start();
                        writers.put(clientInfos.getClientId() + clientInfos.getUrl(), writer);
                    }
                }
            }

            writer.handleMessage(clientInfos, guiStructure, events);
        }
        catch (Exception e) {
            Console.printerrln("could not handle message of client " + clientInfos.getClientId() +
                               ": " + e);
            e.printStackTrace();
            Console.logException(e);
            
            // determine, if the writer exists but is not able to log something. In this case,
            // destroy the writer (part of the message may be logged twice through this).
            writer = writers.get(clientInfos.getClientId() + clientInfos.getUrl());
            if (writer != null) {
                try {
                    writer.handleMessage(clientInfos, guiStructure, events);
                }
                catch (Exception e1) {
                    synchronized (this) {
                        writers.remove(clientInfos.getClientId() + clientInfos.getUrl());
                        writer.stop();
                    }
                }
            }
        }
    }

    /**
     * <p>
     * internal timer task used for detecting session timeouts of clients
     * </p>
     * 
     * @author Patrick Harms
     */
    public class LogFileMonitorTimerTask extends TimerTask {

        /* (non-Javadoc)
         * @see java.lang.Runnable#run()
         */
        @Override
        public void run() {
            synchronized (HtmlMonitorLogManager.this) {
                List<String> timeoutSessions = new ArrayList<String>();
                for (Map.Entry<String, HtmlMonitorOutputWriter> entry : writers.entrySet()) {
                    HtmlMonitorOutputWriter writer = entry.getValue();
                    
                    if (System.currentTimeMillis() - writer.getLastUpdate() > SESSION_TIMEOUT) {
                        timeoutSessions.add(entry.getKey());
                    }
                }
                
                for (String sessionId : timeoutSessions) {
                    HtmlMonitorOutputWriter writer = writers.remove(sessionId);
                    writer.stop();
                }
            }
        }

    }

}
