/**
 * $RCSfile$
 * $Revision$
 * $Date$
 *
 * Copyright (C) 2004 Jive Software. All rights reserved.
 *
 * This software is published under the terms of the GNU Public License (GPL),
 * a copy of which is included in this distribution.
 */

package org.jivesoftware.messenger.muc;

import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.ListIterator;
import java.util.TimeZone;

import org.jivesoftware.messenger.muc.spi.MUCRoleImpl;
import org.jivesoftware.util.Log;
import org.jivesoftware.messenger.Message;
import org.jivesoftware.messenger.MetaDataFragment;
import org.jivesoftware.messenger.auth.UnauthorizedException;

import org.jivesoftware.messenger.muc.MUCRoomHistory;

/**
 * Represents the amount of history requested by an occupant while joining a room. There are 
 * basically four ways to control the amount of history that a user may receive. Those are: limit
 * by the maximum limit of characters to receive, limit by a maximum number of stanzas to receive,
 * limit to receive only the messages before a given date or of the last X seconds.<p>
 * 
 * A user may combine any of these four methods. The idea is that the user will receive the smallest 
 * amount of traffic so the amount of history to collect will stop as soon as any of the requested 
 * method has reached its limit.
 * 
 * @author Gaston Dombiak
 */
public class HistoryRequest {

    private static final DateFormat formatter = new SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'");
    private static final DateFormat delayedFormatter = new SimpleDateFormat("yyyyMMdd'T'HH:mm:ss");
    static {
        delayedFormatter.setTimeZone(TimeZone.getTimeZone("GMT+0"));
    }

    private int maxChars = -1;
    private int maxStanzas = -1;
    private int seconds = -1;
    private Date since;

    public HistoryRequest(MetaDataFragment userFragment) {
        if (userFragment.includesProperty("x.history")) {
            if (userFragment.includesProperty("x.history:maxchars")) {
                this.maxChars = Integer.parseInt(userFragment.getProperty("x.history:maxchars"));
            }
            if (userFragment.includesProperty("x.history:maxstanzas")) {
                this.maxStanzas = Integer.parseInt(userFragment.getProperty("x.history:maxstanzas"));
            }
            if (userFragment.includesProperty("x.history:seconds")) {
                this.seconds = Integer.parseInt(userFragment.getProperty("x.history:seconds"));
            }
            if (userFragment.includesProperty("x.history:since")) {
                try {
                    // parse utc into Date
                    this.since = formatter.parse(userFragment.getProperty("x.history:since"));
                }
                catch(ParseException pe) {
                    Log.error("Error parsing date from history management", pe);
                    this.since = null;
                }
            }
        }
    }
    
    /**
     * Returns the total number of characters to receive in the history.
     * 
     * @return total number of characters to receive in the history.
     */
    public int getMaxChars() {
        return maxChars;
    }

    /**
     * Returns the total number of messages to receive in the history.
     * 
     * @return the total number of messages to receive in the history.
     */
    public int getMaxStanzas() {
        return maxStanzas;
    }

    /**
     * Returns the number of seconds to use to filter the messages received during that time. 
     * In other words, only the messages received in the last "X" seconds will be included in 
     * the history.
     * 
     * @return the number of seconds to use to filter the messages received during that time.
     */
    public int getSeconds() {
        return seconds;
    }

    /**
     * Returns the since date to use to filter the messages received during that time. 
     * In other words, only the messages received since the datetime specified will be 
     * included in the history.
     * 
     * @return the since date to use to filter the messages received during that time.
     */
    public Date getSince() {
        return since;
    }

    /**
     * Sets the total number of characters to receive in the history.
     * 
     * @param maxChars the total number of characters to receive in the history.
     */
    public void setMaxChars(int maxChars) {
        this.maxChars = maxChars;
    }

    /**
     * Sets the total number of messages to receive in the history.
     * 
     * @param maxStanzas the total number of messages to receive in the history.
     */
    public void setMaxStanzas(int maxStanzas) {
        this.maxStanzas = maxStanzas;
    }

    /**
     * Sets the number of seconds to use to filter the messages received during that time. 
     * In other words, only the messages received in the last "X" seconds will be included in 
     * the history.
     * 
     * @param seconds the number of seconds to use to filter the messages received during 
     * that time.
     */
    public void setSeconds(int seconds) {
        this.seconds = seconds;
    }

    /**
     * Sets the since date to use to filter the messages received during that time. 
     * In other words, only the messages received since the datetime specified will be 
     * included in the history.
     * 
     * @param since the since date to use to filter the messages received during that time.
     */
    public void setSince(Date since) {
        this.since = since;
    }

    /**
     * Returns true if the history has been configured with some values.
     * 
     * @return true if the history has been configured with some values.
     */
    private boolean isConfigured() {
        return maxChars > -1 || maxStanzas > -1 || seconds > -1 || since != null;
    }

    /**
     * Sends the smallest amount of traffic that meets any combination of the requested criteria.
     * 
     * @param joinRole the user that will receive the history.
     * @param roomHistory the history of the room.
     */
    public void sendHistory(MUCRoleImpl joinRole, MUCRoomHistory roomHistory) {
        try {
            if (!isConfigured()) {
                Iterator history = roomHistory.getMessageHistory();
                while (history.hasNext()) {
                    joinRole.send((Message) history.next());
                }
            }
            else {
                if (getMaxChars() == 0) {
                    // The user requested to receive no history
                    return;
                }
                Message message;
                int accumulatedChars = 0;
                int accumulatedStanzas = 0;
                MetaDataFragment delayInformation;
                LinkedList historyToSend = new LinkedList();
                ListIterator iterator = roomHistory.getReverseMessageHistory();
                while (iterator.hasPrevious()) {
                    message = (Message)iterator.previous();
                    // Update number of characters to send
                    accumulatedChars += message.getBody().length();
                    if (getMaxChars() > -1 && accumulatedChars > getMaxChars()) {
                        // Stop collecting history since we have exceded a limit
                        break;
                    }
                    // Update number of messages to send
                    accumulatedStanzas ++;
                    if (getMaxStanzas() > -1 && accumulatedStanzas > getMaxStanzas()) {
                        // Stop collecting history since we have exceded a limit
                        break;
                    }
                    
                    if (getSeconds() > -1 || getSince() != null) {
                        delayInformation = (MetaDataFragment) message.getFragment(
                                "x",
                                "jabber:x:delay");
                        try {
                            // Get the date when the historic message was sent
                            Date delayedDate = delayedFormatter.parse(delayInformation
                                    .getProperty("x:stamp"));
                            if (getSince() != null && delayedDate.before(getSince())) {
                                // Stop collecting history since we have exceded a limit
                                break;
                            }
                            if (getSeconds() > -1) {
                                Date current = new Date();
                                long diff = (current.getTime() - delayedDate.getTime()) / 1000;
                                if (getSeconds() <= diff) {
                                    // Stop collecting history since we have exceded a limit
                                    break;
                                }
                            }
                        }
                        catch (ParseException e) {
                            Log.error("Error parsing date from historic message", e);
                        }

                    }
                    
                    historyToSend.addFirst(message);
                }
                // Send the smallest amount of traffic to the user
                Iterator history = historyToSend.iterator();
                while (history.hasNext()) {
                    joinRole.send((Message) history.next());
                }
            }
        }
        catch (UnauthorizedException e) {
            // Do nothing
        }
    }
}
