package com.algobase.share.chart;


import java.util.ArrayList;
import java.util.List;
import java.util.Locale;

import android.content.Context;

import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Paint.Align;
import android.graphics.Point;
import android.graphics.Rect;

import android.util.AttributeSet;

import android.view.Display;
import android.view.View;
import android.view.WindowManager;


abstract public class ChartView extends View {

   public static abstract class ChartViewFunction
   { public abstract int numValues();
     public abstract double getX(int i);
     public abstract double getY(int i);
    }

   protected class ChartViewData 
   { double valueX;
     double valueY;
     int    color;

     ChartViewData(double valueX, double valueY) 
     { this.valueX = valueX;
       this.valueY = valueY;
       this.color = 0;
     }

     ChartViewData(double valueX, double valueY, int color) 
     { this.valueX = valueX;
       this.valueY = valueY;
       this.color = color;
     }
   }

   protected class ChartViewSeries 
   {
      String description;

      int color = 0xff0077cc;
      int fill_color = 0;
      float line_width = 3;

      boolean visible = true;
      boolean show_min = false;
      boolean show_max = false;

      boolean show_points = false;

      float bar_width = 0;

      int pathLength = 0;

      ArrayList<ChartViewData> values = new ArrayList<ChartViewData>();
      ChartViewData current_point;

      ChartViewFunction function;

      double min_y;
      double max_y;


      ChartViewSeries(String descr, int clr, int f_clr, float line_w)
      { this.description = descr;
        this.color = clr;
        this.fill_color = f_clr;
        this.line_width = line_w;
        this.current_point = null;
        this.function = null;
        this.min_y = Integer.MAX_VALUE;
        this.max_y = Integer.MIN_VALUE;
      }

      void add(double x, double y, int clr)
      {
        if (y > max_y) max_y = y;
        if (y < min_y) min_y = y;

        // if x already defined (last position): overwrite its value
        int i = values.size() - 1;
        if (i >=0 && values.get(i).valueX == x) 
        { values.get(i).valueY = y;
          return;
         }

        values.add(new ChartViewData(x,y,clr));

        if (current_point != null) 
        { current_point.valueX = x;
          current_point.valueY = y;
         }
      }

      int  size() { return values.size(); }
      void clear() { values.clear(); }
   }


   protected ChartViewVerticalLabels vLabelsView = null;

   private int width;
   private int height;

   protected float chartWidth;
   protected float chartHeight;

   protected float hlabel_font_f = 1.0f;
   protected float vlabel_font_f = 1.0f;

   protected int labelColor = 0xffcccccc;
   protected int gridColor = 0xffb0b0b0;


   protected float marginBot;
   protected float marginBot_f = 0.1f;
   protected float marginBot_px = 0;

   protected float marginTop;

   protected float marginLeft;
   protected float marginLeft_f = 0.0f;

   protected float marginRight;
   protected float marginRight_f = 0.0f;

   protected boolean isBarView = false;


   protected Paint paint = new Paint();

   protected List<ChartViewSeries> chartList = new ArrayList<ChartViewSeries>();


   private List<String> hLabels = new ArrayList<String>();
   private List<String> vLabels = new ArrayList<String>();

   private boolean dynamic_labels = false;

   private int dynamic_vlines = 10;
   private int dynamic_hlines = 8;
   private int dynamic_hlines_step = 1;
   private int dynamic_vlines_step = 1;

   private String title = "";
   private double viewportStart;
   private double viewportSize;

   private Display display;

   private int display_width;
   private int display_height;

   private boolean xDynamicBounds = true;
   private boolean yDynamicBounds = true;

   protected float  yMaxData = Integer.MIN_VALUE;
   protected float  yMinData = Integer.MAX_VALUE;
   protected float  xMaxData = Integer.MIN_VALUE;
   protected float  xMinData = Integer.MAX_VALUE;

   protected float  yMax = 0;
   protected float  yMin = 0;
   protected float  xMax = 0;
   protected float  xMin = 0;

   protected double unit_factor_x = 1;
   protected double unit_factor_y = 1;

   private int backgroundColor = 0;

   public void setUnitFactorX(double f) { unit_factor_x = f; }
   public void setUnitFactorY(double f) { unit_factor_y = f; }

   public void setVerticalLabelsView(ChartViewVerticalLabels view)
   { vLabelsView = view; }
   
   public void setFunction(int s, ChartViewFunction f) 
   { chartList.get(s).function = f; }


   public int getNumValues(int s)   
   { ChartViewSeries series = chartList.get(s);
     return (series.function != null) ? series.function.numValues() :
                                        series.values.size(); 
    }

   protected double getValueX(int s, int i) 
   { ChartViewSeries series = chartList.get(s);
     return (series.function != null) ? series.function.getX(i) :
                                        series.values.get(i).valueX; }

   protected double getValueY(int s, int i) 
   { ChartViewSeries series = chartList.get(s);
     return (series.function != null) ? series.function.getY(i) :
                                        series.values.get(i).valueY; 
    }


   abstract public void drawChart(Canvas canvas,int s);

   private void constructor(Context context)
   {
     WindowManager wm = 
             (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
     display = wm.getDefaultDisplay();
     Point pt = new Point();
     display.getSize(pt);
     display_width = pt.x;
     display_height = pt.y;
    }


   public ChartView(Context context) { 
      super(context);
      constructor(context);
   }

   public ChartView(Context context, AttributeSet attr) { 
      super(context,attr);
      constructor(context);
   }

   public ChartView(Context context, AttributeSet attr, int defStyle) {
      super(context,attr,defStyle);
      constructor(context);
   }


  protected String format(String pattern, Object... args)
   { return String.format(Locale.US,pattern,args); }




   public void clear() {
      chartList.clear();
      hLabels.clear();
      vLabels.clear();
      xDynamicBounds = true;
      yDynamicBounds = true;
      yMaxData = Integer.MIN_VALUE;
      yMinData = Integer.MAX_VALUE;
      xMaxData = Integer.MIN_VALUE;
      xMinData = Integer.MAX_VALUE;
      yMax = 0;
      yMin = 0;
      xMax = 0;
      xMin = 0;
   }


   public int addChart(String name,int line_clr,int fill_clr,float line_w) 
   { chartList.add(new ChartViewSeries(name,line_clr,fill_clr,line_w));
     return chartList.size()-1;
   }

   public int numCharts() { return chartList.size(); }


   public void removeChart(int index) { 
           chartList.remove(index); 
   }

   public void clearAllCharts() {
     for(int i=0; i<chartList.size(); i++) clearChart(i);
   }

   public void clearChart(int index) { 

//if (index >= chartList.size()) return;

      chartList.get(index).clear(); 
      if (xDynamicBounds)
      { xMinData = Integer.MAX_VALUE;
        xMaxData = Integer.MIN_VALUE;
        xMin = 0;
        xMax = 0;
       }
      if (yDynamicBounds)
      { yMaxData = Integer.MIN_VALUE;
        yMinData = Integer.MAX_VALUE;
        yMax = 0;
        yMin = 0;
       }
   }

   public void setVisible(int s, boolean b) {
     chartList.get(s).visible = b;
  }


   public void setFillColor(int s, int clr) {
     chartList.get(s).fill_color = clr;
  }

   public void setLineColor(int s, int clr) {
     chartList.get(s).color = clr;
  }

   public void addChartPoint(int s, double x, double y) { 
     addChartPoint(s,x,y,0);
   }

   public void setCurrentPoint(int s, double x, double y)
   { 
     x *= unit_factor_x;
     y *= unit_factor_y;

     if (xDynamicBounds)
     { if (x > xMaxData) xMaxData = (float)x;
       if (x < xMinData) xMinData = (float)x;
      }

     if (yDynamicBounds)
     { if (y > yMaxData) yMaxData = (float)y;
       if (y < yMinData) yMinData = (float)y;
      }

     ChartViewSeries series = chartList.get(s);
     series.current_point = new ChartViewData(x,y);
   }

   public void addChartPoint(int s, double x, double y, int clr) 
   { 
     x *= unit_factor_x;
     y *= unit_factor_y;

     if (xDynamicBounds)
     { if (x > xMaxData) xMaxData = (float)x;
       if (x < xMinData) xMinData = (float)x;
      }
     if (yDynamicBounds)
     { if (y > yMaxData) yMaxData = (float)y;
       if (y < yMinData) yMinData = (float)y;
      }

     chartList.get(s).add((float)x,(float)y,clr);
   }

   public void addHorizontalLabel(String label) { hLabels.add(label); }
   public void addVerticalLabel(String label)   { vLabels.add(label); }

   public void delHorizontalLabel(int i) { hLabels.remove(i); }
   public void delVerticalLabel(int i)   { vLabels.remove(i); }

   public void clearHorizontalLabels() { hLabels.clear(); }
   public void clearVerticalLabels()   { vLabels.clear(); }

   public void setHorizontalLabel(int i, String label) 
   { if (i >=0 && i < hLabels.size()) hLabels.set(i,label); }

   public void setVerticallLabel(int i, String label) 
   { if (i >=0 && i < vLabels.size()) vLabels.set(i,label); }

   public void setShowMax(int s, boolean b) { chartList.get(s).show_max = b; }
   public void setShowMin(int s, boolean b) { chartList.get(s).show_min = b; }

   public void setShowPoints(int s, boolean b) { chartList.get(s).show_points = b; }

   public void setBarWidth(int s, float w) { chartList.get(s).bar_width = w; }

   public void setDynamicXBounds(boolean b) { xDynamicBounds = b; }
   public void setDynamicYBounds(boolean b) { yDynamicBounds = b; }

   public void setXBounds(double xmin, double xmax) {
           xMinData = (float)xmin;
           xMaxData = (float)xmax;
           xDynamicBounds = false;
   }

   public void setYBounds(double ymin, double ymax) {
           yMinData = (float)ymin;
           yMaxData = (float)ymax;
           yDynamicBounds = false;
   }


   public void setTitle(String t)      { title = t; }

   public void setMarginLeft(float f)  { marginLeft_f = f; }
   public void setMarginRight(float f) { marginRight_f = f; }

   public void setMarginBot(float f)   { marginBot_f = f; }
   public void setMarginBotPix(int px) { marginBot_px = px; }

   public void setBackgroundColor(int clr)  { backgroundColor = clr; }
   public void setGridColor(int clr)  { gridColor = clr; }
   public void setLabelColor(int clr) { labelColor = clr; }

   public void setLabelFontFactors(float f1, float f2)  
   { hlabel_font_f  = f1; 
     vlabel_font_f = f2;
    }


   public void setDynamicLabels(boolean b) { dynamic_labels = b; }

   public void setDynamicHLines(int n) { dynamic_hlines = n; }

   public void setDynamicHLines(int n, int step) 
   { dynamic_hlines = n; 
     dynamic_hlines_step = step;
   }

   public void setDynamicVLines(int n) { dynamic_vlines = n; }

   public void setDynamicVLines(int n, int step) 
   { dynamic_vlines = n; 
     dynamic_vlines_step = step;
   }



   public void setViewPort(double start, double size) {
           viewportStart = start;
           viewportSize = size;
   }


   private void drawGrid(Canvas canvas) 
   {
     paint.setAntiAlias(true);

     paint.setStrokeWidth(1);
     paint.setStyle(Paint.Style.FILL);

     if (dynamic_labels)
     {  
        hLabels.clear();
        vLabels.clear();

       if (xMaxData < xMinData)
       { hLabels.add(" ");
         for(int i=1; i< dynamic_vlines; i++)  
              hLabels.add(format("%d",i*dynamic_vlines_step));
        }
       else
       { xMin = xMinData;
         xMax = xMaxData;
         double dx = xMaxData/(dynamic_vlines-1);

         double d = (int)(100*xMaxData);

         int p = 1;
         while (d > 10)
         { d = d/10;
           p *= 10;
          }

         if (d < 2.5) d = 1;
         else
         if (d < 5.0) d = 2.5;
         else
         if (d < 10.0) d = 5.0;

         dx = p*d;

         int num_vlines = (int)(1000*xMaxData/dx);

         if (num_vlines > 15)
         { dx *= 2;
           num_vlines /= 2;
          }

         num_vlines++;

         hLabels.add("");

         for(int i=1; i<num_vlines; i++)  
         { if ((i % 2) == 1) 
           { hLabels.add("");
             continue;
            }
           double m = i*dx;
           if (m < 100)
             hLabels.add(format("%.0f",m));
           else
             if ((int)m % 1000 == 0)
               hLabels.add(format("%.0f",m/1000));
             else
               if ((int)m % 100 == 0)
                 hLabels.add(format("%.1f",m/1000));
               else
                 hLabels.add(format("%.2f",m/1000));
          }

       //hLabels.add("km");
        }

       if (yMaxData < yMinData)
         for(int i=dynamic_hlines-1; i>=0; i--) 
              vLabels.add(format("%d",i*dynamic_hlines_step));
       else
       { double step = (yMaxData-yMinData)/dynamic_hlines;

         int d = 1;
         if (step > 250) d = 500;
         else if (step > 200) d = 250;
         else if (step > 100) d = 200;
         else if (step > 50)  d = 100;
         else if (step > 25) d = 50;
         else if (step > 20) d = 25;
         else if (step > 10) d = 20;
         else if (step > 5) d = 10;
         else if (step > 2) d = 5;
         else if (step > 1) d = 2;
         else step = 1;

/*
         yMin = 0;
         while (yMin >= yMinData) yMin -= d; 
         while (yMin <  yMinData) yMin += d; 
         yMin -= d;
*/
         yMin = d*(int)(yMinData/d);
         if (yDynamicBounds && yMin > 0 && yMinData-yMin < 0.25f*d) yMin -= d;


         yMax = yMin;
         while (yMax <= yMaxData) yMax += d; 
         if (yDynamicBounds && yMax-yMaxData < 0.25f*d) yMax += d;


         int num = (int)((yMax - yMin)/d);

         while (yDynamicBounds && num < dynamic_hlines)
         { if (yMin >= d) { yMin -= d; num++; }
           if (num == dynamic_hlines) break;
           yMax += d;
           num++;
          }

         for(int i=num; i>=0; i--) 
         { double y = yMin+i*d;
           if (y >= 0) 
              vLabels.add(format("%.0f",y));
           else
              vLabels.add(" ");
          }
       }
     }
/*
     else
     { // no dynamic labels
       xMax = xMaxData;
       xMin = xMinData;
       yMax = yMaxData;
       yMin = yMinData;
      }
*/


     //paint.setTextSize(0.05f*height);

     float text_sz = 0.04f *display_width;


     if (vLabels.size() > 0) 
     { // horizontal lines and vertical labels
       paint.setTextAlign(Align.RIGHT);
       paint.setTextSize(vlabel_font_f*text_sz);

       Rect rect = new Rect();
       paint.getTextBounds(vLabels.get(0),0,vLabels.get(0).length(), rect);
       int th = rect.top;
       int tw = rect.right;

       if (vLabelsView != null) 
       { vLabelsView.clear();
         vLabelsView.setTextSize(vlabel_font_f*text_sz);
        }

       float yskip = (float)(chartHeight)/(vLabels.size() - 1);

       for (int i = 0; i < vLabels.size(); i++) 
       { String label = vLabels.get(i);

         float y = marginTop + i*yskip;

         float ylab = y - th/2;

         if (vLabelsView == null)
         { paint.setColor(labelColor);
           paint.setAntiAlias(true);
           canvas.drawText(label, 0.8f*marginLeft, ylab, paint);
          }
         else vLabelsView.addLabel(label,ylab+2);

         paint.setColor(gridColor);
         paint.setAntiAlias(false);
         canvas.drawLine(marginLeft, y, marginLeft+chartWidth, y, paint);
        }

       if (vLabelsView != null) vLabelsView.invalidate();
      }


     if (hLabels.size() > 0)
     { // vertical lines and horizontal labels
       int numLabels = hLabels.size();
       int numLines = numLabels;

       if (isBarView) numLines++;

       float dx = chartWidth/(numLines-1);

       float d = 0;

       if (isBarView) d = (int)(chartWidth/200.0f + dx/2);

       paint.setTextAlign(Align.CENTER);

       paint.setTextSize(hlabel_font_f*text_sz);

       for (int i = 0; i < numLines; i++) 
       {
         float x = marginLeft + i * dx;

         paint.setColor(gridColor);
         paint.setAntiAlias(false);
         canvas.drawLine(x, marginTop, x, height-marginBot, paint);

         if (i < numLabels)
         { paint.setColor(labelColor);
           paint.setAntiAlias(true);
           canvas.drawText(hLabels.get(i), x+d, height-0.3f*marginBot, paint);
          }
       }
     }

  }


   @Override
   protected void onMeasure(int widthSpec, int heightSpec)
   { super.onMeasure(widthSpec, heightSpec);
     int parentWidth = MeasureSpec.getSize(widthSpec);
     int parentHeight = MeasureSpec.getSize(heightSpec);
     int w = getLayoutParams().width;
     int h = getLayoutParams().height;
     if (w <= 0) w = parentWidth;
     if (h <= 0) h = parentHeight;
     setMeasuredDimension(w,h);
    }

/*
  @Override
  protected void onMeasure(int widthSpec, int heightSpec)
  { int measuredWidth = MeasureSpec.getSize(widthSpec);
    int measuredHeight = MeasureSpec.getSize(heightSpec);
    setMeasuredDimension( measuredWidth, measuredHeight);
   }
*/

  @Override
  protected void onSizeChanged(int w, int h, int old_w, int old_h)
  { width = w;
    height = h;
    super.onSizeChanged(w,h,old_w,old_h);
  }

   @Override
   protected void onDraw(Canvas canvas) 
   {
     xMax = xMaxData;
     xMin = xMinData;
     yMax = yMaxData;
     yMin = yMinData;

     //height = getHeight();
     //width = getWidth();

     marginLeft = marginLeft_f * height;
     marginRight = marginRight_f * height;

     if (marginBot_px != 0)
       marginBot = marginBot_px;
     else
       marginBot = marginBot_f * height;

     marginTop = 0.3f * marginBot;

     chartHeight = height - marginTop - marginBot;
     chartWidth = width - marginLeft - marginRight;


     if (backgroundColor != 0) 
     { paint.setStyle(Paint.Style.FILL);
       paint.setColor(backgroundColor);
       canvas.drawRect(marginLeft,marginTop,marginLeft+chartWidth,
                                            marginTop+chartHeight,paint);
      }

     paint.setStrokeWidth(1);
     paint.setStyle(Paint.Style.STROKE);
     paint.setStrokeCap(Paint.Cap.ROUND);
     paint.setColor(gridColor);
     paint.setAntiAlias(true);
     canvas.drawRect(marginLeft,marginTop,marginLeft+chartWidth,
                                          marginTop+chartHeight,paint);

     if ((hLabels.size() > 0 && vLabels.size() > 0) || dynamic_labels) 
       drawGrid(canvas );

     // draw title

/*
     String s = title;
     paint.setStyle(Paint.Style.FILL);
     paint.setTextAlign(Align.CENTER);
     paint.setColor(Color.WHITE);
     //paint.setTextSize(0.06f*height);
     paint.setTextSize(0.05f*display_width);
     float x = (display_width - 50)/2;
     canvas.drawText(s, x, 0.7f*marginBot, paint);
*/

     // draw charts

     paint.setAntiAlias(true);

     for (int i=chartList.size()-1; i>=0; i--) 
     { if (!chartList.get(i).visible) continue;
       drawChart(canvas,i);
      }
   }


}


