
package org.jgroups;


import org.jgroups.util.Streamable;
import org.jgroups.util.Util;

import java.io.*;
import java.util.*;

/**
 * A view is a local representation of the current membership of a group. Only one view is installed
 * in a channel at a time. Views contain the address of its creator, an ID and a list of member
 * addresses. These addresses are ordered, and the first address is always the coordinator of the
 * view. This way, each member of the group knows who the new coordinator will be if the current one
 * crashes or leaves the group. The views are sent between members using the VIEW_CHANGE event
 * 
 * @since 2.0
 * @author Bela Ban
 */
public class View implements Comparable<View>, Streamable, Iterable<Address> {

   /**
    * A view is uniquely identified by its ViewID. The view id contains the creator address and a
    * Lamport time. The Lamport time is the highest timestamp seen or sent from a view. if a view
    * change comes in with a lower Lamport time, the event is discarded.
    */
    protected ViewId vid;

   /**
    * A list containing all the members of the view.This list is always ordered, with the
    * coordinator being the first member. the second member will be the new coordinator if the
    * current one disappears or leaves the group.
    */
    protected List<Address> members;



    /**
     * Creates an empty view, should not be used, only used by (de-)serialization
     */
    public View() {
    }


    /**
     * Creates a new view
     *
     * @param vid     The view id of this view (can not be null)
     * @param members Contains a list of all the members in the view, can be empty but not null.
     */
    public View(ViewId vid, List<Address> members) {
        this.vid=vid;
        this.members=new ArrayList<Address>(members);
    }

    /**
     * Creates a new view
     *
     * @param creator The creator of this view (can not be null)
     * @param id      The lamport timestamp of this view
     * @param members Contains a list of all the members in the view, can be empty but not null.
     */
    public View(Address creator, long id, List<Address> members) {
        this(new ViewId(creator, id), members);
    }


    /**
     * Returns the view ID of this view
     * if this view was created with the empty constructur, null will be returned
     *
     * @return the view ID of this view
     */
    public ViewId getVid()    {return vid;}
    public ViewId getViewId() {return vid;}

    /**
     * Returns the creator of this view
     * if this view was created with the empty constructur, null will be returned
     *
     * @return the creator of this view in form of an Address object
     */
    public Address getCreator() {
        return vid.getCreator();
    }

    /**
     * Returns a reference to the List of members (ordered)
     * Do NOT change this list, hence your will invalidate the view
     * Make a copy if you have to modify it.
     *
     * @return a reference to the ordered list of members in this view
     */
    public List<Address> getMembers() {
        return Collections.unmodifiableList(members);
    }

    /**
     * Returns true, if this view contains a certain member
     *
     * @param mbr - the address of the member,
     * @return true if this view contains the member, false if it doesn't
     *         if the argument mbr is null, this operation returns false
     */
    public boolean containsMember(Address mbr) {
        return mbr != null && members.contains(mbr);
    }


    public int compareTo(View o) {
        return vid.compareTo(o.vid);
    }

    public boolean equals(Object obj) {
        return obj instanceof View && (this == obj || compareTo((View)obj) == 0);
    }


    public int hashCode() {
        return vid.hashCode();
    }

    /**
     * Returns the number of members in this view
     *
     * @return the number of members in this view 0..n
     */
    public int size() {
        return members.size();
    }


    public View copy() {
        return new View(vid.copy(), members);
    }



    public String toString() {
        StringBuilder sb=new StringBuilder(64);
        sb.append(vid).append(" ");
        if(members != null)
            sb.append("[").append(Util.printListWithDelimiter(members, ", ", Util.MAX_LIST_PRINT_SIZE)).append("]");
        return sb.toString();
    }



    public void writeTo(DataOutput out) throws Exception {
        vid.writeTo(out);
        Util.writeAddresses(members, out);
    }

    @SuppressWarnings("unchecked") 
    public void readFrom(DataInput in) throws Exception {
        vid=new ViewId();
        vid.readFrom(in);
        members=(List<Address>)Util.readAddresses(in, ArrayList.class);
    }

    public int serializedSize() {
        return (int)(vid.serializedSize() + Util.size(members));
    }


    public Iterator<Address> iterator() {
        return members.iterator();
    }
}
