package page.image;

import java.awt.Color;
import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.MathContext;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

import org.wikiwebserver.core.WareHouse;
import org.wikiwebserver.core.WikiMap;
import org.wikiwebserver.handler.http.FormData;
import org.wikiwebserver.handler.http.HTTPHandler;
import org.wikiwebserver.handler.http.interfaces.*;

public class LineGraph implements HTTPResponder {
    
    private static Color[] colors = {
        Color.blue,
        Color.cyan,
        Color.green,
        Color.magenta,
        Color.orange,
        Color.pink,
        Color.red,
        Color.yellow,
    };
	
    public Object respond(HTTPHandler conn) throws IOException {
        
        int width = 700;
        int height = 400;
        boolean reciprocal = false;
        String lineGraphData = "Time,Data 1,Data 2,Data 3\n" +
                               "99,  1,  32, 1\n" + 
                               "100, 2,  16, 4\n" + 
                               "101, 4,  8,  8\n" + 
                               "102, 8,  4,  16\n" + 
                               "103, 16, 2,  32\n";
        
        // Obtain requested counter properties
        FormData formData = conn.getRequest().getFormData();
        if (formData != null) {
            String heightString = formData.getFirst("h");        
            if (heightString != null) height = Integer.parseInt(heightString);
            String widthString = formData.getFirst("w");        
            if (widthString != null) width = Integer.parseInt(widthString);
            String funcString = formData.getFirst("f");
            if (funcString != null) {
                if (funcString.contains("reciprocal")) reciprocal = true;
            }
            
            // Parse data
            String dataKey = formData.getFirst("d");
            if (dataKey != null) {
                String lgd = (String) getData().get(dataKey);
                if (lgd != null) lineGraphData = lgd;
            }
        }
        
        // Parse the string representation of line graph data
        String[] rows = lineGraphData.split("\n");
        
        double xAxis[] = new double[rows.length-1];
        String xAxisLabel = null;
        
        double data[][] = new double[rows.length-1][];
        LegendItem[] legend = null;
        
        for (int r=0; r<rows.length; r++) {
            String[] cols = rows[r].split(",");
            if (r == 0) {
                legend = new LegendItem[cols.length-1];
                for (int c=0; c<cols.length; c++) {
                    if (c == 0) xAxisLabel = cols[0];
                    else {
                        legend[c-1] = new LegendItem();
                        legend[c-1].label = cols[c];
                        legend[c-1].colour = colors[(c-1)%colors.length];
                    }
                }
            } else {             
                data[r-1] = new double[cols.length-1];
                for (int c=0; c<cols.length; c++) {
                    if (c == 0) xAxis[r-1] = Double.parseDouble(cols[0]);
                    else {
                        data[r-1][c-1] = Double.parseDouble(cols[c]);
                        if (r == rows.length-1) {
                            legend[c-1].value = data[r-1][c-1];
                        }                        
                    }
                }
            }
        }        

        
        // Sort the key, ready for rendering
        List<LegendItem> sortedLegend = new ArrayList<LegendItem>();
        sortedLegend.addAll(Arrays.asList(legend));
        Collections.sort(sortedLegend);
        if (!reciprocal) {
            Collections.reverse(sortedLegend);
        }
        
        
        // Figure out the axis extremes
        double minX = xAxis[0];
        double maxX = xAxis[xAxis.length-1];
        
        double minY = Double.MAX_VALUE;
        double maxY = Double.MIN_VALUE;      
        for (int r=0; r<data.length; r++) {
            for (int c=0; c<data[r].length; c++) {
                if (data[r][c] < minY) minY = data[r][c];
                if (data[r][c] > maxY) maxY = data[r][c];
            }
        }
        double midY = (minY + maxY)/2;
        
        String yAxisTopLabel = getSignificantFigures(maxY, 3);
        String yAxisMiddleLabel = getSignificantFigures(midY, 3);
        String yAxisBottomLabel = getSignificantFigures(minY, 3);
        
        // Useful for displaying decimal odds for BetFair
        if (reciprocal) {
            String tmp = yAxisTopLabel;
            yAxisTopLabel = yAxisBottomLabel;
            yAxisBottomLabel = tmp;
            minY = 0; maxY = 0;
            for (int r=0; r<data.length; r++) {
                for (int c=0; c<data[r].length; c++) {
                    data[r][c] = (double)1/data[r][c];
                    if (data[r][c] < minY) minY = data[r][c];
                    if (data[r][c] > maxY) maxY = data[r][c];
                }
            }
            midY = (minY + maxY)/2;
            yAxisMiddleLabel = getSignificantFigures(1/midY, 3);
        }        
        
        
        
        int leftMargin = 10;
        int rightMargin = 10;
        int topMargin = 10;
        int bottomMargin = 20;
 
        
        BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        Graphics2D g2d = image.createGraphics();
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);        
        
        g2d.setColor(Color.white);
        g2d.fillRect(0, 0, width, height);

        FontMetrics fm = g2d.getFontMetrics();   
        
        // Format y axis labels
        Rectangle2D yAxisTopLabelBounds = fm.getStringBounds(yAxisTopLabel, g2d);
        Rectangle2D yAxisMiddleLabelBounds = fm.getStringBounds(yAxisMiddleLabel, g2d);
        Rectangle2D yAxisBottomLabelBounds = fm.getStringBounds(yAxisBottomLabel, g2d);

        // Update left margin to fit axis labels
        if (yAxisTopLabelBounds.getWidth() > leftMargin) {
            leftMargin = (int) (yAxisTopLabelBounds.getWidth() + 6);
        }
        if (yAxisMiddleLabelBounds.getWidth() > leftMargin) {
            leftMargin = (int) (yAxisMiddleLabelBounds.getWidth() + 6);
        }        
        if (yAxisBottomLabelBounds.getWidth() > leftMargin) {
            leftMargin = (int) (yAxisBottomLabelBounds.getWidth() + 6);
        }                
        
        // Calculate legend width
        int legendWidth = rightMargin;
        for (LegendItem item : sortedLegend) {
            int w = fm.stringWidth(item.label);
            if (w > legendWidth) legendWidth = w;
        }
        
        int printableWidth = width - (leftMargin+legendWidth+rightMargin);
        int printableHeight = height - (topMargin+bottomMargin);
        
        double xUnit = (double) printableWidth / (maxX - minX);
        double yUnit = (double) printableHeight / (maxY - minY);        
        
        // Print legend
        int top = topMargin;
        for (LegendItem item : sortedLegend) {
            g2d.setColor(item.colour);
            g2d.drawString(item.label, leftMargin+printableWidth+(rightMargin/2), top);
            top += 20;
        }
       
        int shift = fm.getAscent()/2;
        
        g2d.setColor(Color.black);
        
        // Draw the labels
        g2d.drawString(yAxisTopLabel, (int)(leftMargin-yAxisTopLabelBounds.getWidth()-3), topMargin+shift);
        g2d.drawString(yAxisMiddleLabel, (int)(leftMargin-yAxisMiddleLabelBounds.getWidth()-3), topMargin+(printableHeight/2)+shift);
        g2d.drawString(yAxisBottomLabel, (int)(leftMargin-yAxisBottomLabelBounds.getWidth()-3), topMargin+printableHeight+shift);
        
        // Draw the axis
        g2d.drawLine(leftMargin, topMargin, leftMargin, topMargin+printableHeight);        
        g2d.drawLine(leftMargin, topMargin+printableHeight, leftMargin+printableWidth, topMargin+printableHeight);
                
        Rectangle2D axisLabelBounds = fm.getStringBounds(xAxisLabel, g2d);
        int xAxisLabelX = (int) (leftMargin + (printableWidth/2-axisLabelBounds.getWidth()/2));
        int xAxisLabelY = (int) (topMargin+printableHeight+((bottomMargin-axisLabelBounds.getHeight())/2)+fm.getAscent());
        g2d.drawString(xAxisLabel, xAxisLabelX, xAxisLabelY);
        
        int[] startX = new int[data[0].length];
        int[] startY = new int[startX.length];
        
        // A start point is required for drawing a line
        if (data.length > 1) {
            int x = (int) (leftMargin + ((xAxis[0] - minX) * xUnit));
            for (int c=0; c<data[1].length; c++) {
                int y = (int) (height - bottomMargin - ((data[0][c] - minY) * yUnit));    
                startX[c] = x; startY[c] = y; 
            }
        }
        
        g2d.setColor(Color.red);
        for (int r=1; r<data.length; r++) {
            int x = (int) (leftMargin + ((xAxis[r] - minX) * xUnit));
            for (int c=0; c<data[r].length; c++) {
                int y = (int) (height - bottomMargin - ((data[r][c] - minY) * yUnit));
                g2d.setColor(colors[c%colors.length]);
                g2d.drawLine(startX[c], startY[c], x, y);
                startX[c] = x; startY[c] = y;     
            }
        }
  
        return image;
    }
    
    public WikiMap getData() {     
        WikiMap data = WareHouse.getWikiMap("LineGraphData");
        if (data == null) data = WareHouse.initWikiMap("LineGraphData");
        
        return data;
    }      
    
    public static String getSignificantFigures(double value, int sigFigs) {
        MathContext mc = new MathContext(sigFigs, RoundingMode.HALF_UP);
        BigDecimal bigDecimal = new BigDecimal(value, mc);
        return bigDecimal.toPlainString();
    }    
    
    class LegendItem implements Comparable<LegendItem> {
        String label;
        Color colour;
        double value;
        
        public int compareTo(LegendItem item) {
            int diff = Double.compare(value, item.value);
            if (diff == 0) diff = 1;
            return diff;
        }
    }
}
