package page.image;

import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.text.NumberFormat;
import java.util.Calendar;
import java.util.GregorianCalendar;

import org.wikiwebserver.core.WareHouse;
import org.wikiwebserver.handler.http.FormData;
import org.wikiwebserver.handler.http.interfaces.*;
import org.wikiwebserver.util.CacheableDynamicImage;

import page.config.SiteMonitor;

public class UsageGraph extends CacheableDynamicImage implements HTTPResponder {
    
    private String label = "Second";
    private int field = Calendar.SECOND;
    private int divisions = 60;
    private int expiry = 1000;
	
    protected void init(FormData formData) {
        
        if (formData == null) return;

        if (formData.get("w") != null && formData.get("h") != null) {
            setSize(Integer.parseInt(formData.getFirst("w")),
                    Integer.parseInt(formData.getFirst("h")));
        } 
        else {
            setSize(500, 300);
        }
        
        label = formData.getFirst("u");
        
        if (label == null) label = "Second";

        if (label.equals("Second")) {
            divisions = 60;
            field = Calendar.SECOND;
            expiry = 1000;            
        }        
        else if (label.equals("Minute")) {
            divisions = 60;
            field = Calendar.MINUTE;
            expiry = 60 * 1000;
        } 
        else if (label.equals("Hour")) {
            divisions = 24;
            field = Calendar.HOUR_OF_DAY;
            expiry = 60 * 60 * 1000;
        } 
        else if (label.equals("Day")) {
            divisions = 31;
            field = Calendar.DAY_OF_MONTH;
            expiry = 24 * 60 * 60 * 1000;
        } 
        else if (label.equals("Month")) {
            divisions = 12;
            field = Calendar.MONTH;
            expiry = 28 * 24 * 60 * 60 * 1000;
        }

    }
    
    public String getCacheKey() {
        long sec = System.currentTimeMillis() / expiry;
        return super.getCacheKey() + label + "_" + sec;
    }
    
    public long getExpireTime() {
        return System.currentTimeMillis() + expiry;
    }
    
    protected void paint(Graphics2D g2d) {
        
        GregorianCalendar cal = new GregorianCalendar();
        int current = cal.get(field);
        if (field == Calendar.MONTH) current++;

        long[] requests = getValues(label + "RequestTotal", current, divisions);
        long[] responses = getValues(label + "AverageResponseTime", current, divisions);
        long[] read = getValues(label + "BytesReadTotal", current, divisions);
        long[] written = getValues(label + "BytesWrittenTotal", current, divisions);
        

        g2d.setColor(Color.white);
        g2d.fillRect(0, 0, getWidth(), getHeight());
        
        Font f = g2d.getFont();
        g2d.setFont(new Font(f.getName(), f.getStyle(), 10));

        g2d.setColor(Color.black);  
        renderXAxis(g2d, getWidth(), getHeight(), divisions, label, cal.getTimeInMillis(), field);
        FontMetrics fm = g2d.getFontMetrics();        
        int y = fm.getAscent();
        
        NumberFormat nf = NumberFormat.getInstance();
        
        g2d.setColor(new Color(200, 200, 255));  
        long max = getMaxValue(requests);
        //if (max < 10) max = 10;
        renderBarGraph(g2d, getWidth(), getHeight(), requests, max);  
        String itemLabel = "Peak Requests - " + nf.format(max);
        g2d.drawString(itemLabel, getWidth() - fm.stringWidth(itemLabel) - 10, y);
        
        g2d.setColor(Color.blue);  
        long maxIn = getMaxValue(read);
        long maxOut = getMaxValue(written);
        max = maxIn > maxOut ? maxIn : maxOut;
        //if (max < 102400) max = 102400;
        renderLineGraph(g2d, getWidth(), getHeight(), read, max); 
        y += fm.getHeight();
        itemLabel = "Peak Data In - " + WareHouse.formatSize(maxIn);
        g2d.drawString(itemLabel, getWidth() - fm.stringWidth(itemLabel) - 10, y);        
        
        g2d.setColor(Color.red);  
        //if (max < 102400) max = 102400;
        renderLineGraph(g2d, getWidth(), getHeight(), written, max); 
        y += fm.getHeight();
        itemLabel = "Peak Data Out - " + WareHouse.formatSize(maxOut);
        g2d.drawString(itemLabel, getWidth() - fm.stringWidth(itemLabel) - 10, y);               
        
        g2d.setColor(Color.green);        
        max = getMaxValue(responses);
        //if (max < 1000) max = 1000;
        renderLineGraph(g2d, getWidth(), getHeight(), responses, max);   
        y += fm.getHeight();
        itemLabel = "Peak Mean Response - " + max + "ms";
        g2d.drawString(itemLabel, getWidth() - fm.stringWidth(itemLabel) - 10, y);           

    }
    
    
    private static void renderLineGraph(Graphics2D g2d, int width, int height, long[] data, long max) {

        int leftMargin = 10;
        int rightMargin = 10;
        int topMargin = 45;
        int bottomMargin = 30;        
        
        int printableWidth = width - (leftMargin+rightMargin);
        int printableHeight = height - (topMargin+bottomMargin);
        
        double xUnit = (double) printableWidth / data.length;
        double yUnit = (double) printableHeight / max;     
        
        // A start point is required for drawing a line
        int x = leftMargin;
        int y = (int) (height - bottomMargin - ((data[0]) * yUnit));    

        for (int r=1; r<data.length; r++) {
            int nextX = (int) (leftMargin + xUnit * r);
            int nextY = (int) (height - bottomMargin - (data[r] * yUnit));
            g2d.drawLine(x, y, nextX, nextY);
            x = nextX;
            y = nextY;
        }
    }
    
    private static void renderBarGraph(Graphics2D g2d, int width, int height, long[] data, long max) {

        int leftMargin = 10;
        int rightMargin = 10;
        int topMargin = 45;
        int bottomMargin = 30;        
        
        int printableWidth = width - (leftMargin+rightMargin);
        int printableHeight = height - (topMargin+bottomMargin);
        
        double xUnit = (double) printableWidth / (data.length);
        int halfXUnit = (int)(xUnit / 2) + 1;
        double yUnit = (double) printableHeight / max;     

        for (int r=0; r<data.length; r++) {
            int x = (int) (leftMargin + xUnit * r);
            int y = (int) (height - bottomMargin - (data[r] * yUnit));
            g2d.fillRect(x-halfXUnit, y, halfXUnit*2, (int)(yUnit * data[r]));
        }
    }    
    
    private static void renderXAxis(Graphics2D g2d, int width, int height, long divisions, 
                             String axis, long startTime, int calendarField) {

        int leftMargin = 10;
        int rightMargin = 10;
        int bottomMargin = 30;        
        
        int printableWidth = width - (leftMargin+rightMargin);
        
        double xUnit = (double) printableWidth / divisions; 
        GregorianCalendar cal = new GregorianCalendar();
        cal.setTimeInMillis(startTime);
        
        FontMetrics fm = g2d.getFontMetrics();
        int spacer = (int) (fm.stringWidth("0000") / xUnit);
        if (spacer == 0) spacer = 1;
        
        for (int r=0; r<divisions; r++) {
            int x = (int) (width - rightMargin - xUnit * (r+1));
            if (r % spacer == 0) {
                int value = cal.get(calendarField);
                if (calendarField == Calendar.MONTH) value++;
                String label = String.valueOf(value);
                int offset = -fm.stringWidth(label) / 2;
                g2d.drawString(label, x+offset, height-bottomMargin+fm.getAscent());
            }
            cal.add(calendarField, -1);
        }
        
        int x = (printableWidth - fm.stringWidth(axis)) / 2;
        g2d.drawString(axis, x, height-fm.getDescent());
    }
    
    
    private static long[] getValues(String statName, int idx, int divisions) {
        
        long[] data = new long[divisions];      
        for (int i=0; i<divisions; i++) {
            int t = (1 + idx + i) % divisions;
            data[i] = SiteMonitor.getStatisticAsLong(statName, String.valueOf(t));
        }
        
        return data;
    }
    
    private static long getMaxValue(long[] data) {
        long max = 0;        
        for (int i=0; i<data.length; i++) {
            if (data[i] > max) max = data[i];
        }
        return max;
    }     
}
