// Path: src/main/java/org/kontalk/xmppserver/messages/OfflineMessages.java
// protected static final String ID = "msgoffline2";
// 
// Path: src/main/java/org/kontalk/xmppserver/messages/OfflineMessages.java
// protected static final String XMLNS = "jabber:client";


import tigase.db.NonAuthUserRepository;
import tigase.db.TigaseDBException;
import tigase.db.UserNotFoundException;
import tigase.server.Packet;
import tigase.util.DNSResolver;
import tigase.util.TigaseStringprepException;
import tigase.xml.Element;
import tigase.xmpp.*;
import tigase.xmpp.impl.C2SDeliveryErrorProcessor;
import tigase.xmpp.impl.FlexibleOfflineMessageRetrieval;
import tigase.xmpp.impl.Message;
import tigase.xmpp.impl.PresenceState;
import tigase.xmpp.impl.annotation.*;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
import static org.kontalk.xmppserver.messages.OfflineMessages.ID;
import static org.kontalk.xmppserver.messages.OfflineMessages.XMLNS;
/*
 * Kontalk XMPP Tigase extension
 * Copyright (C) 2017 Kontalk Devteam <devteam@kontalk.org>

 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.

 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.

 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.kontalk.xmppserver.messages;





/**
 * A more advanced offline messages implementation.
 * It uses an ad-hoc table and it is capable of expiring messages.
 * Inspired by the original OfflineMessages by Tigase.
 * @author Daniele Ricci
 */
@Id(ID)
@Handles({
    @Handle(path={PresenceState.PRESENCE_ELEMENT_NAME},xmlns=XMLNS)
})
@DiscoFeatures({
    "msgoffline"
})
public class OfflineMessages extends AnnotatedXMPPProcessor
        implements XMPPPostprocessorIfc, XMPPProcessorIfc {

    protected static final String XMLNS = "jabber:client";
    /**
     * This can be considered a second version of the original <em>msgoffline</em>.
     */
    protected static final String ID = "msgoffline2";

    /** Field holds the default hostname of the machine. */
    private static final String defHost = DNSResolver.getDefaultHostname();

    private static final Logger log = Logger.getLogger(OfflineMessages.class.getName());

    private static final int DEF_EXPIRE_SECONDS = 604800;

    private int messageExpire;
    private int presenceExpire;

    private Timer taskTimer;

    private MsgRepository msgRepo;
    private Message message = new Message();
    private final DateFormat formatter;

    {
        this.formatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
        this.formatter.setTimeZone(TimeZone.getTimeZone("UTC"));
    }

    public OfflineMessages() {
        msgRepo = new JDBCMsgRepository();
    }

    // for test only
    OfflineMessages(MsgRepository msgRepo) {
        this.msgRepo = msgRepo;
    }

    @Override
    public int concurrentQueuesNo() {
        return Runtime.getRuntime().availableProcessors();
    }

    @Override
    public void init(Map<String, Object> settings) throws TigaseDBException {
        super.init(settings);
        String uri = (String) settings.get("db-uri");
        msgRepo.initRepository(uri, null);

        try {
            messageExpire = (int) settings.get("message-expire");
        }
        catch (Exception e) {
            messageExpire = DEF_EXPIRE_SECONDS;
        }
        try {
            presenceExpire = (int) settings.get("presence-expire");
        }
        catch (Exception e) {
            presenceExpire = DEF_EXPIRE_SECONDS;
        }

        long hour = TimeUnit.HOURS.toMillis(1);
        taskTimer = new Timer(ID + " tasks", true);
        taskTimer.scheduleAtFixedRate(new TimerTask() {
            @Override
            public void run() {
                try {
                    if (log.isLoggable(Level.FINEST)) {
                        log.finest("Purging expired messages.");
                    }
                    msgRepo.expireMessages();
                }
                catch (TigaseDBException e) {
                    log.log(Level.WARNING, "error purging expired messages", e);
                }
            }
        }, hour, hour);

    }

    /**
     * Returns expiration time for the given packet.
     * @return expiration UTC time, or null for no expiration
     */
    private Date getExpiration(Packet packet) {
        int seconds;
        if (packet.getElemName() == tigase.server.Presence.ELEM_NAME) {
            seconds = presenceExpire;
        }
        else {
            seconds = messageExpire;
        }

        Calendar cal = Calendar.getInstance();
        cal.add(Calendar.SECOND, seconds);
        return cal.getTime();
    }

    @Override
    public void process(Packet packet, XMPPResourceConnection session, NonAuthUserRepository repo,
                        Queue<Packet> results, Map<String, Object> settings) throws XMPPException {
        if ( loadOfflineMessages( packet, session ) ){
            try {
// Path: src/main/java/org/kontalk/xmppserver/messages/OfflineMessages.java
// protected static final String ID = "msgoffline2";
// 
// Path: src/main/java/org/kontalk/xmppserver/messages/OfflineMessages.java
// protected static final String XMLNS = "jabber:client";


// Path: src/main/java/org/kontalk/xmppserver/messages/OfflineMessages.java
import tigase.db.NonAuthUserRepository;
import tigase.db.TigaseDBException;
import tigase.db.UserNotFoundException;
import tigase.server.Packet;
import tigase.util.DNSResolver;
import tigase.util.TigaseStringprepException;
import tigase.xml.Element;
import tigase.xmpp.*;
import tigase.xmpp.impl.C2SDeliveryErrorProcessor;
import tigase.xmpp.impl.FlexibleOfflineMessageRetrieval;
import tigase.xmpp.impl.Message;
import tigase.xmpp.impl.PresenceState;
import tigase.xmpp.impl.annotation.*;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
import static org.kontalk.xmppserver.messages.OfflineMessages.ID;
import static org.kontalk.xmppserver.messages.OfflineMessages.XMLNS;

/*
 * Kontalk XMPP Tigase extension
 * Copyright (C) 2017 Kontalk Devteam <devteam@kontalk.org>

 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.

 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.

 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.kontalk.xmppserver.messages;





/**
 * A more advanced offline messages implementation.
 * It uses an ad-hoc table and it is capable of expiring messages.
 * Inspired by the original OfflineMessages by Tigase.
 * @author Daniele Ricci
 */
@Id(ID)
@Handles({
    @Handle(path={PresenceState.PRESENCE_ELEMENT_NAME},xmlns=XMLNS)
})
@DiscoFeatures({
    "msgoffline"
})
public class OfflineMessages extends AnnotatedXMPPProcessor
        implements XMPPPostprocessorIfc, XMPPProcessorIfc {

    protected static final String XMLNS = "jabber:client";
    /**
     * This can be considered a second version of the original <em>msgoffline</em>.
     */
    protected static final String ID = "msgoffline2";

    /** Field holds the default hostname of the machine. */
    private static final String defHost = DNSResolver.getDefaultHostname();

    private static final Logger log = Logger.getLogger(OfflineMessages.class.getName());

    private static final int DEF_EXPIRE_SECONDS = 604800;

    private int messageExpire;
    private int presenceExpire;

    private Timer taskTimer;

    private MsgRepository msgRepo;
    private Message message = new Message();
    private final DateFormat formatter;

    {
        this.formatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
        this.formatter.setTimeZone(TimeZone.getTimeZone("UTC"));
    }

    public OfflineMessages() {
        msgRepo = new JDBCMsgRepository();
    }

    // for test only
    OfflineMessages(MsgRepository msgRepo) {
        this.msgRepo = msgRepo;
    }

    @Override
    public int concurrentQueuesNo() {
        return Runtime.getRuntime().availableProcessors();
    }

    @Override
    public void init(Map<String, Object> settings) throws TigaseDBException {
        super.init(settings);
        String uri = (String) settings.get("db-uri");
        msgRepo.initRepository(uri, null);

        try {
            messageExpire = (int) settings.get("message-expire");
        }
        catch (Exception e) {
            messageExpire = DEF_EXPIRE_SECONDS;
        }
        try {
            presenceExpire = (int) settings.get("presence-expire");
        }
        catch (Exception e) {
            presenceExpire = DEF_EXPIRE_SECONDS;
        }

        long hour = TimeUnit.HOURS.toMillis(1);
        taskTimer = new Timer(ID + " tasks", true);
        taskTimer.scheduleAtFixedRate(new TimerTask() {
            @Override
            public void run() {
                try {
                    if (log.isLoggable(Level.FINEST)) {
                        log.finest("Purging expired messages.");
                    }
                    msgRepo.expireMessages();
                }
                catch (TigaseDBException e) {
                    log.log(Level.WARNING, "error purging expired messages", e);
                }
            }
        }, hour, hour);

    }

    /**
     * Returns expiration time for the given packet.
     * @return expiration UTC time, or null for no expiration
     */
    private Date getExpiration(Packet packet) {
        int seconds;
        if (packet.getElemName() == tigase.server.Presence.ELEM_NAME) {
            seconds = presenceExpire;
        }
        else {
            seconds = messageExpire;
        }

        Calendar cal = Calendar.getInstance();
        cal.add(Calendar.SECOND, seconds);
        return cal.getTime();
    }

    @Override
    public void process(Packet packet, XMPPResourceConnection session, NonAuthUserRepository repo,
                        Queue<Packet> results, Map<String, Object> settings) throws XMPPException {
        if ( loadOfflineMessages( packet, session ) ){
            try {
                Queue<Packet> packets = restorePacketForOffLineUser(session, msgRepo);