
package org.jgroups.stack;

import org.jgroups.Address;
import org.jgroups.logging.Log;
import org.jgroups.logging.LogFactory;
import org.jgroups.util.TimeScheduler;
import org.jgroups.util.Util;

import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;


/**
 * Abstract class of a retransmitter.
 * Maintains a pool of sequence numbers of messages that need to be retransmitted. Messages
 * are aged and retransmission requests sent according to age (configurable backoff). If a
 * TimeScheduler instance is given to the constructor, it will be used, otherwise Reransmitter
 * will create its own. The retransmit timeouts have to be set first thing after creating an instance.
 * The <code>add()</code> method adds the sequence numbers of messages to be retransmitted. The
 * <code>remove()</code> method removes a sequence number again, cancelling retransmission requests for it.
 * Whenever a message needs to be retransmitted, the <code>RetransmitCommand.retransmit()</code> method is called.
 * It can be used e.g. by an ack-based scheme (e.g. AckSenderWindow) to retransmit a message to the receiver, or
 * by a nak-based scheme to send a retransmission request to the sender of the missing message.<br/>
 * @author Bela Ban
 */
public abstract class Retransmitter {

    /** Default retransmit intervals (ms) - exponential approx. */
    protected Interval                       retransmit_timeouts=new ExponentialInterval(300);
    protected final Address                  sender;
    protected final RetransmitCommand        cmd;
    protected final TimeScheduler            timer;
    protected long                           xmit_stagger_timeout=0;
    protected static final Log log=LogFactory.getLog(Retransmitter.class);


    /** Retransmit command (see Gamma et al.) used to retrieve missing messages */
    public interface RetransmitCommand {
        /**
         * Get the missing messages between sequence numbers <code>first_seqno</code> and <code>last_seqno</code>.
         * This can either be done by sending a retransmit message to destination <code>sender</code>
         * (nak-based scheme), or by retransmitting the missing message(s) to <code>sender</code> (ack-based scheme).
         * @param first_seqno The sequence number of the first missing message
         * @param last_seqno  The sequence number of the last missing message
         * @param sender The destination of the member to which the retransmit request will be sent
         *               (nak-based scheme), or to which the message will be retransmitted (ack-based scheme).
         */
        void retransmit(long first_seqno, long last_seqno, Address sender);
    }


    /**
     * Create a new Retransmitter associated with the given sender address
     * @param sender the address from which retransmissions are expected or to which retransmissions are sent
     * @param cmd the retransmission callback reference
     * @param sched retransmissions scheduler
     */
    public Retransmitter(Address sender, RetransmitCommand cmd, TimeScheduler sched) {
        this.sender=sender;
        this.cmd=cmd;
        timer=sched;
    }



    public void setRetransmitTimeouts(Interval interval) {
        if(interval != null)
            retransmit_timeouts=interval;
    }

    public long getXmitStaggerTimeout() {
        return xmit_stagger_timeout;
    }

    public void setXmitStaggerTimeout(long xmit_stagger_timeout) {
        this.xmit_stagger_timeout=xmit_stagger_timeout;
    }

    /**
     * Add messages from <code>first_seqno</code> to <code>last_seqno</code/> for retransmission
     */
    public abstract void add(long first_seqno, long last_seqno);

    /**
     * Remove the given sequence number from retransmission
     */
    public abstract void remove(long seqno);

    /**
     * Reset the retransmitter: clear all msgs and cancel all the respective tasks
     */
    public abstract void reset();


    public abstract int size();




    /* ------------------------------- Private Methods -------------------------------------- */



    /* ---------------------------- End of Private Methods ------------------------------------ */

    protected abstract class Task implements TimeScheduler.Task {
        protected final Interval       intervals;
        protected volatile Future      future;
        protected Address              msg_sender=null;
        protected RetransmitCommand    command;
        protected volatile boolean     cancelled=false;

        protected Task(Interval intervals, RetransmitCommand cmd, Address msg_sender) {
            this.intervals=intervals;
            this.command=cmd;
            this.msg_sender=msg_sender;
        }

        public long nextInterval() {
            return intervals.next();
        }

        public void doSchedule() {
            if(cancelled) {
                return;
            }
            long delay=intervals.next();

            if(xmit_stagger_timeout > 0) {
                long stagger_time=Util.random(xmit_stagger_timeout);
                delay+=stagger_time;
            }

            future=timer.schedule(this, delay, TimeUnit.MILLISECONDS);
        }

        public void cancel() {
            if(!cancelled) {
                cancelled=true;
            }
            if(future != null)
                future.cancel(true);
        }

        public void run() {
            if(cancelled) {
                return;
            }
            try {
                callRetransmissionCommand();
            }
            catch(Throwable t) {
                if(log.isErrorEnabled())
                    log.error("failed retransmission task", t);
            }
            doSchedule();
        }


        protected abstract void callRetransmissionCommand();
    }


}

