package org.jgroups.util;

import org.jgroups.Global;
import org.jgroups.Header;
import org.jgroups.conf.ClassConfigurator;

import java.util.HashMap;
import java.util.Map;

/**
 * Helper class providing functions to manipulate the {@link org.jgroups.Message#headers} array. The headers are stored
 * in the array as follows:
 * <pre>
 * Headers:  hdr-1 | hdr-2 | hdr-3 | ... | hdr-n |
 * </pre>
 *
 * The arrays are populated from left to right, and any empty slot in 'headers' can terminate an interation
 * (e.g. a getHeader())
 * <br/>
 * It is assumed that we only have a few headers, 3-4 on average. Note that getting a header for a given key and
 * putting a new key/header are operations with O(n) cost, so this implementation is <em>not</em> recommended for
 * a large number of elements.
 * <br/>
 * This class is synchronized for writes (put(), resize()), but not for reads (size(), get())
 * @author Bela Ban
 */
public final class Headers {
    private static final int RESIZE_INCR=3;

	private Headers() {
		throw new InstantiationError( "Must not instantiate this class" );
	}

    /**
     * Returns the header associated with an ID
     * @param id The ID
     * @return
     */
    public static <T extends Header> T getHeader(final Header[] hdrs, short id) {
        if(hdrs == null)
            return null;
        for(Header hdr: hdrs) {
            if(hdr == null)
                return null;
            if(hdr.getProtId() == id)
                return (T)hdr;
        }
        return null;
    }


    public static Map<Short,Header> getHeaders(final Header[] hdrs) {
        if(hdrs == null)
            return new HashMap<>();
        Map<Short,Header> retval=new HashMap<>(hdrs.length);
        for(Header hdr: hdrs) {
            if(hdr == null)
                break;
            retval.put(hdr.getProtId(), hdr);
        }
        return retval;
    }

    public static String printHeaders(final Header[] hdrs) {
        if(hdrs == null)
            return "";
        StringBuilder sb=new StringBuilder();
        boolean first=true;
        for(Header hdr: hdrs) {
            if(hdr == null)
                break;
            short id=hdr.getProtId();
            if(first)
                first=false;
            else
                sb.append(", ");
            Class clazz=ClassConfigurator.getProtocol(id);
            String name=clazz != null? clazz.getSimpleName() : Short.toString(id);
            sb.append(name).append(": ").append(hdr);
        }
        return sb.toString();
    }


    /**
     * Adds hdr at the next available slot. If none is available, the headers array passed in will be copied and the copy
     * returned
     * @param headers The headers array
     * @param id The protocol ID of the header
     * @param hdr The header
     * @param replace_if_present Whether or not to overwrite an existing header
     * @return A new copy of headers if the array needed to be expanded, or null otherwise
     */
    public static Header[] putHeader(final Header[] headers, short id, Header hdr, boolean replace_if_present) {
        int i=0;
        Header[] hdrs=headers;
        boolean resized=false;
        while(i < hdrs.length) {
            if(hdrs[i] == null) {
                hdrs[i]=hdr;
                return resized? hdrs: null;
            }
            short hdr_id=hdrs[i].getProtId();
            if(hdr_id == id) {
                if(replace_if_present || hdrs[i] == null)
                    hdrs[i]=hdr;
                return resized? hdrs : null;
            }
            i++;
            if(i >= hdrs.length) {
                hdrs=resize(hdrs);
                resized=true;
            }
        }
        throw new IllegalStateException("unable to add element " + id + ", index=" + i); // we should never come here
    }

    /**
     * Increases the capacity of the array and copies the contents of the old into the new array
     */
    public static Header[] resize(final Header[] headers) {
        int new_capacity=headers.length + RESIZE_INCR;
        Header[] new_hdrs=new Header[new_capacity];
        System.arraycopy(headers, 0, new_hdrs, 0, headers.length);
        return new_hdrs;
    }

     public static Header[] copy(final Header[] headers) {
         if(headers == null)
             return new Header[0];
         Header[] retval=new Header[headers.length];
         System.arraycopy(headers, 0, retval, 0, headers.length);
         return retval;
     }

    public static String printObjectHeaders(final Header[] hdrs) {
        if(hdrs == null)
            return "";
        StringBuilder sb=new StringBuilder();
        for(Header hdr: hdrs) {
            if(hdr == null)
                break;
            sb.append(hdr.getProtId()).append(": ").append(hdr).append('\n');
        }
        return sb.toString();
    }

    public static int marshalledSize(final Header[] hdrs) {
        int retval=0;
        if(hdrs == null)
            return retval;
        for(Header hdr: hdrs) {
            if(hdr == null)
                break;
            retval+=Global.SHORT_SIZE *2;    // for protocol ID and magic number
            retval+=hdr.serializedSize();
        }
        return retval;
    }

    public static int size(Header[] hdrs) {
        int retval=0;
        if(hdrs == null)
            return retval;
        for(Header hdr: hdrs) {
            if(hdr == null)
                break;
            retval++;
        }
        return retval;
    }

    public static int size(Header[] hdrs, short... excluded_ids) {
        int retval=0;
        if(hdrs == null)
            return retval;
        for(Header hdr: hdrs) {
            if(hdr == null)
                break;
            if(!Util.containsId(hdr.getProtId(), excluded_ids))
                retval++;
        }
        return retval;
    }


}
