package org.jgroups.protocols.pbcast;

import org.jgroups.Address;
import org.jgroups.View;
import org.jgroups.ViewId;
import org.jgroups.util.ArrayIterator;
import org.jgroups.util.Util;

import java.io.DataInput;
import java.io.DataOutput;
import java.util.Arrays;
import java.util.Iterator;

/**
 * Subclass of {@link org.jgroups.View} with a null members field. Adds an array for left members and one for joined
 * members compared to the previous view. A recipient receiving a DeltaView can construct a new view by grabbing the
 * view corresponding to {@link #view_id}, removing the left members and adding the new members.<p/>
 * This class is only used with VIEW messages in GMS to install new views (not merge views). When a VIEW
 * message is received, the DeltaView is read from the {@link org.jgroups.protocols.pbcast.GMS.GmsHeader}, a View is
 * constructured and the header discarded. Therefore, the lifetime of a DeltaView is short: it is created and set in
 * a GmsHeader, the header is then marshalled. On the receiving side, the DeltaView is created from the stream, a View
 * is created and the DeltaView discarded again.<p/>
 * Instances of this class are created by {@link CoordGmsImpl#handleMembershipChange(java.util.Collection)}.<p/>
 * JIRA issue: https://issues.jboss.org/browse/JGRP-1354
 * @author Bela Ban
 * @since  3.4
 */
public class DeltaView extends View {
    /** The fields left_members and new_members refer to the view corresponding to ref_view_id */
    protected ViewId    ref_view_id;

    /** Members which left the view corresponding to ref_view_id */
    protected Address[] left_members;

    /** Members which joined the view corresponding to ref_view_id */
    protected Address[] new_members;

    public DeltaView() {

    }


    public DeltaView(ViewId view_id, ViewId ref_view_id, Address[] left_members, Address[] new_members) {
        this.view_id=view_id;
        this.ref_view_id=ref_view_id;
        this.left_members=left_members;
        this.new_members=new_members;
        if(view_id == null)      throw new IllegalArgumentException("view_id cannot be null");
        if(ref_view_id == null) throw new IllegalArgumentException("ref_view_id cannot be null");
    }

    public ViewId    getRefViewId()     {return ref_view_id;}
    public Address[] getLeftMembers()   {return left_members;}
    public Address[] getNewMembers()    {return new_members;}


    public int serializedSize() {
        int retval=view_id.serializedSize() + ref_view_id.serializedSize();
        retval+=Util.size(left_members);
        retval+=Util.size(new_members);
        return retval;
    }


    public void writeTo(DataOutput out) throws Exception {
        view_id.writeTo(out);
        ref_view_id.writeTo(out);
        Util.writeAddresses(left_members, out);
        Util.writeAddresses(new_members, out);
    }

    public void readFrom(DataInput in) throws Exception {
        view_id=new ViewId();
        view_id.readFrom(in);
        ref_view_id=new ViewId();
        ref_view_id.readFrom(in);
        left_members=Util.readAddresses(in);
        new_members=Util.readAddresses(in);
    }

    public Iterator<Address> iterator() {
        Address[] combined=new Address[left_members.length + new_members.length];
        int left_len=left_members.length;
        System.arraycopy(left_members, 0, combined, 0, left_len);
        System.arraycopy(new_members, 0, combined, left_len, new_members.length);
        return new ArrayIterator<>(combined);
    }

    public String toString() {
        StringBuilder sb=new StringBuilder(super.toString()).append(", ref-view=").append(ref_view_id);
        if(left_members != null && left_members.length > 0)
            sb.append(", left=").append(Arrays.toString(left_members));
        if(new_members != null && new_members.length > 0)
            sb.append(", joined=").append(Arrays.toString(new_members));
        return sb.toString();
    }
}
