package page.tools.stats;


import java.util.Iterator;
import java.util.Map;
import java.util.TreeSet;

import org.wikiwebserver.core.WareHouse;
import org.wikiwebserver.core.WikiMap;
import org.wikiwebserver.core.WikiWebServer;
import org.wikiwebserver.handler.http.HTTPRequest;
import org.wikiwebserver.handler.http.HTTPResponse;
import org.wikiwebserver.handler.http.interfaces.*;
import org.wikiwebserver.html.HTMLHelper;
import org.wikiwebserver.html.TemplatedPage;

import org.wikiwebserver.util.comparator.DescendingEntryValueComparator;

import page.config.SiteMonitor;
import page.image.FullUsageGraph;
import page.image.UsageGraph;
import page.tools.admin.ManagementTools;
import page.tools.entity.Browser;
import page.tools.entity.NodeData;
import page.tools.entity.ProtectedStorable;
import page.tools.entity.User;
import page.tools.html.TemplatedPushPage;
import page.tools.management.LogViewer;
import page.tools.management.PersistentStorageBrowser;
import static org.wikiwebserver.html.HTMLHelper.*;


public class Status extends TemplatedPushPage implements HTTPResponder {

    public static final String LF = "\r\n";
    private static final String RESOURCE_ROOT = "/templates/default/stats/";
    private static final int MODE_VALUE = 0, MODE_BYTES = 1, MODE_DATE = 2;  
    private static final int TYPE_USER = 0, TYPE_BROWSER = 1, TYPE_NODE = 2, TYPE_TEXT = 3, TYPE_LINK = 4;  
    
    private long lastPieTime = 0;
    
    public long getExpireTime() {
        return System.currentTimeMillis() + 500;
    }
    
    public void generate() {
        
        boolean pushEnabled = false;
        boolean ajaxEnabled = false;
        
        setTitle("Status - WikiWebServer.org");
        addResourceRoot(RESOURCE_ROOT);
        addCSSLink("stats.css");        
        
        int limit = getPageType() == TemplatedPage.PAGE_TYPE_MOBILE ? 3 : 5;

        if (getFormData() != null) {
            String limitString = getFormData().getFirst("limit");
            if (limitString != null) {
                limit = Integer.parseInt(limitString);
            }
            String liveUpdate = getFormData().getFirst("live");
            if (liveUpdate != null) {
                if (liveUpdate.equals("push")) {
                    pushEnabled = true;
                } 
                else if (liveUpdate.equals("ajax")) {
                    ajaxEnabled = true;
                }
            }
            String expand = getFormData().getFirst("expand");
            if (expand != null) {
                int ref = Integer.parseInt(getFormData().getFirst("ref"));                
                if (expand.equals("entity")) {
                    append(getEntityData(ref, limit));
                }
                else if (expand.equals("stat")) {
                    append(getStatData(ref, limit));
                }                
                return;                
            }
        }
        
        SiteMonitor.updateRates();
        
        
        append(h(1, "WikiWebServer Status:"));
        
        String memAlt = "Memory: Green=available, Blue=allocated, Red=used";
        append("<div class='pieBlock'>");
        append("<img id='memoryPieImg' src='" + getMemoryPieURL() + "' title='" + memAlt + "'/></div>");        
                
        append("<p>This page shows the status of WikiWebServer. Live updating [ ");
        append(a("?live=ajax", "AJAX") + " | " + a("?live=push", "PUSH") + " ]</p>");
        
        append(h(2, "General Status:"));
        
        append("<div class='statsBlock'><h3>Connections:</h3>");
        append("<div id='connectionThreadCountDiv'>" + 
                    getPrintableConnThreadCount() + "</div></div>"); 
        
        append("<div class='statsBlock'><h3>Memory used:</h3>");
        append("<div id='memoryUsedDiv'>" + 
                    getPrintableMemoryUsed() + "</div><br/></div>"); 
       
        append("<div style='clear:left;'></div>");  
        
        append(h(2, "Network Transfer Rates:"));

        String rateAlt = "Transfer rates: Green=available, Blue=read-rate, Red=write-rate";
        append("<div class='pieBlock'>" +
                    "<img id='ratePieImg' src='" + getRatePieURL() + "' title='" + rateAlt + "'/></div>"); 
        
        append("<div class='statsBlock'><h3>Read Rate: (bit/s)</h3>" +
                    "<div id='dataReadRateDiv'>" + 
                    getPrintableValue("dataReadRate") + "</div><br/></div>");

        append("<div class='statsBlock'><h3>Write Rate: (bit/s)</h3>" +
                    "<div id='dataWrittenRateDiv'>" + 
                    getPrintableValue("dataWrittenRate") + "</div><br/></div>");

        append("<div style='clear:left;'></div>");
        
        append("<div class='statsBlock'><h3>Average Response: (ms)</h3>" +
                    "<div id='averageResponseDiv'>" + 
                    getPrintableValue("averageResponse") + "</div><br/></div>");   
        
        append("<div class='statsBlock'><h3>Request Rate: (requests/s)</h3>" +
                "<div id='numRequestsRateDiv'>" + 
                getPrintableValue("numRequestsRate") + "</div><br/></div>");        
        
        append("<div style='clear:left;'></div>");      
        
        append(h(2, "Recent Transfer History:"));
        
        append(image(getMinuteHistoryURL(), "Minutly stats", 
                "id='minuteHistoryImg' float='left' width='345' height='200'")); 
        append(image(getSecondHistoryURL(), "Secondly stats", 
                "id='secondHistoryImg' float='left' width='345' height='200'"));         
        
        append("<div style='clear:left;'></div>");      
        
        append(h(2, "Counters:"));
        
        append("<div class='statsBlock'><h3>Bytes Read:</h3>" +
                    "<div id='dataReadDiv'>" + 
                    getPrintableValue("dataRead") + "</div><br/></div>");

        append("<div class='statsBlock'><h3>Bytes Written:</h3>" +
                    "<div id='dataWrittenDiv'>" + 
                    getPrintableValue("dataWritten") + "</div><br/></div>");        
        
        append("<div class='statsBlock'><h3>Requests:</h3>" +
                    "<div id='numRequestsDiv'>" + 
                    getPrintableValue("numRequests") + "</div><br/></div>");   
        
        append("<div class='statsBlock'><h3>Connections:</h3>" +
                    "<div id='numConnectionsDiv'>" + 
                    getPrintableValue("numConnections") + "</div><br/></div>");     
        
        append("<div class='statsBlock'><h3>Errors:</h3>" +
                    "<div id='numErrorsDiv'>" + 
                    getPrintableValue("numErrors") + "</div><br/></div>");     
        
        append("<div style='clear:left;'></div>");
        
        append(h(2, "Response Cache:"));
        
        append("<div class='statsBlock'><h3>Hits:</h3>" +
               div("numCacheHitsDiv", getPrintableValue("numCacheHits")) + "<br/></div>");     
    
        append("<div class='statsBlock'><h3>Misses:</h3>" +
               div("numCacheMissesDiv", getPrintableValue("numCacheMisses")) + "<br/></div>");           
        
        append("<div class='statsBlock'><h3>Items:</h3>" +
               div("cacheItems", WareHouse.formatNumber(WareHouse.getWikiCacheSize())) +
               "<br/></div>");       
        
        append("<div class='statsBlock'><h3>Size:</h3>" +
               div("cacheSizeInBytes", WareHouse.formatSize(WareHouse.getWikiCacheSizeInBytes())) +
               "</div>");     
        
        append("<div style='clear:left;'></div>");      
        
        append(h(2, "Active Transfers"));
        append(div("transfersBlock", getActiveTransfers()));        
        
        
        String threadStatus = WareHouse.getUrlPathForClass(ThreadStatus.class);        
        String viewLiveLogs = WareHouse.getUrlPathForClass(LiveLogPage.class);
        String viewLogs = WareHouse.getUrlPathForClass(LogViewer.class);        
        String storeBrowser = WareHouse.getUrlPathForClass(PersistentStorageBrowser.class);
        
        append(p("View [ " + a(threadStatus, "Thread status")  + " | " +
        		a(viewLiveLogs, "Live logs") + " | " +
        		a(viewLogs, "Log files") + " | " +
        		a(storeBrowser, "Persistent store") + " ]"));
        
        
        
        String rssStats = WareHouse.getUrlPathForClass(RSSStats.class); 
        String rssAccessTime = rssStats + "?name=UserAccessTimes";
        String rssRegTime = rssStats + "?name=UserRegistrationTimes";
        String rssNewNodeTime = rssStats + "?name=NewNodeTimes";
        String rssCommentTime = rssStats + "?name=CommentTimes";
        String rssPaymentTime = rssStats + "?name=PaymentTimes";
        
        
        String monitors = "/JRSSTray.jnlp?" + 
                          "url1=" + WareHouse.formDataEncode(getServiceAddress() + rssRegTime) +
                          "&url2=" + WareHouse.formDataEncode(getServiceAddress() + rssNewNodeTime) +                          
                          "&url3=" + WareHouse.formDataEncode(getServiceAddress() + rssCommentTime) +
                          "&url4=" + WareHouse.formDataEncode(getServiceAddress() + rssPaymentTime);        
        
        append(p("Monitor (RSS) [ " + a(rssAccessTime, "User Access")  + " | " +
                 a(rssRegTime, "User Registrations") + " | " +
                 a(rssNewNodeTime, "New Nodes") + " | " +
                 a(rssCommentTime, "Comments") + " | " +
                 a(rssPaymentTime, "Contributions") + " ]"));
        
        append(p("Monitor using " + a(monitors, "JRSSTray") + "."));    
        
        append(h(1, "Maintenance Status"));
        String classReloadDetail = "Class reload time: " + WareHouse.formatStandardDate(WareHouse.getLastFileChangeTime());  
        String vmStartDetail = "VM start time: " + WareHouse.formatStandardDate(WareHouse.getVMStartTime());        
        append(p(classReloadDetail + br() + vmStartDetail));
        
        String task = WikiWebServer.getMaintenceTask();
        String taskDetail = task + " started at " + WareHouse.formatStandardDate(WikiWebServer.getMaintenceTaskStartTime());
        append(h(3, "Current maintenance task:") +
               "<div id='maintenanceTask' style='padding:10px;font-size: 2em;'>" + task + "</div>" +
               "<div id='maintenanceTaskDetail' style='padding-left:10px;font-size: 1em;'>" + taskDetail + "</div>");        

        String manageTools = WareHouse.getUrlPathForClass(ManagementTools.class);
        append(p(a(manageTools, "WikiWebServer management options")));
        append(h(1, "Usage Statistics"));
        
        String usageGraph = WareHouse.getUrlPathForClass(UsageGraph.class);   
        append(image(usageGraph + "?u=Month&w=225&h=200", "Monthly stats", "float='left' width='225' height='200'"));         
        append(image(usageGraph + "?u=Day&w=225&h=200", "Daily stats", "float='left' width='225' height='200'"));      
        append(image(usageGraph + "?u=Hour&w=225&h=200", "Hourly stats", "float='left' width='225' height='200'"));         
 
        String fullUsageGraph = WareHouse.getUrlPathForClass(FullUsageGraph.class);
        append(p(a(fullUsageGraph + "?Content-Type=application/pdf", "PDF of usage")));
        
        append("<div style='clear:left;'></div>");              
        
        append(h(1, "Users:"));
        for (int i=0; i<4; i++) {
            append(getEntityData(i, limit)); 
        }
        
        
        append(h(1, "Nodes:"));
        for (int i=4; i<8; i++) {
            append(getEntityData(i, limit)); 
        }        

        
        append(h(1, "Browsers:"));
        for (int i=8; i<12; i++) {
            append(getEntityData(i, limit)); 
        }          

        
        append(h(1, "Sources:"));
        for (int i=12; i<16; i++) {
            append(getStatData(i, limit)); 
        }  
        

        
        append(h(1, "Resources:")); 
        for (int i=16; i<21; i++) {
            append(getStatData(i, limit)); 
        }          
        


        int rate = pushEnabled ? 250 : 1000;
        setUpdatePeriod(rate);
        setPushEnabled(pushEnabled);        
        setPeriodicAjaxUpdateEnabled(ajaxEnabled);
        
    }
    
    protected void ajax() {
        SiteMonitor.updateRates();
        
        long time = System.currentTimeMillis();        
        
        append(updateContentScript("dataRead"));
        append(updateContentScript("dataWritten"));
        append(updateContentScript("numRequests"));
        append(updateContentScript("numConnections"));
        append(updateContentScript("numErrors"));
        append(updateContentScript("dataReadRate"));
        append(updateContentScript("dataWrittenRate"));
        append(updateContentScript("numRequestsRate"));      
        append(updateContentScript("averageResponse"));
        append(updateContentScript("numCacheHits"));      
        append(updateContentScript("numCacheMisses"));        
                
        append(HTMLHelper.updateHTMLScript("memoryUsedDiv", getPrintableMemoryUsed()));
        append(updateHTMLScript("connectionThreadCountDiv", getPrintableConnThreadCount()));

        append(updateHTMLScript("cacheItems", WareHouse.formatNumber(WareHouse.getWikiCacheSize())));
        append(updateHTMLScript("cacheSizeInBytes", WareHouse.formatSize(WareHouse.getWikiCacheSizeInBytes())));
        
        if (lastPieTime + 1000 < time) {
            append(updateSrcScript("memoryPieImg", getMemoryPieURL()));
            append(updateSrcScript("ratePieImg", getRatePieURL()));
            append(updateSrcScript("secondHistoryImg", getSecondHistoryURL()));
            append(updateSrcScript("minuteHistoryImg", getMinuteHistoryURL()));
            lastPieTime = time;
        }
        
        append(updateHTMLScript("transfersBlock", getActiveTransfers()));
        
        String task = WikiWebServer.getMaintenceTask();
        append(updateHTMLScript("maintenanceTask", task));
        long sd = WikiWebServer.getMaintenceTaskStartTime();
        String detail = "Status updated at " + WareHouse.formatStandardDate(sd);
        append(updateHTMLScript("maintenanceTaskDetail", detail));
    }
    
    private String getMinuteHistoryURL() {
        String usageGraph = WareHouse.getUrlPathForClass(UsageGraph.class);
        return usageGraph + "?u=Minute&w=345&h=200&r=" + 
                            (System.currentTimeMillis() / 60000); 
    }

    private String getSecondHistoryURL() {
        String usageGraph = WareHouse.getUrlPathForClass(UsageGraph.class);
        return usageGraph + "?u=Second&w=345&h=200&t=" + 
                            (System.currentTimeMillis() / 1000); 
    }

    protected void update() {
        append("<script type='text/javascript'>" + LF + "<!--" + LF);
        ajax();
        append(LF + "-->" + LF + "</script>");
    } 
    
    private String getPrintableConnThreadCount() {
        int count = Thread.currentThread().getThreadGroup().activeCount();
        return WareHouse.formatNumber(count);        
    }    
    
    private String getPrintableMemoryUsed() {
        Runtime tim = Runtime.getRuntime();
        return WareHouse.formatNumber(tim.totalMemory() - tim.freeMemory());        
    }   
    
    private String getPrintableValue(String name) {
        Object obj = SiteMonitor.getStatistic(name);
        if (obj == null) return String.valueOf(0);
        else if (obj instanceof Double) {
            double value = ((Double)obj).doubleValue();
            return String.valueOf(WareHouse.formatNumber(value));
        }        
        else if (obj instanceof Number) {
            long value = ((Number)obj).longValue();
            return String.valueOf(WareHouse.formatNumber(value));
        }        
        return String.valueOf(obj);        
    }
    
    private String updateContentScript(String name) {
        return updateHTMLScript(name+"Div", getPrintableValue(name));
    }      
       
    private String getMemoryPieURL() {
        Runtime tim = Runtime.getRuntime();
        long memAvailable = tim.freeMemory();
        long memUsed = tim.totalMemory() - memAvailable;
        long obtainableMemory = tim.maxMemory() - tim.totalMemory();
        
        String data = getNiceValue(obtainableMemory, memAvailable, memUsed) + "," + 
                      getNiceValue(memAvailable, obtainableMemory, memUsed) + "," + 
                      getNiceValue(memUsed, obtainableMemory, memAvailable);
        
        String pie = WareHouse.getUrlPathForClass(page.image.Pie.class);
        return pie + "?w=100&h=100&d=" + data;
    }    
    
    private String getActiveTransfers() {
    	StringBuilder bill = new StringBuilder();
    	Map<HTTPRequest, HTTPResponse> map = SiteMonitor.getActiveRequestResponseMap();
    	Iterator<Map.Entry<HTTPRequest, HTTPResponse>> i = map.entrySet().iterator();
    	while (i.hasNext()) {
    		Map.Entry<HTTPRequest, HTTPResponse> entry = i.next();
    		HTTPRequest request = entry.getKey();
    		HTTPResponse response = entry.getValue();
    		
    		String browserId = request.getHeaders().getRequestCookies().get("BrowserID");
    		String userId = request.getHeaders().getRequestCookies().get("UserID");
    		String nodeId = request.getHeaders().getFirst("X-Node-ID");
    		if (nodeId != null) {
    		    String url = WareHouse.getUrlPathForClass(NodeInfo.class) + "?nodeID=" + nodeId;
    		    bill.append(a(url, strong(request.getSourceAddress())));
    		}
    		else if (userId != null) {
                String url = WareHouse.getUrlPathForClass(BrowserInfo.class) + "?userID=" + userId;
                bill.append(a(url, strong(request.getSourceAddress())));
            }    	
            else if (browserId != null) {
                String url = WareHouse.getUrlPathForClass(BrowserInfo.class) + "?browserID=" + browserId;
                bill.append(a(url, strong(request.getSourceAddress())));
            }      		
    		else {
    		    bill.append(strong(request.getSourceAddress()));
    		}
    		
    		bill.append(" ");
    		bill.append(WareHouse.formatSize(request.getNumBytesRead()));    		
    		bill.append(" &raquo; ");
    		String url = request.getUri();
    		String label = url;
            if (label.length() > 60) {
            	label = url.substring(0, 30) + " ... " 
                      + url.substring(url.length()-30, url.length());
            }
    		bill.append(a(url, WareHouse.escapeHTMLEntities(label)));

    		bill.append(" &raquo; ");
    		bill.append(WareHouse.formatSize(response.getNumBytesWritten()));
    		
    		bill.append(" [");
    		long endTime = response.getFinishTime();
    		if (endTime == 0) endTime = System.currentTimeMillis();
    		long duration = endTime - request.getStartTime();
            if (duration >= 60 * 60 * 1000) {
                bill.append((duration / (60 * 60 * 1000)) + "h ");
            }      		
            if (duration >= 60 * 1000) {
                bill.append((duration / (60 * 1000))%60 + "m ");
            }   		
            bill.append((duration / 1000)%60 + "s");
    		bill.append("]");
    		if (i.hasNext()) {
    			bill.append("<br>");
    		}
    	}
    	return bill.toString();
    }
        
    private String getRatePieURL() {
        long readRate = getLong("dataReadRate");
        long writtenRate = getLong("dataWrittenRate");
        long maxRate = getLong("dataReadMaxRate") + getLong("dataWrittenMaxRate");
        long idleRate = maxRate-readRate-writtenRate;
        
        String data = getNiceValue(idleRate, readRate, writtenRate) + "," + 
                      getNiceValue(readRate, idleRate, writtenRate) + "," + 
                      getNiceValue(writtenRate, idleRate, readRate);
        
        String pie = WareHouse.getUrlPathForClass(page.image.Pie.class);        
        return pie + "?w=100&h=100&d=" + data;
    }
    
    private int getNiceValue(long v, long o1, long o2) {
        double fract = (double) v / (v + o1 + o2);
        return (int) (fract * 360);
    }
    
    private String getSortedStats(String store, int limit, int mode, int type) {
        StringBuilder body = new StringBuilder();
        
        TreeSet<Map.Entry<String, Object>> sortedSet 
            = new TreeSet<Map.Entry<String, Object>>(
                    new DescendingEntryValueComparator<String, Object>());

        Map<String, Object> dataStore = (WikiMap) SiteMonitor.getStatistic(store);
        if (dataStore == null) return p("No data");
        for (Map.Entry<String, Object> entry : dataStore.entrySet()) {
            if (entry.getKey() == null) continue;
            if (entry.getKey().endsWith("$total")) continue;
            if (entry.getKey().endsWith("$count")) continue;
            sortedSet.add(entry);
        }
        
        int c=0;
        for (Map.Entry<String, Object> entry: sortedSet) {
            Object value = entry.getValue();
            String url = entry.getKey();
            long count = 0;
            if (value instanceof Number) {
                count = ((Number) value).longValue();
            }           
            body.append("<div class='accessCount'>");
            if (mode == MODE_BYTES) {
                body.append(WareHouse.formatSize(count));
            } else {
                body.append(WareHouse.formatNumber(count));
            }
            body.append("</div>");
            if (type == TYPE_LINK) {
                url = url.replace("&", "&amp;");
                body.append("<a href='" + url + "'>");
            }
            if (url.length() > 70) {
                url = url.substring(0, 35) + " ... " 
                    + url.substring(url.length()-35, url.length());
            }
            body.append(WareHouse.escapeHTMLEntities(url));
            if (type == TYPE_LINK) body.append("</a>");
            body.append(br());
            if (limit != -1 && ++c >= limit) break;            
        } 
        if (c==0) body.append(p("No data"));
        
        return body.toString();
    }
    
    private String getEntityData(int i, int limit) {
    	int newLimit = limit * 10;
    	if (newLimit > 10000) newLimit = 10000;    	
        String expandLink = "?expand=entity&ref=" + i + "&limit=" + newLimit;
        return h(2, statBlocks[i].title) + 
               p(statBlocks[i].description +
               getSortedEntities(statBlocks[i].store, limit, statBlocks[i].mode, statBlocks[i].type) +
               p(a(expandLink, "Show more"))) +
               div(ContainerType.CLASS, "spacer");
    }
    
    private String getStatData(int i, int limit) {
    	int newLimit = limit * 10;
    	if (newLimit > 10000) newLimit = 10000;  
        String expandLink = "?expand=stat&ref=" + i + "&limit=" + newLimit;
        return h(2, statBlocks[i].title) + 
               p(statBlocks[i].description +
               getSortedStats(statBlocks[i].store, limit, statBlocks[i].mode, statBlocks[i].type) +
               p(a(expandLink, "Show more"))) +
               div(ContainerType.CLASS, "spacer");
    }    
    
    private String getSortedEntities(String store, int limit, int mode, int type) {
        
        String currentID = null;
        if (type== TYPE_BROWSER) {
            getCookie().get("browserID");
        }
        else if (type == TYPE_USER) {
            currentID = getCookie().get("userID");
        }
        else if (type == TYPE_NODE) {
            currentID = getCookie().get("nideID");
        }        
        StringBuilder body = new StringBuilder();
        
        TreeSet<Map.Entry<String, Object>> sortedSet 
            = new TreeSet<Map.Entry<String, Object>>(
                    new DescendingEntryValueComparator<String, Object>());

        Map<String, Object> dataStore = (WikiMap) SiteMonitor.getStatistic(store);
        if (dataStore == null) return "<p>No data</p>";
        
        for (Map.Entry<String, Object> entry : dataStore.entrySet()) {
            if (entry.getKey().endsWith("$total")) continue;
            if (entry.getKey().endsWith("$count")) continue;
            sortedSet.add(entry);
        }

        
        int c=0;
        for (Map.Entry<String, Object> entry: sortedSet) {
            Object value = entry.getValue();
            String id = entry.getKey();
            long count = 0;
            if (value instanceof Number) {
                count = ((Number) value).longValue();
            }   
            else if (value instanceof String) {
                count = Long.parseLong((String) value);
            }             
            String key = String.valueOf(count);
            if (mode == MODE_VALUE) {
                key = WareHouse.formatNumber(count);
            } 
            else if (mode == MODE_BYTES) {
                key = WareHouse.formatSize(count);
            }            
            else if (mode == MODE_DATE) {
                key = WareHouse.formatStandardDate(count);
            }
            
            ProtectedStorable b = null;
            
            if (type == TYPE_BROWSER) b = Browser.getBrowserById(id);
            else if (type == TYPE_USER) b = User.getUserById(id);
            else if (type == TYPE_NODE) b = NodeData.getNodeDataById(id);
            
            if (b != null) {
                
                body.append("<div class='");
                body.append(id.equals(currentID) ? "current" : "normal");
                body.append("'>");       
                
                body.append("<div class='");
                body.append(mode == MODE_DATE ? "dateKey" : "valueKey");
                body.append("'>");
                body.append(key);
                body.append("</div>");
                
                String identity = id;
                String url = null;
                if (b instanceof User) {
                    User user = (User) b;
                    identity = emailImage(user);
                    url = WareHouse.getUrlPathForClass(page.tools.stats.BrowserInfo.class) + "?userID=" + id;
                } 
                else if (b instanceof Browser) {
                    url = WareHouse.getUrlPathForClass(page.tools.stats.BrowserInfo.class) + "?browserID=" + id;
                }
                else if (b instanceof NodeData) {
                    NodeData nd = (NodeData) b;
                    identity = nd.getName();
                    url = WareHouse.getUrlPathForClass(page.tools.stats.NodeInfo.class) + "?nodeID=" + id;
                }
                
                String ip = (String) b.get("lastRequestAddress");
                String country = (String) b.get("lastRequestCountry");
                if (ip == null) ip = "unknown";
                if (country == null) country = "unknown";                  
                
                StringBuilder printable = new StringBuilder();
                printable.append(a(url, identity));
                printable.append(",&nbsp;&nbsp;");
                printable.append("IP: " + ip + " (" + country + ")");
                
                body.append(div(HTMLHelper.ContainerType.CLASS, "value", null, 
                            printable.toString()));
                
                body.append("</div>");

                if (limit != -1 && ++c >= limit) break;                  
            }          
        } 
        if (c==0) body.append(p("No data"));
        
        return body.toString();
    }    
    
    
    private long getLong(String key) {
        Object obj = SiteMonitor.getStatistic(key);
        if (obj != null && obj instanceof Number) {
            return ((Number)obj).longValue();
        }      
        return 0;
    }
    
    

    
    private static class StatItem {
        public String title, description, store;
        public int mode, type;
        public StatItem(String title, String description, String store, int mode, int type) {
            this.title = title;
            this.description = description;
            this.store = store;
            this.mode = mode;
            this.type = type;
        }
    }    
    
    private static StatItem[] statBlocks = {
        
        new StatItem("Recent users:", "Users sorted by last request.", "UserAccessTimes", Status.MODE_DATE, Status.TYPE_USER),
        new StatItem("Frequent users:", "Users sorted by number of visits.", "UserVisits", MODE_VALUE, TYPE_USER),
        new StatItem("User requests:", "Users sorted by number of requests.", "UserRequests", MODE_VALUE, TYPE_USER),       
        new StatItem("User bytes downloaded:", "Users sorted by bytes downloaded.", "UserBytesDownloaded", MODE_BYTES, TYPE_USER), 
        
        new StatItem("Recent nodes:", "Nodes sorted by last request.", "NodeAccessTimes", Status.MODE_DATE, Status.TYPE_NODE),
        new StatItem("Node requests:", "Nodes sorted by number of requests.", "NodeRequests", MODE_VALUE, TYPE_NODE),       
        new StatItem("Node bytes downloaded:", "Node sorted by bytes downloaded.", "NodeBytesDownloaded", MODE_BYTES, TYPE_NODE), 
        new StatItem("Node bytes uploaded:", "Node sorted by bytes uploaded.", "NodeBytesUploaded", MODE_BYTES, TYPE_NODE),    
        
        new StatItem("Recent browsers:", "Browsers sorted by last request.", "BrowserAccessTimes", Status.MODE_DATE, TYPE_BROWSER),
        new StatItem("Frequent browsers:", "Browsers sorted by number of visits.", "BrowserVisits", MODE_VALUE, TYPE_BROWSER),
        new StatItem("Browser requests:", "Browsers sorted by number of requests.", "BrowserRequests", MODE_VALUE, TYPE_BROWSER),       
        new StatItem("Browser bytes downloaded:", "Browsers sorted by bytes downloaded.", "BrowserBytesDownloaded", MODE_BYTES, TYPE_BROWSER),
        
        new StatItem("Referring sites:", "Urls linking to this WikiWebServer.", "Referers", MODE_VALUE, TYPE_LINK), 
        new StatItem("Search keywords:", "Keywords used to find WikiWebServer sorted by frequency.", "Keywords", MODE_VALUE, TYPE_TEXT), 
        new StatItem("Connection addresses:", "Inbound ip addresses sorted by frequency", "ConnectionAddresses", MODE_VALUE, TYPE_TEXT), 
        new StatItem("Connection countries:", "Inbound countries sorted by frequency.", "ConnectionCountries", MODE_VALUE, TYPE_TEXT),
        
        new StatItem("Popular objects:", "Objects sorted by total number of accesses.", "RequestedUrls", MODE_VALUE, TYPE_LINK), 
        new StatItem("Slow objects:", "Objects sorted by average response times (measured in milliseconds).", "ResponseTimes", MODE_VALUE, TYPE_LINK), 
        new StatItem("Large objects:", "Objects sorted by average response size.", "ResponseSizes", MODE_BYTES, TYPE_LINK), 
        new StatItem("Heavy objects:", "Objects sorted by accumulative response size.", "TotalResponseSizes", MODE_BYTES, TYPE_LINK), 
        new StatItem("Response codes:", "Server responses sorted by frequency.", "ResponseInfo", MODE_VALUE, TYPE_TEXT),
   
                
    };    
}
