/*
 * Created on 18-Jan-2005
 * Created by Paul Gardner
 * Copyright (C) Azureus Software, Inc, All Rights Reserved.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 */

package com.aelitis.azureus.core.dht.transport.util;

import java.net.InetSocketAddress;
import java.util.Arrays;

import org.gudy.azureus2.core3.util.AddressUtils;
import org.gudy.azureus2.core3.util.SystemTime;

import com.aelitis.azureus.core.dht.impl.DHTLog;
import com.aelitis.azureus.core.dht.transport.DHTTransportStats;
import com.aelitis.azureus.core.dht.transport.udp.impl.DHTUDPPacketHelper;
import com.aelitis.azureus.core.dht.transport.udp.impl.DHTUDPPacketRequest;
import com.aelitis.azureus.core.util.bloom.BloomFilter;
import com.aelitis.azureus.core.util.bloom.BloomFilterFactory;

/**
 * @author parg
 *
 */

public abstract class 
DHTTransportStatsImpl
	implements DHTTransportStats
{
	private byte	protocol_version;
	
	private long[]	pings			= new long[4];
	private long[]	find_nodes		= new long[4];
	private long[]	find_values		= new long[4];
	private long[]	stores			= new long[4];
	private long[]	stats			= new long[4];
	private long[]	data			= new long[4];
	private long[]	key_blocks		= new long[4];
	private long[]	store_queries	= new long[4];
	
	private long[]	aliens		= new long[7];

	private long	incoming_requests;
	private long	outgoing_requests;
	
	private long	incoming_version_requests;
	private long[]	incoming_request_versions;
	private long	outgoing_version_requests;
	private long[]	outgoing_request_versions;
	
	private static final int SKEW_VALUE_MAX	= 256;
	private final int[]	skew_values = new int[SKEW_VALUE_MAX];
	private int			skew_pos	= 0;
	private long		last_skew_average;
	private long		last_skew_average_time;
	
	private BloomFilter	skew_originator_bloom = 
		BloomFilterFactory.createRotating(
				BloomFilterFactory.createAddOnly( SKEW_VALUE_MAX*4 ),
				2 );
	protected
	DHTTransportStatsImpl(
		byte	_protocol_version )
	{
		protocol_version	= _protocol_version;
		
		incoming_request_versions = new long[protocol_version+1];
		outgoing_request_versions = new long[protocol_version+1];
		
		Arrays.fill( skew_values, Integer.MAX_VALUE );
	}
	
	protected byte
	getProtocolVersion()
	{
		return( protocol_version );
	}
	
	public void
	add(
		DHTTransportStatsImpl	other )
	{
		add( pings, other.pings );
		add( find_nodes, other.find_nodes );
		add( find_values, other.find_values );
		add( stores, other.stores );
		add( stats, other.stats );
		add( data, other.data );
		add( key_blocks, other.key_blocks );
		add( store_queries, other.store_queries );
		add( aliens, other.aliens );
		
		incoming_requests += other.incoming_requests;
		outgoing_requests += other.outgoing_requests;
	}
	
	protected void
	add(
		long[]	a,
		long[] 	b )
	{
		for (int i=0;i<a.length;i++){
			a[i]	+= b[i];
		}
	}
	
	protected void
	snapshotSupport(
		DHTTransportStatsImpl	clone )
	{
		clone.pings			= (long[])pings.clone();
		clone.find_nodes	= (long[])find_nodes.clone();
		clone.find_values	= (long[])find_values.clone();
		clone.stores		= (long[])stores.clone();
		clone.data			= (long[])data.clone();
		clone.key_blocks	= (long[])key_blocks.clone();
		clone.store_queries	= (long[])store_queries.clone();
		clone.aliens		= (long[])aliens.clone();
		
		clone.incoming_requests	= incoming_requests;
		clone.outgoing_requests	= outgoing_requests;
	}
		// ping
	
	public void
	pingSent(
		DHTUDPPacketRequest	request )
	{
		pings[STAT_SENT]++;
					
		outgoingRequestSent( request );
	}
	
	public void
	pingOK()
	{
		pings[STAT_OK]++;
	}
	public void
	pingFailed()
	{
		pings[STAT_FAILED]++;
	}
	public void
	pingReceived()
	{
		pings[STAT_RECEIVED]++;
	}
	
	public long[]
	getPings()
	{
		return( pings );
	}
	
		// key blocks
	
	public void
	keyBlockSent(
		DHTUDPPacketRequest	request )
	{
		key_blocks[STAT_SENT]++;
					
		outgoingRequestSent( request );
	}
	public void
	keyBlockOK()
	{
		key_blocks[STAT_OK]++;
	}
	public void
	keyBlockFailed()
	{
		key_blocks[STAT_FAILED]++;
	}
	public void
	keyBlockReceived()
	{
		key_blocks[STAT_RECEIVED]++;
	}
	
	public long[]
	getKeyBlocks()
	{
		return( key_blocks );
	}
	
		// store queries
	
	public void
	queryStoreSent(
		DHTUDPPacketRequest	request )
	{
		store_queries[STAT_SENT]++;
					
		outgoingRequestSent( request );
	}
	
	public void
	queryStoreOK()
	{
		store_queries[STAT_OK]++;
	}
	
	public void
	queryStoreFailed()
	{
		store_queries[STAT_FAILED]++;
	}
	
	public void
	queryStoreReceived()
	{
		store_queries[STAT_RECEIVED]++;
	}
	
	public long[]
	getQueryStores()
	{
		return( store_queries );
	}
	
		// find node
	
	public void
	findNodeSent(
		DHTUDPPacketRequest	request )
	{
		find_nodes[STAT_SENT]++;
					
		outgoingRequestSent( request );
	}
	public void
	findNodeOK()
	{
		find_nodes[STAT_OK]++;
	}
	public void
	findNodeFailed()
	{
		find_nodes[STAT_FAILED]++;
	}
	public void
	findNodeReceived()
	{
		find_nodes[STAT_RECEIVED]++;
	}
	public long[]
	getFindNodes()
	{
		return( find_nodes );
	}
	
		// find value
	
	public void
	findValueSent(
		DHTUDPPacketRequest	request )
	{
		find_values[STAT_SENT]++;
					
		outgoingRequestSent( request );
	}
	public void
	findValueOK()
	{
		find_values[STAT_OK]++;
	}
	public void
	findValueFailed()
	{
		find_values[STAT_FAILED]++;
	}
	public void
	findValueReceived()
	{
		find_values[STAT_RECEIVED]++;
	}
	public long[]
	getFindValues()
	{
		return( find_values );
	}
	
		// store
	
	public void
	storeSent(
		DHTUDPPacketRequest	request )
	{
		stores[STAT_SENT]++;
		
		outgoingRequestSent( request );
	}
	
	public void
	storeOK()
	{
		stores[STAT_OK]++;
	}
	public void
	storeFailed()
	{
		stores[STAT_FAILED]++;
	}
	public void
	storeReceived()
	{
		stores[STAT_RECEIVED]++;
	}
	public long[]
	getStores()
	{
		return( stores );
	}
		//stats
	
	public void
	statsSent(
		DHTUDPPacketRequest	request )
	{
		stats[STAT_SENT]++;
		
		outgoingRequestSent( request );
	}
	
	public void
	statsOK()
	{
		stats[STAT_OK]++;
	}
	public void
	statsFailed()
	{
		stats[STAT_FAILED]++;
	}
	public void
	statsReceived()
	{
		stats[STAT_RECEIVED]++;
	}
	
		//data
	
	public void
	dataSent(
		DHTUDPPacketRequest	request )
	{
		data[STAT_SENT]++;
		
		outgoingRequestSent( request );
	}
	
	public void
	dataOK()
	{
		data[STAT_OK]++;
	}

    public void
	dataFailed()
	{
		data[STAT_FAILED]++;
	}
	
	public void
	dataReceived()
	{
		data[STAT_RECEIVED]++;
	}
	
	public long[]
	getData()
	{
		return( data );
	}
	
	protected void
	outgoingRequestSent(
		DHTUDPPacketRequest	request )
	{
		outgoing_requests++;		

		if ( DHTLog.TRACE_VERSIONS && request != null ){
			
			byte protocol_version = request.getProtocolVersion();
			
			if ( protocol_version >= 0 && protocol_version < outgoing_request_versions.length ){
				
				outgoing_request_versions[ protocol_version ]++;
				
				outgoing_version_requests++;
				
				if ( outgoing_version_requests%100 == 0 ){
					
					String	str= "";
					
					for (int i=0;i<outgoing_request_versions.length;i++){
						
						long	count = outgoing_request_versions[i];
						
						if ( count > 0 ){
							
							str += (str.length()==0?"":", ") + i + "=" +  count + "[" +
										((outgoing_request_versions[i]*100)/outgoing_version_requests) + "]";
						}
					}
					
					System.out.println( "net " + request.getTransport().getNetwork() + ": Outgoing versions: tot = " + outgoing_requests +"/" + outgoing_version_requests + ": " + str );
				}
				
				if ( outgoing_version_requests%1000 == 0 ){
					
					for (int i=0;i<outgoing_request_versions.length;i++){
						
						outgoing_request_versions[i] = 0;
					}
	
					outgoing_version_requests	= 0;
				}
			}
		}
	}
	
	public void
	incomingRequestReceived(
		DHTUDPPacketRequest	request,
		boolean				alien )
	{
		incoming_requests++;
		
		if ( alien && request != null ){
			
			// System.out.println( "Alien on net " + request.getNetwork() + " - sender=" + request.getAddress());
			
			int	type = request.getAction();
			
			if ( type == DHTUDPPacketHelper.ACT_REQUEST_FIND_NODE ){
				
				aliens[AT_FIND_NODE]++;
			
			}else if ( type == DHTUDPPacketHelper.ACT_REQUEST_FIND_VALUE ){
				
				aliens[AT_FIND_VALUE]++;
				
			}else if ( type == DHTUDPPacketHelper.ACT_REQUEST_PING ){
				
				aliens[AT_PING]++;
				
			}else if ( type == DHTUDPPacketHelper.ACT_REQUEST_STATS ){
				
				aliens[AT_STATS]++;
				
			}else if ( type == DHTUDPPacketHelper.ACT_REQUEST_STORE ){
				
				aliens[AT_STORE]++;
				
			}else if ( type == DHTUDPPacketHelper.ACT_REQUEST_KEY_BLOCK ){
				
				aliens[AT_KEY_BLOCK]++;
				
			}else if ( type == DHTUDPPacketHelper.ACT_REQUEST_QUERY_STORE ){
				
				aliens[AT_QUERY_STORE]++;
			}
		}
		
		if ( DHTLog.TRACE_VERSIONS && request != null ){
			
			byte protocol_version = request.getProtocolVersion();
			
			if ( protocol_version >= 0 && protocol_version < incoming_request_versions.length ){
				
				incoming_request_versions[ protocol_version ]++;
				
				incoming_version_requests++;
				
				if ( incoming_version_requests%100 == 0 ){
					
					String	str= "";
					
					for (int i=0;i<incoming_request_versions.length;i++){
						
						long	count = incoming_request_versions[i];
						
						if ( count > 0 ){
							
							str += (str.length()==0?"":", ") + i + "=" +  count + "[" +
										((incoming_request_versions[i]*100)/incoming_version_requests) + "]";
						}
					}
					
					System.out.println( "net " + request.getTransport().getNetwork() + ": Incoming versions: tot = " + incoming_requests +"/" + incoming_version_requests + ": " + str );
				}
				
				if ( incoming_version_requests%1000 == 0 ){
					
					for (int i=0;i<incoming_request_versions.length;i++){
						
						incoming_request_versions[i] = 0;
					}
	
					incoming_version_requests	= 0;
				}
			}
		}
	}
	
	public long[]
	getAliens()
	{
		return( aliens );
	}
	
	public long
	getIncomingRequests()
	{
		return( incoming_requests );
	}
	
	public void
	recordSkew(
		InetSocketAddress	originator_address,
		long				skew )
	{
		byte[]	bytes = AddressUtils.getAddressBytes( originator_address );
		
		if ( skew_originator_bloom.contains( bytes)){
		
			//System.out.println( "skipping skew: " + originator_address );
			
			return;
		}
		
		skew_originator_bloom.add( bytes );
		
		//System.out.println( "adding skew: " + originator_address + "/" + skew );
		
		int	i_skew = skew<Integer.MAX_VALUE?(int)skew:(Integer.MAX_VALUE-1);
		
			// no sync here as not important so ensure things work ok
		
		int	pos = skew_pos;
		
		skew_values[ pos++ ] = i_skew;
		
		if ( pos == SKEW_VALUE_MAX ){
			
			pos	= 0;
		}
		
		skew_pos = pos;
	}
	
	public long
	getSkewAverage()
	{
		long	now = SystemTime.getCurrentTime();
		
		if ( 	now < last_skew_average_time ||
				now - last_skew_average_time > 30000 ){
			
			int[]	values = (int[])skew_values.clone();
			
			int		pos = skew_pos;
			
			int		num_values;
			
			if ( values[pos] == Integer.MAX_VALUE ){
				
				num_values = pos;
			}else{
				
				num_values = SKEW_VALUE_MAX;
			}
			
			Arrays.sort( values, 0, num_values );
			
				// remove outliers
			
			int	start 	= num_values/3;
			int end		= 2*num_values/3;
			
			int	entries	= end - start;
			
			if ( entries < 5 ){
				
				last_skew_average = 0;
				
			}else{
				
				long	total = 0;
				
				for (int i=start;i<end;i++){
					
					total += (long)values[i];
				}
				
				last_skew_average 		= total / entries;
			}
			
			last_skew_average_time	= now;
		}
		
		return( last_skew_average );
	}
	
	public String
	getString()
	{
		return( "ping:" + getString( pings ) + "," +
				"store:" + getString( stores ) + "," +
				"node:" + getString( find_nodes ) + "," +
				"value:" + getString( find_values ) + "," +
				"stats:" + getString( stats ) + "," +
				"data:" + getString( data ) + "," +
				"kb:" + getString( key_blocks ) + "," +
				"incoming:" + incoming_requests +"," +
				"alien:" + getString( aliens ));
	}
	
	protected String
	getString(
		long[]	x )
	{
		String	str = "";
		
		for (int i=0;i<x.length;i++){
			
			str += (i==0?"":",") + x[i];
		}
				
		return( str );
	}
}
