package org.concord.datagraph.ui;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Stroke;
import java.awt.geom.GeneralPath;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;

import org.concord.datagraph.engine.DataGraphable;
import org.concord.framework.data.stream.DataStore;
import org.concord.graph.engine.CoordinateSystem;
import org.concord.graph.engine.DefaultGraphable;
import org.concord.graph.engine.Graphable;
import org.concord.graph.engine.GraphableList;

public class DataPointMarker extends DefaultGraphable implements DataAnnotation {
    public static enum MarkerShape { X, TRIANGLE, CIRCLE, TEXT_ABOVE, TEXT_BELOW }
    private DataGraphable graphable;
    private double xValue = 0.0;
    private Color color;
    private MarkerShape shape = MarkerShape.X;
    private String text = "text";
    
    private static Stroke lineStroke = new BasicStroke(2.5f,       // Width
                                                       BasicStroke.CAP_SQUARE,     // End cap
                                                       BasicStroke.JOIN_MITER,     // Join style
                                                       1.0f);
    
    private static GeneralPath xShape = new GeneralPath();
    private static GeneralPath triShape = new GeneralPath();
    static {
        xShape.append(new Line2D.Double(0,0, 10,10), false);
        xShape.append(new Line2D.Double(0,10, 10,0), false);
        
        triShape.append(new Line2D.Double(5,0, 10,10), false);
        triShape.append(new Line2D.Double(10,10, 0,10), false);
        triShape.append(new Line2D.Double(0,10, 5,0), false);
    }
    
    public Graphable getCopy() {
        // TODO Auto-generated method stub
        return null;
    }

    public void draw(Graphics2D gOrig) {
        if (graphable.isVisible() && isVisible()) {
            CoordinateSystem cs = graphArea.getCoordinateSystem();
            Point2D point = interpolatePoint();
            
            Graphics2D g = (Graphics2D) gOrig.create();
            
            if (point != null) {
                Point2D intersection = cs.transformToDisplay(point);
                
                g.translate(intersection.getX()-5, intersection.getY()-5);
                
                g.setColor(color);
                g.setStroke(lineStroke);
                
                switch (shape) {
                case TRIANGLE:
                    g.draw(triShape);
                    break;
                case CIRCLE:
                    g.fillOval(1, 1, 8, 8);
                    break;
                case TEXT_ABOVE:
                    g.drawString(text, -17, 0);
                    break;
                case TEXT_BELOW:
                    g.drawString(text, -17, 20);
                    break;
                default:
                    g.draw(xShape);
                    break;
                }
            }
        }
    }

    // Finds the closest points and either side of the xValue
    // then linearly interpolates and calculates the yValue for the xValue
    private Point2D interpolatePoint() {
        DataStore dataStore = graphable.getDataStore();
        for (int i = 0; i < dataStore.getTotalNumSamples(); i++) {
            Float x = (Float) dataStore.getValueAt(i, 0);
            Float y = (Float) dataStore.getValueAt(i, 1);
            if (x == xValue) {
                // we have an exact match! return it
                return new Point2D.Float(x, y);
            } else if (x >= xValue) {
                // this will be the value to the right of our xValue
                if(i == 0) {
                    // we don't have a left point, so return this point
                    return new Point2D.Float(x, y);
                }
                
                Float leftX = (Float) dataStore.getValueAt(i-1, 0);
                Float leftY = (Float) dataStore.getValueAt(i-1, 1);
                
                // rise over run
                float m = (y - leftY)/(x - leftX);
                
                // point-slope formula: y = m(x - x1) + y1
                double yValue = m * (xValue - x) + y;
                return new Point2D.Double(xValue, yValue);
            } else if (i == (dataStore.getTotalNumSamples() - 1)) {
                // this is the last sample, and we're still on the left of the expected x.
                // just return this point
                return new Point2D.Float(x, y);
            }
        }
        return null;
    }

    public DataGraphable getDataGraphable() {
        return graphable;
    }

    public void setDataGraphable(DataGraphable dataGraphable) {
        graphable = dataGraphable;
    }

    public void setGraphableList(GraphableList gList) {
        // Not supported...
    }
    
    public void setXValue(double x) {
        this.xValue = x;
    }
    
    public double getXValue() {
        return this.xValue;
    }

    public void setColor(Color color) {
        this.color = color;
    }

    public Color getColor() {
        return this.color;
    }

    public void setShape(MarkerShape shape) {
        this.shape = shape;
    }

    public MarkerShape getShape() {
        return shape;
    }

    public void setText(String text) {
        this.text = text;
    }

    public String getText() {
        return text;
    }

}

@@ -80,16 +80,45 @@
 .Color;%0A
+import java.awt.FontMetrics;%0A
 import j
@@ -2665,34 +2665,32 @@
                 
-g.
 drawString(text,
@@ -2688,20 +2688,18 @@
 ing(
+g, 
 text, 
--17, 0
+1
 );%0A 
@@ -2773,26 +2773,24 @@
             
-g.
 drawString(t
@@ -2792,16 +2792,19 @@
 ing(
+g, 
 text, -1
 7, 2
@@ -2803,13 +2803,8 @@
 , -1
-7, 20
 );%0A 
@@ -2927,32 +2927,32 @@
               %7D%0A
-
             %7D%0A  
@@ -2960,24 +2960,621 @@
      %7D%0A    %7D
+%0A    %0A    private void drawString(Graphics2D g, String str, int verticalShift) %7B%0A        // 5,5 in the graphics obj is the point on the graph where this marker corresponds%0A        FontMetrics fontMetrics = g.getFontMetrics();%0A        int width = fontMetrics.stringWidth(str);%0A        float gap = fontMetrics.getLeading()/2.0f;%0A        if (verticalShift %3E 0) %7B%0A            // shift up%0A            g.drawString(text, 5 - (width/2.0f), 5-(fontMetrics.getDescent()+gap));%0A        %7D else %7B%0A            g.drawString(text, 5 - (width/2.0f), Math.round(5 + gap + fontMetrics.getAscent()));%0A        %7D%0A    %7D
 %0A%0A    // Fin

Use font metrics to determine string positioning.