package org.jgroups.util;

import org.jgroups.Constructable;
import org.jgroups.Global;

import java.io.DataInput;
import java.io.DataOutput;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.function.Supplier;

/**
 * A bitset of missing messages with a fixed size. The index (in the bit set) of a seqno is computed as seqno - offset.
 * @author Bela Ban
 * @since  3.1
 */
public class SeqnoList extends FixedSizeBitSet implements SizeStreamable, Iterable<Long>, Constructable<SeqnoList> {
    protected long offset; // first seqno

    /** Only to be used by serialization */
    public SeqnoList() {
    }

    /**
     * Creates a SeqnoList with a capacity for size elements.
     * @param size The max number of seqnos in the bitset
     * @param offset Lowest seqno. Used to compute the index of a given seqno into the bitset: seqno - offset
     */
    public SeqnoList(int size, long offset) {
        super(size);
        this.offset=offset;
    }

    public Supplier<? extends SeqnoList> create() {
        return SeqnoList::new;
    }

    public SeqnoList(int size) {
        this(size, 0);
    }

    /** Adds a single seqno */
    public SeqnoList add(long seqno) {
        super.set(index(seqno));
        return this;
    }

    public SeqnoList add(long ... seqnos) {
        if(seqnos != null)
            for(long seqno: seqnos)
                add(seqno);
        return this;
    }

    /** Adds a seqno range */
    public SeqnoList add(long from, long to) {
        super.set(index(from), index(to));
        return this;
    }


    /** Removes all seqnos > seqno */
    public void removeHigherThan(long max_seqno) {
        int from=index(max_seqno + 1), to=size-1;
        if(from <= to && from >= 0)
            super.clear(from, to);
    }



    /** Returns the last seqno, this should also be the highest seqno in the list as we're supposed to add seqnos
     * in order
     * @return
     */
    public long getLast() {
        int index=previousSetBit(size - 1);
        return index == -1? -1 : seqno(index);
    }

    public int serializedSize() {
        return Global.INT_SIZE // number of words
          + (words.length+1) * Global.LONG_SIZE; // words + offset
    }

    public void writeTo(DataOutput out) throws Exception {
        out.writeInt(size);
        out.writeLong(offset);
        for(long word: words)
            out.writeLong(word);
    }

    public void readFrom(DataInput in) throws Exception {
        size=in.readInt();
        offset=in.readLong();
        words=new long[wordIndex(size - 1) + 1];
        for(int i=0; i < words.length; i++)
            words[i]=in.readLong();
    }



    public int size() {
        return super.cardinality();
    }

    public boolean isEmpty() {return cardinality() == 0;}


    public String toString() {
        if(isEmpty())
            return "{}";
        StringBuilder sb=new StringBuilder("(").append(cardinality()).append("): {");

        boolean first=true;
        int num=Util.MAX_LIST_PRINT_SIZE;
        for(int i=nextSetBit(0); i >= 0; i=nextSetBit(i + 1)) {
            int endOfRun=nextClearBit(i);
            if(first)
                first=false;
            else
                sb.append(", ");

            if(endOfRun != -1 && endOfRun-1 != i) {
                sb.append(seqno(i)).append('-').append(seqno(endOfRun-1));
                i=endOfRun;
            }
            else
                sb.append(seqno(i));
            if(--num <= 0) {
                sb.append(", ... ");
                break;
            }
        }

        sb.append('}');
        return sb.toString();
    }

    public Iterator<Long> iterator() {
        return new SeqnoListIterator();
    }

    protected int index(long seqno) {return (int)(seqno-offset);}

    protected long seqno(int index) {return offset + index;}


    protected class SeqnoListIterator implements Iterator<Long> {
        protected int         index;

        public boolean hasNext() {
            return index < size && nextSetBit(index) != -1;
        }

        public Long next() {
            int next_index=nextSetBit(index);
            if(next_index == -1 || next_index >= size)
                throw new NoSuchElementException("index: " + next_index);
            index=next_index+1;
            return seqno(next_index);
        }

        public void remove() { // not supported
        }
    }
}
