/* This code is part of Freenet. It is distributed under the GNU General
 * Public License, version 2 (or at your option any later version). See
 * http://www.gnu.org/ for further details of the GPL. */
package freenet.io.comm;

import java.net.InetAddress;
import java.util.Arrays;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.Map;

import freenet.support.Logger;
import freenet.support.Logger.LogLevel;

public class IOStatisticCollector {
	public static final int STATISTICS_ENTRIES = 10;
	public static final int STATISTICS_DURATION_S = 30;
	public static final int STATISTICS_DURATION = 1000*STATISTICS_DURATION_S;
	private long lastrotate;
	
	private static boolean logDEBUG;
	private long totalbytesin;
	private long totalbytesout;
	private final LinkedHashMap<String, StatisticEntry> targets;
	static boolean ENABLE_PER_ADDRESS_TRACKING = false;
	
	public IOStatisticCollector() {
		targets = new LinkedHashMap<String, StatisticEntry>();
		// TODO: only for testing!!!!
		// This should only happen once
		//SNMPAgent.create();
		//SNMPStarter.initialize();
		logDEBUG = Logger.shouldLog(LogLevel.DEBUG, this);
	}
	
	public void addInfo(InetAddress addr, int port, int inbytes, int outbytes, boolean isLocal) {
		try {
			synchronized (this) {
				_addInfo(addr, port, inbytes, outbytes, isLocal);
			}
		} catch (Throwable t) {
			t.printStackTrace();
		}
	}
	
	private void _addInfo(InetAddress addr, int port, int inbytes, int outbytes, boolean isLocal) {
		rotate();
		if(ENABLE_PER_ADDRESS_TRACKING) {
			String key = addr + ":" + port;
			StatisticEntry entry = targets.get(key);
			if (entry == null) {
				entry = new StatisticEntry();
				targets.put(key, entry);
			}
			entry.addData(Math.max(inbytes, 0), Math.max(outbytes, 0));
		}
		if(!isLocal) {
			synchronized(this) {
				totalbytesout += Math.max(outbytes, 0);
				totalbytesin += Math.max(inbytes, 0);
				if(logDEBUG)
					Logger.debug(IOStatisticCollector.class, "Add("+addr+":"+port+ ',' +inbytes+ ',' +outbytes+" -> "+totalbytesin+" : "+totalbytesout);
			}
		}
	}
	
	public void dumpInfo() {
		synchronized (this) {
			_dumpInfo();
		}
	}

	public long[] getTotalIO() {
		synchronized (this) {
			return _getTotalIO();
		}
	}
	
	private long[] _getTotalIO() {
		long ret[] = new long[2]; 
		synchronized(this) {
			ret[0] = totalbytesout;
			ret[1] = totalbytesin;
		}
		return ret;
	}
	
	public int[][] getTotalStatistics() {
		synchronized (this) {
			return _getTotalStatistics();
		}
	}

	private int[][] _getTotalStatistics() {
		//String[] keys = (String[])targets.keySet().toArray();
		int ret[][] = new int[STATISTICS_ENTRIES][2];
		for (int i = 0 ; i < STATISTICS_ENTRIES ; i++) {
			ret[i][0] = ret[i][1] = 0;
		}
		
		for (Map.Entry<String,StatisticEntry> entry : targets.entrySet()) {
			int inres[] = entry.getValue().getRecieved();
			int outres[] = entry.getValue().getSent();
			for (int i = 0 ; i < STATISTICS_ENTRIES ; i++) {
				ret[i][1] += inres[i];
				ret[i][0] += outres[i];
			}
		}
		
		return ret;
	}
	
	private void _dumpInfo() {
		rotate();
		//DateFormat df = DateFormat.getDateInstance(DateFormat.LONG, Locale.FRANCE);
		//System.err.println(DateFormat.getDateInstance().format(new Date()));
		System.err.println(new Date());
		final double divby = STATISTICS_DURATION_S*1024; 
		for (Map.Entry<String,StatisticEntry> entry : targets.entrySet()) {
			String key = entry.getKey();
			int inres[] = entry.getValue().getRecieved();
			int outres[] = entry.getValue().getSent();
			System.err.print((key + "          ").substring(0,22) + ": ");
			int tin = 0;
			int tout = 0;
			
			for (int i = 0 ; i < inres.length ; i++) {
				// in/out in 102.4 bytes (hecto-bytes)
				tin += inres[i];
				tout += outres[i];
				
				int in = (int) ((tin*10.0) / (divby*(i+1)));
				int out =(int) ((tout*10.0) /(divby*(i+1)));
				
				System.err.print("i:" + (in/10) + '.' + (in%10));
				System.err.print(" o:" + (out/10) + '.' + (out%10));
				System.err.print(" \t");
			}
			System.err.println();
		}
		System.err.println();
	}
	
	private void rotate() {
		long now = System.currentTimeMillis();
		if ((now - lastrotate) >= STATISTICS_DURATION) {
			lastrotate = now;
			Object[] keys = targets.keySet().toArray();
			if(keys == null) return; // Why aren't we iterating there ?
			for(int i = 0 ; i < keys.length ; i++) {
				Object key = keys[i];
				if (targets.get(key).rotate() == false)
					targets.remove(key);
			}
			// FIXME: debugging
			//_dumpInfo();
		}
	}

	
/*	
 * to thead each update.... heavy stuff
	private class StatisticUpdater implements Runnable {
		private IOStatisticCollector sc;
		private String key;
		private int inbytes;
		private int outbytes;
		
		public StatisticUpdater(IOStatisticCollector sc, String key,
				int inbytes, int outbytes) {
			this.sc = sc;
			this.key = key;
			this.inbytes = inbytes;
			this.outbytes = outbytes;
			new Thread(this, "IOStatisticCollector$StatisticUpdater").run();
		}
		
		public void run() {
			
		}
	}
	*/
	
	
	
	private static class StatisticEntry {
		private int recieved[];
		private int sent[];
		
		public StatisticEntry() {
			// Create a new array and clear it
			recieved = new int[IOStatisticCollector.STATISTICS_ENTRIES+1];
			sent     = new int[IOStatisticCollector.STATISTICS_ENTRIES+1];
			for (int i = 0 ; i < recieved.length ; i++) {
				recieved[i] = sent[i] = 0;
			}
		}
		
		public void addData(int inbytes, int outbytes) {
			recieved[0] += inbytes;
			sent[0]     += outbytes;
		}
		
		public boolean rotate() {
			boolean hasdata = false;
			for (int i = recieved.length - 1 ; i > 0 ; i--) {
				recieved[i] = recieved[i-1];
				sent[i]     = sent[i-1];
				hasdata |= (recieved[i] > 0) || (sent[i] > 0);
			}
			recieved[0] = sent[0] = 0;
			return hasdata;
		}
		
		public int[] getRecieved() {
			return Arrays.copyOfRange(recieved, 1, 1 + IOStatisticCollector.STATISTICS_ENTRIES);
		}
		
		public int[] getSent() {
			return Arrays.copyOfRange(sent, 1, 1 + IOStatisticCollector.STATISTICS_ENTRIES);
		}
		
	}
}
