package com.limegroup.gnutella.messages.vendor;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Iterator;
import java.util.List;

import com.limegroup.gnutella.ErrorService;
import com.limegroup.gnutella.ManagedConnection;
import com.limegroup.gnutella.RouterService;
import com.limegroup.gnutella.messages.BadPacketException;
import com.limegroup.gnutella.statistics.BandwidthStat;

public class StatisticVendorMessage extends VendorMessage {
    
    public static final int VERSION = 1;

    private static final String DELIMITER = " | ";
    
    private static final String DELIMITER2 = " ^ ";
    

    /**
     * Constructor for a StatisticVendorMessage read off the network, meaing it
     * was received in respose to a GiveStatsVendorMessage sent by this node.
     */
    public StatisticVendorMessage(byte[] guid, byte ttl, byte hops, 
                                   int version, byte[] payload) 
                                                     throws BadPacketException {
        super(guid, ttl, hops, F_LIME_VENDOR_ID, F_STATISTICS, version,
              payload);
    }

    /**
     * Constructor to make a StatisticVendorMessage in response to a
     * GiveStatisticsVendorMessage. This is an outgoing StatisticVendorMessage
     */
    public StatisticVendorMessage(GiveStatsVendorMessage giveStatVM) {
        super(F_LIME_VENDOR_ID, F_STATISTICS, VERSION, 
                                                  derivePayload(giveStatVM));
    }
    
    /**
     * Determines whether or not we know how to respond to the GiveStats msg.
     */
    public static boolean isSupported(GiveStatsVendorMessage vm) {
        switch(vm.getStatType()) {
        case GiveStatsVendorMessage.GNUTELLA_INCOMING_TRAFFIC:
        case GiveStatsVendorMessage.GNUTELLA_OUTGOING_TRAFFIC:
            switch(vm.getStatControl()) {
            case GiveStatsVendorMessage.PER_CONNECTION_STATS:
            case GiveStatsVendorMessage.ALL_CONNECTIONS_STATS:
            case GiveStatsVendorMessage.UP_CONNECTIONS_STATS:
            case GiveStatsVendorMessage.LEAF_CONNECTIONS_STATS:
                return true;
            default:
                return false;
            }
        case GiveStatsVendorMessage.HTTP_DOWNLOAD_TRAFFIC_STATS:
        case GiveStatsVendorMessage.HTTP_UPLOAD_TRAFFIC_STATS:            
            return true;
        default:
            return false;
        }
    }
    
    private static byte[] derivePayload(GiveStatsVendorMessage giveStatsVM) {
        byte control = giveStatsVM.getStatControl();
        byte type = giveStatsVM.getStatType();
        byte[] part1 = {control, type};
        //write the type of stats we are writing out
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        try {
            baos.write(part1);
        } catch(IOException iox) {
            ErrorService.error(iox); // impossible.
        }
        byte[] part2;

        StringBuffer buff;
        switch(type) {
        case GiveStatsVendorMessage.GNUTELLA_INCOMING_TRAFFIC:
        case GiveStatsVendorMessage.GNUTELLA_OUTGOING_TRAFFIC:
            boolean incoming = 
            type==GiveStatsVendorMessage.GNUTELLA_INCOMING_TRAFFIC;
            part2 = getGnutellaStats(control,incoming);
            break;
        case GiveStatsVendorMessage.HTTP_DOWNLOAD_TRAFFIC_STATS:
            //NOTE: in this case we ignore the granularity control, since
            //HTTP traffic is not connection specific
            buff = new StringBuffer();
            buff.append(
                     BandwidthStat.HTTP_HEADER_DOWNSTREAM_BANDWIDTH.getTotal());
            buff.append(DELIMITER2);
            buff.append(
                      BandwidthStat.HTTP_BODY_DOWNSTREAM_BANDWIDTH.getTotal());
            
            part2 = buff.toString().getBytes();
            break;
        case GiveStatsVendorMessage.HTTP_UPLOAD_TRAFFIC_STATS:
            //NOTE: in this case we ignore the granularity control, since
            //HTTP traffic is not connection specific
            buff = new StringBuffer();
            buff.append(
                       BandwidthStat.HTTP_HEADER_UPSTREAM_BANDWIDTH.getTotal());
            buff.append(DELIMITER2);
            buff.append(BandwidthStat.HTTP_BODY_UPSTREAM_BANDWIDTH.getTotal());
            part2 = buff.toString().getBytes();
            break;
        default:
            throw new IllegalArgumentException("unknown type: " + type);
        }
        
        
        try {
            baos.write(part2);
        } catch (IOException iox) {
            ErrorService.error(iox); // impossible.
        }        
        return baos.toByteArray();
    }
 

    private static byte[] getGnutellaStats(byte control, boolean incoming) {
        List conns = RouterService.getConnectionManager().getConnections();
        StringBuffer buff = new StringBuffer();

        switch(control) {
        case GiveStatsVendorMessage.PER_CONNECTION_STATS:
            for(Iterator iter = conns.iterator(); iter.hasNext() ; ) {
                ManagedConnection c = (ManagedConnection)iter.next();
                buff.append(c.toString());
                buff.append(DELIMITER2);
                if(incoming) {
                    buff.append(c.getNumMessagesReceived());
                    buff.append(DELIMITER);
                    buff.append(c.getNumReceivedMessagesDropped());
                }
                else {
                    buff.append(c.getNumMessagesSent());
                    buff.append(DELIMITER);
                    buff.append(c.getNumSentMessagesDropped());
                }
                buff.append(DELIMITER2);
            }
            return buff.toString().getBytes();
        case GiveStatsVendorMessage.ALL_CONNECTIONS_STATS:
            int messages = -1;
            int dropped = -1;
            for(Iterator iter = conns.iterator(); iter.hasNext() ; ) {
                ManagedConnection c = (ManagedConnection)iter.next();
                messages += incoming ? c.getNumMessagesReceived() : 
                                               c.getNumMessagesSent();
                dropped += incoming ? c.getNumReceivedMessagesDropped() :
                                               c.getNumSentMessagesDropped();
            }
            buff.append(messages);
            buff.append(DELIMITER);
            buff.append(dropped);
            return buff.toString().getBytes();
        case GiveStatsVendorMessage.UP_CONNECTIONS_STATS:
            for(Iterator iter = conns.iterator(); iter.hasNext() ; ) {
                ManagedConnection c = (ManagedConnection)iter.next();
                if(!c.isSupernodeConnection())
                    continue;
                buff.append(c.toString());
                buff.append(DELIMITER2);
                if(incoming) {
                    buff.append(c.getNumMessagesReceived());
                    buff.append(DELIMITER);
                    buff.append(c.getNumReceivedMessagesDropped());
                }
                else {
                    buff.append(c.getNumMessagesSent());
                    buff.append(DELIMITER);
                    buff.append(c.getNumSentMessagesDropped());
                }
                buff.append(DELIMITER2);
            }
            return buff.toString().getBytes();
        case GiveStatsVendorMessage.LEAF_CONNECTIONS_STATS:
            for(Iterator iter = conns.iterator(); iter.hasNext() ; ) {
                ManagedConnection c = (ManagedConnection)iter.next();
                if(!c.isLeafConnection())
                    continue;
                buff.append(c.toString());
                buff.append(DELIMITER2);
                if(incoming) {
                    buff.append(c.getNumMessagesReceived());
                    buff.append(DELIMITER);
                    buff.append(c.getNumReceivedMessagesDropped());
                }
                else {
                    buff.append(c.getNumMessagesSent());
                    buff.append(DELIMITER);
                    buff.append(c.getNumSentMessagesDropped());
                }
                buff.append(DELIMITER2);
            }
            return buff.toString().getBytes();
        default:
            throw new IllegalArgumentException("unknown control: " + control);
        }
    }
 
    public String getReportedStats() {
        return new String(getPayload());
    }
   
}
