package org.jgroups.util;

import org.jgroups.Address;
import org.jgroups.TimeoutException;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

/**
 * Collects acks from a number of nodes, waits for all acks. Can also be time bounded
 * @author Bela Ban
 */
public class AckCollector {
    /** List of members from whom we haven't received an ACK yet */
    protected final List<Address>     missing_acks;
    protected final Promise<Boolean>  all_acks_received=new Promise<Boolean>();
    protected final List<Address>     suspected_mbrs=new ArrayList<Address>(5);
    protected int                     expected_acks;


    public AckCollector() {
        missing_acks=new ArrayList<Address>();
        expected_acks=0;
    }

    public AckCollector(Collection<Address> members) {
        missing_acks=new ArrayList<Address>(members != null? members.size() : 10);
        addAll(members);
    }

    public AckCollector(Address ... members) {
        missing_acks=new ArrayList<Address>(members != null? members.length : 10);
        addAll(members);
    }


    public synchronized void reset(Collection<Address> members) {
        suspected_mbrs.clear();
        missing_acks.clear();
        addAll(members);
        all_acks_received.reset();
    }

    public synchronized void destroy() {
        suspected_mbrs.clear();
        missing_acks.clear();
        expected_acks=0;
        all_acks_received.setResult(null);
    }

    public synchronized int size() {
        return missing_acks.size();
    }

    public synchronized int expectedAcks() {
        return expected_acks;
    }

    public synchronized void ack(Address member) {
        if(member != null && missing_acks.remove(member) &&  missing_acks.isEmpty())
            all_acks_received.setResult(Boolean.TRUE);
    }

    public synchronized void suspect(Address member) {
        if(member == null) return;
        if(!suspected_mbrs.contains(member))
            suspected_mbrs.add(member);
        ack(member);
    }

    public boolean retainAll(Collection<Address> members) {
        if(members == null) return false;
        boolean retval=false;
        synchronized(this) {
            suspected_mbrs.retainAll(members);
            if((retval=missing_acks.retainAll(members)) && missing_acks.isEmpty())
                all_acks_received.setResult(Boolean.TRUE);
        }
        return retval;
    }

    public boolean waitForAllAcks() {
        if(missing_acks.isEmpty())
            return true;
        Boolean result=all_acks_received.getResult();
        return result != null && result.booleanValue();
    }

    public boolean waitForAllAcks(long timeout) throws TimeoutException {
        if(missing_acks.isEmpty())
            return true;
        Boolean result=all_acks_received.getResultWithTimeout(timeout);
        return result != null && result.booleanValue();
    }

    public String toString() {
        return suspected_mbrs.isEmpty() ? printMissing() : printMissing() + " (suspected: " + printSuspected() + ")";
    }

    public synchronized String printMissing() {
        return missing_acks.toString();
    }

    public synchronized String printSuspected() {
        return suspected_mbrs.toString();
    }

    protected synchronized void addAll(Address ... members) {
        if(members == null)
            return;
        for(Address member: members)
            if(member != null && !missing_acks.contains(member))
                missing_acks.add(member);
        expected_acks=missing_acks.size();
    }

    protected synchronized void addAll(Collection<Address> members) {
        if(members == null)
            return;
        for(Address member: members)
            if(member != null && !missing_acks.contains(member))
                missing_acks.add(member);
        expected_acks=missing_acks.size();
    }
}
