package net.i2p.router.web;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.text.DecimalFormat;

import net.i2p.data.DataHelper;
import net.i2p.stat.Rate;
import net.i2p.stat.RateStat;
import net.i2p.router.Router;
import net.i2p.router.RouterContext;
import net.i2p.router.RouterVersion;

/**
 * Simple helper to query the appropriate router for data necessary to render
 * the summary sections on the router console.  
 */
public class SummaryHelper {
    private RouterContext _context;
    /**
     * Configure this bean to query a particular router context
     *
     * @param contextId begging few characters of the routerHash, or null to pick
     *                  the first one we come across.
     */
    public void setContextId(String contextId) {
        try {
            _context = ContextHelper.getContext(contextId);
        } catch (Throwable t) {
            t.printStackTrace();
        }
    }
    
    /**
     * Retrieve the shortened 4 character ident for the router located within
     * the current JVM at the given context.
     *
     */
    public String getIdent() { 
        if (_context == null) return "[no router]";
        
        if (_context.routerHash() != null)
            return _context.routerHash().toBase64().substring(0, 4);
        else
            return "[unknown]";
    }
    /**
     * Retrieve the version number of the router.
     *
     */
    public String getVersion() { 
        return RouterVersion.VERSION + "-" + RouterVersion.BUILD;
    }
    /**
     * Retrieve a pretty printed uptime count (ala 4d or 7h or 39m)
     *
     */
    public String getUptime() { 
        if (_context == null) return "[no router]";
        
        Router router = _context.router();
        if (router == null) 
            return "[not up]";
        else
            return DataHelper.formatDuration(router.getUptime());
    }

        
    /**
     * Retrieve amount of used memory.
     *
     */
    public String getMemory() {
        DecimalFormat integerFormatter = new DecimalFormat("###,###,##0");
        long used = (Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory())/1024;
        long usedPc = 100 - ((Runtime.getRuntime().freeMemory() * 100) / Runtime.getRuntime().totalMemory());
        return integerFormatter.format(used) + "KB (" + usedPc + "%)"; 
    }
    
    /**
     * How many peers we are talking to now
     *
     */
    public int getActivePeers() { 
        if (_context == null) 
            return 0;
        else
            return _context.commSystem().countActivePeers();
    }
    /**
     * How many active identities have we spoken with recently
     *
     */
    public int getActiveProfiles() { 
        if (_context == null) 
            return 0;
        else
            return _context.profileOrganizer().countActivePeers();
    }
    /**
     * How many active peers the router ranks as fast.
     *
     */
    public int getFastPeers() { 
        if (_context == null) 
            return 0;
        else
            return _context.profileOrganizer().countFastPeers();
    }
    /**
     * How many active peers the router ranks as having a high capacity.
     *
     */
    public int getHighCapacityPeers() { 
        if (_context == null) 
            return 0;
        else
            return _context.profileOrganizer().countHighCapacityPeers();
    }
    /**
     * How many active peers the router ranks as well integrated.
     *
     */
    public int getWellIntegratedPeers() { 
        if (_context == null) 
            return 0;
        else
            return _context.profileOrganizer().countWellIntegratedPeers();
    }
    /**
     * How many peers the router ranks as failing.
     *
     */
    public int getFailingPeers() { 
        if (_context == null) 
            return 0;
        else
            return _context.profileOrganizer().countFailingPeers();
    }
    /**
     * How many peers totally suck.
     *
     */
    public int getShitlistedPeers() { 
        if (_context == null) 
            return 0;
        else
            return _context.shitlist().getRouterCount();
    }
 
    /**
     * How fast we have been receiving data over the last minute (pretty printed
     * string with 2 decimal places representing the KBps)
     *
     */
    public String getInboundMinuteKBps() { 
        if (_context == null) 
            return "0.0";
        
        RateStat receiveRate = _context.statManager().getRate("transport.receiveMessageSize");
        Rate rate = receiveRate.getRate(60*1000);
        double bytes = rate.getLastTotalValue();
        double bps = (bytes*1000.0d)/(rate.getPeriod()*1024.0d); 

	DecimalFormat fmt = new DecimalFormat("##0.00");
        return fmt.format(bps);
    }
    /**
     * How fast we have been sending data over the last minute (pretty printed
     * string with 2 decimal places representing the KBps)
     *
     */
    public String getOutboundMinuteKBps() { 
        if (_context == null) 
            return "0.0";
        
        RateStat receiveRate = _context.statManager().getRate("transport.sendMessageSize");
        Rate rate = receiveRate.getRate(60*1000);
        double bytes = rate.getLastTotalValue();
        double bps = (bytes*1000.0d)/(rate.getPeriod()*1024.0d); 

	DecimalFormat fmt = new DecimalFormat("##0.00");
        return fmt.format(bps);
    }
    
    /**
     * How fast we have been receiving data over the last 5 minutes (pretty printed
     * string with 2 decimal places representing the KBps)
     *
     */
    public String getInboundFiveMinuteKBps() {
        if (_context == null) 
            return "0.0";
        
        RateStat receiveRate = _context.statManager().getRate("transport.receiveMessageSize");
        Rate rate = receiveRate.getRate(5*60*1000);
        double bytes = rate.getLastTotalValue();
        double bps = (bytes*1000.0d)/(rate.getPeriod()*1024.0d); 

	DecimalFormat fmt = new DecimalFormat("##0.00");
        return fmt.format(bps);
    }
    
    /**
     * How fast we have been sending data over the last 5 minutes (pretty printed
     * string with 2 decimal places representing the KBps)
     *
     */
    public String getOutboundFiveMinuteKBps() { 
        if (_context == null) 
            return "0.0";
        
        RateStat receiveRate = _context.statManager().getRate("transport.sendMessageSize");
        Rate rate = receiveRate.getRate(5*60*1000);
        double bytes = rate.getLastTotalValue();
        double bps = (bytes*1000.0d)/(rate.getPeriod()*1024.0d); 

	DecimalFormat fmt = new DecimalFormat("##0.00");
        return fmt.format(bps);
    }
    
    /**
     * How fast we have been receiving data since the router started (pretty printed
     * string with 2 decimal places representing the KBps)
     *
     */
    public String getInboundLifetimeKBps() { 
        if (_context == null) 
            return "0.0";
        
        long received = _context.bandwidthLimiter().getTotalAllocatedInboundBytes();

        DecimalFormat fmt = new DecimalFormat("##0.00");

        // we use the unadjusted time, since thats what getWhenStarted is based off
        long lifetime = _context.clock().now()-_context.clock().getOffset()
                        - _context.router().getWhenStarted();
        lifetime /= 1000;
        if (received > 0) {
            double receivedKBps = received / (lifetime*1024.0);
            return fmt.format(receivedKBps);
        } else {
            return "0.0";
        }
    }
    
    /**
     * How fast we have been sending data since the router started (pretty printed
     * string with 2 decimal places representing the KBps)
     *
     */
    public String getOutboundLifetimeKBps() { 
        if (_context == null) 
            return "0.0";
        
        long sent = _context.bandwidthLimiter().getTotalAllocatedOutboundBytes();

        DecimalFormat fmt = new DecimalFormat("##0.00");

        // we use the unadjusted time, since thats what getWhenStarted is based off
        long lifetime = _context.clock().now()-_context.clock().getOffset() 
                        - _context.router().getWhenStarted();
        lifetime /= 1000;
        if (sent > 0) {
            double sendKBps = sent / (lifetime*1024.0);
            return fmt.format(sendKBps);
        } else {
            return "0.0";
        }
    }
    
    /**
     * How much data have we received since the router started (pretty printed
     * string with 2 decimal places and the appropriate units - GB/MB/KB/bytes)
     *
     */
    public String getInboundTransferred() { 
        if (_context == null) 
            return "0.0";
        
        long received = _context.bandwidthLimiter().getTotalAllocatedInboundBytes();

        return getTransferred(received);
    }
    
    /**
     * How much data have we sent since the router started (pretty printed
     * string with 2 decimal places and the appropriate units - GB/MB/KB/bytes)
     *
     */
    public String getOutboundTransferred() { 
        if (_context == null) 
            return "0.0";
        
        long sent = _context.bandwidthLimiter().getTotalAllocatedOutboundBytes();
        return getTransferred(sent);
    }
    
    private static String getTransferred(long bytes) {
        double val = bytes;
        int scale = 0;
        if (bytes > 1024*1024*1024) {
            // gigs transferred
            scale = 3; 
            val /= (double)(1024*1024*1024);
        } else if (bytes > 1024*1024) {
            // megs transferred
            scale = 2;
            val /= (double)(1024*1024);
        } else if (bytes > 1024) {
            // kbytes transferred
            scale = 1;
            val /= (double)1024;
        } else {
            scale = 0;
        }
        
        DecimalFormat fmt = new DecimalFormat("##0.00");

        String str = fmt.format(val);
        switch (scale) {
            case 1: return str + "KB";
            case 2: return str + "MB";
            case 3: return str + "GB";
            default: return bytes + "bytes";
        }
    }
    
    /**
     * How many client destinations are connected locally.
     *
     * @return html section summary
     */
    public String getDestinations() {
        ByteArrayOutputStream baos = new ByteArrayOutputStream(1024);
        try {
            OutputStreamWriter osw = new OutputStreamWriter(baos);
            _context.clientManager().renderStatusHTML(osw);
            osw.flush();
            return new String(baos.toByteArray());
        } catch (IOException ioe) {
            _context.logManager().getLog(SummaryHelper.class).error("Error rendering client info", ioe);
            return "";
        }
    }
    
    /**
     * How many free inbound tunnels we have.
     *
     */
    public int getInboundTunnels() { 
        if (_context == null) 
            return 0;
        else
            return _context.tunnelManager().getFreeTunnelCount();
    }
    
    /**
     * How many active outbound tunnels we have.
     *
     */
    public int getOutboundTunnels() { 
        if (_context == null) 
            return 0;
        else
            return _context.tunnelManager().getOutboundTunnelCount();
    }
    
    /**
     * How many tunnels we are participating in.
     *
     */
    public int getParticipatingTunnels() { 
        if (_context == null) 
            return 0;
        else
            return _context.tunnelManager().getParticipatingCount();
    }
 
    /**
     * How lagged our job queue is over the last minute (pretty printed with
     * the units attached)
     *
     */
    public String getJobLag() { 
        if (_context == null) 
            return "0ms";
        
        Rate lagRate = _context.statManager().getRate("jobQueue.jobLag").getRate(60*1000);
        return ((int)lagRate.getAverageValue()) + "ms";
    }
 
    /**
     * How long it takes us to pump out a message, averaged over the last minute 
     * (pretty printed with the units attached)
     *
     */   
    public String getMessageDelay() { 
        if (_context == null) 
            return "0ms";
        
        return _context.throttle().getMessageDelay() + "ms";
    }
    
    /**
     * How long it takes us to test our tunnels, averaged over the last 10 minutes
     * (pretty printed with the units attached)
     *
     */
    public String getTunnelLag() { 
        if (_context == null) 
            return "0ms";
        
        return _context.throttle().getTunnelLag() + "ms";
    }
}