package com.limegroup.gnutella.messages;

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

import org.limewire.collection.IntervalSet;
import org.limewire.io.BadGGEPPropertyException;
import org.limewire.io.GGEP;
import org.limewire.util.ByteUtils;

import com.limegroup.gnutella.settings.SharingSettings;


/**
 * Contains the logic for writing and reading IntervalSets
 * to and from a GGEP field.
 */
public class IntervalEncoder {
    
    public static void encode(long size, GGEP g, IntervalSet s) {
        List<Integer> bytes = new ArrayList<Integer>();
        List<Integer> shorts = new ArrayList<Integer>();
        List<Integer> b24 = new ArrayList<Integer>();
        List<Integer> ints = new ArrayList<Integer>();
        
        for (int i : s.encode(size)) {
            long l = i & 0xFFFFFFFF;
            if (l > 0xFFFFFFL)
                ints.add(i);
            else if (l > 0xFFFF)
                b24.add(i);
            else if (l > 0xFF)
                shorts.add(i);
            else
                bytes.add(i);
        }
        
        byte [] bytesB = new byte[bytes.size()];
        for (int i = 0; i < bytesB.length; i++)
            bytesB[i] = (byte)bytes.get(i).intValue();
        
        byte [] shortsB = new byte[shorts.size() * 2];
        for (int i = 0; i < shorts.size(); i++)
            ByteUtils.short2beb(shorts.get(i).shortValue(), shortsB, i * 2);
        
        byte [] b24B = new byte[b24.size() * 3];
        for (int i = 0; i < b24.size(); i++) {
            int value = b24.get(i);
            b24B[i*3] = (byte)((value & 0xFF0000) >> 16);
            b24B[i*3 + 1] = (byte)((value & 0xFF00) >> 8);
            b24B[i*3 + 2] = (byte)(value & 0xFF);
        }
        
        byte [] intsB = new byte[ints.size() * 4];
        for (int i = 0; i < ints.size(); i++) 
            ByteUtils.int2beb(ints.get(i).intValue(), intsB, i * 4);
        
        int availableSpace = SharingSettings.MAX_PARTIAL_ENCODING_SIZE.getValue();
        availableSpace = addIfSpace(bytesB,g,1,availableSpace);
        availableSpace = addIfSpace(shortsB,g,2,availableSpace);
        availableSpace = addIfSpace(b24B,g,3,availableSpace);
        addIfSpace(intsB,g,4,availableSpace);
        
        // special case - for an empty interval set we add an extention
        if (bytes.size() + shorts.size() + b24.size() + ints.size() == 0)
            g.put(GGEPKeys.GGEP_HEADER_PARTIAL_RESULT_PREFIX+"0");
    }
    
    /**
     * adds the byte array to the appropriate ggep value if there is enough space 
     * @param dataSize the size of each entry
     * @param available how much space we have availble
     * @return how much space is left after adding
     */
    private static int addIfSpace(byte [] toAdd, GGEP ggep, int dataSize, int available) {
        if (toAdd.length == 0 || available <= 0)
            return available;
        assert toAdd.length % dataSize == 0;
        if (toAdd.length > available) {
            byte [] tmp = new byte[available - (available % dataSize)];
            if (tmp.length == 0)
                return available;
            System.arraycopy(toAdd,0,tmp,0,tmp.length);
            toAdd = tmp;
        }
        ggep.put(GGEPKeys.GGEP_HEADER_PARTIAL_RESULT_PREFIX+dataSize,toAdd);
        return available - toAdd.length;
    }
    
    /**
     * @return an IntervalSet contained in this GGEP.  Null if none.
     */
    public static IntervalSet decode(long size, GGEP ggep) throws BadGGEPPropertyException{
        if (ggep.hasKey(GGEPKeys.GGEP_HEADER_PARTIAL_RESULT_PREFIX+"0"))
            return new IntervalSet();
        IntervalSet ret = null;
        for (int i = 1; i <= 4; i++ ) {
            String key = GGEPKeys.GGEP_HEADER_PARTIAL_RESULT_PREFIX+i;
            if (ggep.hasKey(key)) {
                byte [] b = ggep.get(key);
                if (b == null)
                    continue;
                
                if (ret == null)
                    ret = new IntervalSet();

                // is data valid?
                if (b.length % i != 0)
                    return null;

                for (int j = 0; j < b.length; j+=i) {
                    int nodeId = 0;
                    for (int k = 0; k < i; k++) {
                        nodeId <<= 8;
                        nodeId |= (b[j + k] & 0xFF);
                    }
                    
                    ret.decode(size, nodeId);
                }
            }
        }
        return ret;
    }
}
