package page.tools.stats;

import java.io.File;
import java.util.Collection;
import java.util.Map;
import java.util.TreeSet;

import org.wikiwebserver.core.Privilege;
import org.wikiwebserver.core.WareHouse;
import org.wikiwebserver.core.WikiMap;
import org.wikiwebserver.handler.http.HTTPException;
import org.wikiwebserver.handler.http.interfaces.HTTPResponder;
import org.wikiwebserver.html.HTMLHelper;

import page.config.SiteTemplatedPage;
import org.wikiwebserver.util.IPToCountry;
import org.wikiwebserver.util.comparator.DescendingEntryValueComparator;
import org.wikiwebserver.util.comparator.EntryKeyComparator;
import org.wikiwebserver.util.comparator.EntryValueComparator;

import page.misc.CommentView;
import page.misc.PaymentView;
import page.tools.entity.Browser;
import page.tools.entity.Comment;
import page.tools.entity.Payment;
import page.tools.entity.ProtectedStorable;
import page.tools.entity.User;
import page.tools.management.FileTreeView;
import static org.wikiwebserver.html.HTMLHelper.*;

public class BrowserInfo extends SiteTemplatedPage implements HTTPResponder {
    
    private String resourcePath = "/templates/default/browserinfo/";      
    
    private long getLongStat(Browser b, String key) {
        long value = 0;
        Number valueObj = (Number) b.get(key);
        if (valueObj != null) value = valueObj.longValue(); 
        return value;
    }
    
    private String getStringStat(Browser b, String key) {
        String value = "Unknown";
        Object valueObj = b.get(key);
        if (valueObj != null) value = valueObj.toString();
        return value;
    }    
	
    public void generate() throws HTTPException {    
        
        addResourceRoot(resourcePath);
        addCSSLink("browserinfo.css");        
        
        String operatorID = getRequest().getHeaders().getRequestCookies().get("userID");
        String browserID = null;
        String userID = null;
        
        User operator = getUser(); 
        
        if (getFormData() != null) {
            browserID = getFormData().getFirst("browserID"); 
            userID = getFormData().getFirst("userID"); 
            
            String command = getFormData().getFirst("cmd");
            if (command != null) {
                if (operator != null && operator.getPrivilege().isAbove(Privilege.USER)) {                 
                    if (command.equals("delete")) {
                    	if (userID != null) {
                            User.getUserById(userID).clear();
                    	}
                    	if (browserID != null) {
                            Browser.getBrowserById(browserID).clear();
                    	}                    	
                        String url = WareHouse.getUrlPathForClass(page.tools.stats.UserList.class);
                        throw new HTTPException(302, "Entity deleted", url);
                    }                  
                }
            }
        }        
        
        if (userID == null && browserID == null) {
            browserID = getCookie().get("browserID");
        }
        
        Browser browser = null;
        if (browserID != null) {
            browser = Browser.getBrowserById(browserID);
            setTitle("Browser Information - WikiWebServer.org");
            append(h(1, "Browser Information") +
                   p("A browser (in the context of WikiWebServer) is an individual" +
                   " web browser, web client, spider or child WikiWebServer."));            
        }
        if (userID != null) {
            browser = User.getUserById(userID);
            setTitle("User Information - WikiWebServer.org");
            append(h(1, "User Information"));   
            append(avatarImage(User.getUserById(userID)));
            if (userID.equals(operatorID) || (operator != null && operator.getPrivilege().isAbove(Privilege.USER))) {
                try {
                    operator.checkAuthorised(getRequest());
                    String profile = WareHouse.getUrlPathForClass(page.tools.admin.UserProfile.class);
                    append(p(a(profile + "?userid=" + userID, "Edit details")));   
                } 
                catch (Exception ex) {
                    append(p("You must sign in before you can change details"));  
                }
            }
        }
        
        if (browser == null) {
            throw new HTTPException(404, "Entity not found");
        }
        
        append(getDetails(browser));
        
        if (operator != null && operator.getPrivilege().isAbove(Privilege.USER)) {
        	if (userID != null) {
        		append(form(hiddenfield("userID", userID) + submitbutton("cmd", "delete")));
        	}
        	if (browserID != null) {
        		append(form(hiddenfield("browserID", browserID) + submitbutton("cmd", "delete")));
        	}
        }            
    }
    
    private String divInfoItem(String label, String content) {
        return div(HTMLHelper.ContainerType.CLASS, "label", null, label) +
               div(HTMLHelper.ContainerType.CLASS, "value", null, content);
    }
    
    public String getDetails(Browser browser) {
    
        StringBuilder details = new StringBuilder();
        
        long firstAccess = getLongStat(browser, "firstRequestTime");
        long lastAccess = getLongStat(browser, "lastRequestTime");
        
        String lastIP = (String) getStringStat(browser, "lastRequestAddress");
        String lastCountry = (String) getStringStat(browser, "lastRequestCountry");
        if (lastCountry == null) {
            lastCountry = IPToCountry.getCountryName(lastIP);
        }
        String referer = getStringStat(browser, "referer");
        String entrance = getStringStat(browser, "entrance");     
        
        String numRequests = formatNumber(getLongStat(browser, "numRequests"));
        String numVisits = formatNumber(getLongStat(browser, "numVisits"));
        String bytesUploaded = WareHouse.formatSize(getLongStat(browser, "bytesUploaded"));
        String bytesDownloaded = WareHouse.formatSize(getLongStat(browser, "bytesDownloaded"));
        
        String userAgent = getStringStat(browser, "userAgent");
        
              
        details.append(divInfoItem("Identity:", browser.getId()));
        
        if (browser instanceof User) {
            User user = (User) browser;
            details.append(divInfoItem("Identifier:", emailImage(user)));
            details.append(divInfoItem("Privileges:", user.getPrivilege().getLabel()));
            String fullName = (String) user.get("fullname");
            if (fullName != null && fullName.length() > 0) {
                details.append(divInfoItem("Full Name:", fullName));
            }
            String webSite = (String) user.get("website");
            if (webSite != null && webSite.length() > 0) {
                details.append(divInfoItem("Website:", a(webSite, webSite)));
            }    
            String country = (String) user.get("country");
            if (country != null && country.length() > 0) {
                details.append(divInfoItem("Country:", country));
            }  
            String profile = (String) user.get("profile");
            if (profile != null && profile.length() > 0) {
                profile = WareHouse.escapeHTMLEntities(profile);
                details.append(divInfoItem("Profile:", profile));
            }            
        }
        
        
        details.append(divInfoItem("First access:", WareHouse.formatStandardDate(firstAccess)));
        details.append(divInfoItem("Last access:", WareHouse.formatStandardDate(lastAccess)));
        details.append(divInfoItem("Last IP address:", lastIP));
        details.append(divInfoItem("Last IP country:", lastCountry));
        details.append(divInfoItem("Num visits:", numVisits));
        details.append(divInfoItem("Num requests:", numRequests));
        details.append(divInfoItem("Bytes uploaded:", bytesUploaded));
        details.append(divInfoItem("Bytes downloaded:", bytesDownloaded));  
        
        details.append(cleardiv());
        details.append(h(2, "User Agent") + p(userAgent) + cleardiv());
        
        if (browser instanceof User) {
            String browserList = getIds(browser);
            details.append(h(2, "Accessed using:") + 
                   p("This user has accessed WikiWebServer by the following browser(s).") +
                   div("userIds", browserList) + cleardiv()); 
        }         
        else if (browser instanceof Browser) {
            String userList = getIds(browser);
            details.append(h(2, "Signed in as:") + 
                   p("This browser has been used by the following user(s).") +
                   div("userIds", userList) + cleardiv()); 
        }       
        
        details.append(h(2, "Referrer") + p(linkify(referer)) + cleardiv());
        details.append(h(2, "First Visited Page") + p(linkify(entrance)) + cleardiv());   
            
        String addressList = getIPAddresses(browser);
        details.append(h(2, "IP Addresses") + 
               p("IP addresses used by browser sorted by number of requests.") +
               div("ipAddresses", addressList) + cleardiv());
        
        
        if (browser instanceof User) {
            User user = (User) browser;
            
            details.append(h(2, "Comments"));            

            CommentView cv = new CommentView();
            Collection<Comment> comments = user.getComments();
            if (comments.size() > 0) {
                for (Comment c : comments) {
                    details.append(cv.showComment(c, this));
                }
            } else {
                details.append(p("No comments"));
            }
            
            details.append(h(2, "Payments"));            

            PaymentView pv = new PaymentView();
            Collection<Payment> payments = user.getPayments();
            if (payments.size() > 0) {
                for (Payment p : payments) {
                    details.append(pv.showPayment(p, this));
                }
            } else {
                details.append(p("No payments"));
            }            
            
            
            details.append(h(2, "Files"));
            
            File userDir = getUserDir(user);
            if (userDir != null && userDir.exists() && userDir.listFiles().length > 0) {
                FileTreeView tree = new FileTreeView();
                appendToHead(tree.getHead());                
                details.append(tree.getFileTree("User directory", userDir, userDir));
            } else {
                details.append(p("No files"));
            }
        }        
        
        String visitTimes = getVisitTimes(browser, 100);
        details.append(h(2, "Visit times:") + 
               p("Visit times and entrance urls sorted by time.") +
               div("visitTimes", visitTimes) + cleardiv());       
           
        String type = browser instanceof User ? "User%20ID" : "Browser%20ID";
        String link = WareHouse.getUrlPathForClass(page.tools.management.LogViewer.class)
                    + "?logName=requests&" + type + "=" + browser.getId();
        details.append(p("[ <a href='" + link + "'>Search log files</a> ]"));
        
        return details.toString();
    }
    
    private String getIPAddresses(Browser b) {
        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) b.get("RequestAddresses");
        if (dataStore == null) return p("No data");
        for (Map.Entry<String, Object> entry : dataStore.entrySet()) {
            sortedSet.add(entry);
        }
        
        
        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(ContainerType.CLASS, "accessCount", null, formatNumber(count)));
            body.append(url);
            body.append(br());          
        }
        
        return body.toString();
    }   
    
    private String getLink(ProtectedStorable item) {
        if (item instanceof User) {
            return WareHouse.getUrlPathForClass(BrowserInfo.class) + "?userID=" + item.getId();      
        } else if (item instanceof Browser) {
            return WareHouse.getUrlPathForClass(BrowserInfo.class) + "?browserID=" + item.getId();  
        }
        return "#";
    }
    
    private String getIds(Browser b) {
        StringBuilder body = new StringBuilder();
        
        TreeSet<Map.Entry<String, Object>> sortedSet 
            = new TreeSet<Map.Entry<String, Object>>(
                    new EntryValueComparator<String, Object>());

        Map<String, Object> dataStore = null;
        if (b instanceof User) dataStore = (WikiMap) b.get("BrowserIDs");
        else dataStore = (WikiMap) b.get("UserIDs");
        
        if (dataStore == null) return p("No data");        
        
        for (Map.Entry<String, Object> entry : dataStore.entrySet()) {
            sortedSet.add(entry);
        }
        
        int c = 0;
        for (Map.Entry<String, Object> entry : sortedSet) {
            String id = entry.getKey();
            if (b instanceof User) {
                Browser u = Browser.getBrowserById(id);
                if (u == null || !u.isValid()) {
                    // Old, unlinked browser
                  //FIXME b.remove(id, "BrowserIDs");
                } else {
                    body.append(a(getLink(u), id) + " &nbsp;");
                    c++;
                }
            } else if (b instanceof Browser) {
                User u = User.getUserById(id);
                if (u == null || !u.isValid()) {
                    // Old, unlinked user
                    //FIXME b.remove(id, "UserIDs");
                } else { 
                    body.append(a(getLink(u), emailImage(u)) + " &nbsp;");
                    c++;
                }
            }
        }
        if (c==0) return p("No data");
        
        return p(body.toString());
    }      
       
    
    private String getVisitTimes(Browser b, int limit) {
        StringBuilder body = new StringBuilder();
        
        TreeSet<Map.Entry<String, Object>> sortedSet 
            = new TreeSet<Map.Entry<String, Object>>(
                    new EntryKeyComparator<String, Object>());

        Map<String, Object> dataStore = (WikiMap) b.get("VisitTimes");
        if (dataStore == null) return p("No data");
        for (Map.Entry<String, Object> entry : dataStore.entrySet()) {
            sortedSet.add(entry);
        }
        
        int c = 0;
        for (Map.Entry<String, Object> entry: sortedSet) {
            String timeString = (String) entry.getKey();
            String url = ((String)entry.getValue()).replace("&", "&amp;");
            
            String time = WareHouse.formatStandardDate(Long.parseLong(timeString));
                       
            body.append(div(ContainerType.CLASS, "timeBlock", null, time));
            String printableUrl = url;
            if (url.length() > 70) {
                printableUrl = url.substring(0, 35) + " ... " 
                             + url.substring(url.length()-35, url.length());
            }
            body.append(a(url, WareHouse.escapeHTMLEntities(printableUrl)));
            body.append(br());
            if (limit != -1 && ++c > limit) break;            
        } 
        if (c==0) body.append("No data");
        
        return p(body.toString());
    }        
    
    private String formatNumber(Object obj) {
        if (obj instanceof Number) {
            return WareHouse.formatNumber(((Number)obj).longValue());
        }
        return "unknown";
    }
    
    private String linkify(String s) {
        if (s == null) return "Unknown";
        if (s.startsWith("/") || s.startsWith("http://")) {
            return a(s, s);
        }
        return s;
    }
    
    public String getCacheKey() {
        return null;
    }
}
