/* @(#) PieChart.java 1.0 2000/05/24 Larry Rodrigues
 * Copyright (c) 2000 Larry Rodrigues
 */
package vis.beans.plotter;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import vis.util.*;
import vis.beans.events.*;
import vis.beans.customizers.*;

import java.awt.print.*;
import java.awt.image.*;
import java.beans.*;
import java.beans.beancontext.*;
import java.io.*;
import com.sun.image.codec.jpeg.*;

/** Draws a pie chart.
  * @version 2.0  24  May 2000
  * @author Larry Rodrigues
  **/
public class PieChart extends Viewport implements Printable{
    static int MAX_INDEX =32;
    static final int ROW_GAP =15;
    static final int COL_GAP = 10;
    static final Point BORDER = new Point(10,15);
    static final int HEADER_HT = 25;
    static final int LEGEND_BOX_SIZE = 5;
    static final int LEGEND_STR_WID  = 8;
    static final int MAX_GRAPHS  = 4;

    protected Point border = new Point(BORDER.x, BORDER.y);
    /** Origin of the graph */
    protected Point orig;
     //Relative coordinates of the graphs origin.
    static Point relOrig = new Point(60,60);

    /** Position of the title. */
    protected Point headerPt = new Point(20,30);

    /** Hieght of the header.*/
    protected int headerHt;

    /** X axis scale factor. */
    protected double xScale= 1.0;
    /** Y axis scale factor.*/
    protected double yScale= 1.0;
    /**
      * X axis range of values i.e.; max - min.
      */
    protected double xRange =1.0;

    /** Y axis range */
    protected double yRange = 1.0;

    /** Size of the legend box. */
    protected int legBoxSize;

    /** Absolute legend position, i.e.; legendPos+incr.
      */
    protected Point legPt;

    /** Object that holds an array of X axis values. The array type can be of any Java primitive
      * type or String.
      */
    protected Object x;

    /**
      * Object that holds an array of Y axis values. The array type can be of any Java primitive
      * type.
      */
    protected Object y;

    /**
      *  Flag that indicates whether to redraw the plot.
      */
    protected boolean enableDraw= false;

    /**
      * Gap between X axis ticks in pixels.
      */
   // protected double tickGap;

    /**
      * Array that holds the values corresponding to the X axis ticks(big).
      */
    //protected int xTickPt[];

    //---------------- Properties-----------------------------------//

     /**  Plot title*/
     protected String titleString = "Plot Title";
    /** Graph color */
    protected Color graphColor = Color.blue;

    /** Font */
    protected Font font = new Font("Helevetica", Font.BOLD, 9);

    /** Font metrics */
    protected FontMetrics  pFont;

    /** Font size */
    protected int fontSize =9;
    /**Width and height of the graph.
     */
    protected int graphWid, graphHt;

    /** Current graph number.
     */
      protected int graphNum =0;
    /** Graph title.
      */
    protected String graphTitle= "Graph Title";

    /** X axis string values. */
    protected String xTextValues[] = {"Jan", "Feb", "Mar", "Apr", "May", "June"};

    /** Legend on or off. The default value is false.
      */
    protected boolean legendOn =false;
    /** Upper left hand corner coordinate of the border. */
    protected int[] borderULHC = {border.x,border.y};

    /** Legend position.*/
    protected int[] legendPos = {0,0};

    /** Legend position increment in X direction. */
    protected int legendPosXIncr =0;

    /** Legend position increment in Y direction.*/
    protected int legendPosYIncr =0;

    /** The gap between two legend strings.
      */
    protected int legendGap =12;

    /** Legend gap increment */
    protected int legendGapIncr =0;
    /** The customizer class */
    protected Class customizerClass = null;
    boolean printOn = false;

    Color color[] = {Color.blue, Color.red, Color.white, Color.green,
                     Color.gray, Color.orange, Color.yellow, Color.magenta,
                     Color.darkGray,Color.pink, Color.lightGray,Color.cyan
                     };

    /** Maximum number of pies */
    protected int maxPies = 12;
    /** Upper left hand corner coordinate of the first legend string */
    protected int legendULHC[] = {0,0};
    /** Pie diameter increment */
    protected int pieDiaIncr= 0;
    /** Pie center X coordinate increment */
    protected int pieCenterXIncr =0;
    /** Pie center Y coordinate increment */
    protected int pieCenterYIncr =0;

    public PieChart(){
       //These are set to null so that they are not displayed
       //on the property sheet.
       graphColor =null;
       enableEvents(AWTEvent.MOUSE_EVENT_MASK|AWTEvent.MOUSE_MOTION_EVENT_MASK);
    }

    public void addNotify(){
       super.addNotify();
       setWidHt(300,200);
       setCustomizerClass(PieChartCustomizer.class);
       if(Beans.isDesignTime()){
          y = new int[] {250, 400, 700, 401, 480, 300};
          x = new String[] {"Jan", "Feb", "Mar", "Apr", "May", "Jun"};
          //x= x1;y= y1;
          drawPlot(x,y);
       }
    }
     /** This is an indexed propety that represents the legend position.
       * The first element holds the x coordinate and second the y coordinate.
       * @param pt an array of size two which holds the upper left
       * hand corner coordinate of the legend.
       **/
     public void setLegendULHC(int[] pt){
        legendULHC = pt;
        legPt = new Point(pt[0], pt[1]);
        draw();
     }
     /**
       * This is a setter method that sets an element of the indexed property.
       * @param xy the x or y coordinate of the legend.
       * @param index the index of the property.
       */
     public void setLegendULHC(int xy, int index){
        legendULHC[index]= xy;
     }
     /**
       * @return the legend position.
       */

     public int[] getLegendULHC(){
        return legendULHC;
     }
     /**
       *  @return an element (x or y coordinate) of the legend position property.
       */
     public int getLegendULHC(int index){
        return legendULHC[index];
     }

     /**
       * Sets the pie diameter increment property.
       * @param incr the increment.
       *
       */
     public void setPieDiaIncr(int incr){
        //System.out.println("dia incr "+incr);
        pieDiaIncr = incr;
        draw();
     }

     /**
       * @return the pie diameter increment.
       */

     public int getPieDiaIncr(){
        return pieDiaIncr;
     }

     /** Sets the pie center x increment property.
       * @param incr the increment.
       */
     public void setPieCenterXIncr(int incr){
        pieCenterXIncr = incr;
        draw();
     }

     /** @return the pie center x increment.
       */

     public int getPieCenterXIncr(){
        return pieCenterXIncr;
     }

     /** Sets the pie center y increment property.
       * @param incr the increment.
       */

     public void setPieCenterYIncr(int incr){
        pieCenterYIncr = incr;
        draw();
     }

     /** @return the pie center y increment.
       */
     public int getPieCenterYIncr(){
        return pieCenterYIncr;
     }

     /** Sets the legend gap increment property.
       * @param incr the increment.
       */
     public void setLegendGapIncr(int incr){
        legendGapIncr = incr;
        draw();
     }

     /** @return the legend gap increment.
       */
     public int getLegendGapIncr(){
        return legendGapIncr;
     }

     /** Sets width and height.
       */
    public void setWidHt(int wd, int hgt){
       width = wd;
       height = hgt;
       if((offScrImage = (BufferedImage) createImage(width, height)) == null){
           super.addNotify();
       }
       setOffScrGc();
    }
     /**
     * Sets the background color.
     * @param clr the color.
     */
     public void setBackground(Color clr){
        super.setBackground(clr);
        draw();
     }
     /** Sets the foreground color.
     * @param clr the color.
     */
     public void setForeground(Color clr){
        super.setForeground(clr);
        draw();
     }

     /** Sets the plot title.
       * @param title the plot title.
       */
     public void setTitleString(String title){
        titleString = title;
        draw();
     }

     /** @return the plot title.
       */
     public String getTitleString(){
        return titleString;
     }


     /**
       * Sets the graph title.
       * @param grTitle the graph title.
       */
     public void setGraphTitle(String grTitle){
        graphTitle = grTitle;
        draw();
     }
     /**
       * @return the graph title.
       */
     public String getGraphTitle(){
        return graphTitle;
     }
     /**
       * Sets the legend visibility on or of.
       * @param onOff the legend is on or off.
       */
     public void setLegendOn(boolean onOff){
        legendOn = onOff;
        draw();
     }
     /**
       * @return the legend on property.
       */
     public boolean getLegendOn(){
        return legendOn;
     }

     /**
       * Sets the font.
       * @param fnt the font.
       */
     public void setFont(Font fnt){
        super.setFont(fnt);
        font = fnt;
        draw();
     }


     /**
       * Sets the graph color.
       * @param col the graph color.
       */
     public void  setGraphColor(Color col){
        graphColor = col;
        draw();
     }

     /**
       * @return the graph color.
       */
     public Color getGraphColor(){
        return graphColor;
     }

     /**
       * Sets the upper left hand corner coordinate(ULHC) of the plot border.
       * This is an indexed property.
       * @param br an array of size two that holds
       * the x and y coordinates of upper left hand corner of the border.
       */
     public void setBorderULHC(int[] br){
        borderULHC = br;
        draw();
     }

     /**
       * @return the upper left hand coordinate of the plot border.
       */
     public int[] getBorderULHC(){
        return borderULHC;
     }

     /**
       * Sets one of the border ULHC coordinate which is an indexed property.
       * @param index the index of the borderULHC index property.
       * @param xy the value corresponding to the index.
       */
     public void setBorderULHC(int index, int xy){
        borderULHC[index]= xy;
        draw();
     }
     /**
       * @return the value at a given index.
       * @param index the index of the borderULHC property. It can be 0 or 1.
       */
     public int getBorderULHC(int index){
        return borderULHC[index];
     }

     /**
       * Sets the legend position which is an indexed property.
       * @param br an array of size two that holds
       * the x and y coordinates of upper left hand corner of the border.
       */
     public void setLegendPos(int[] pt){
        legendPos = pt;
        draw();
     }

     /**
       * Sets the legend position which is an indexed property.
       * @param index the index of the borderULHC index property.
       * @param xy the value corresponding to the index.
       */
     public void setLegendPos(int index, int xy){
        legendPos[index]= xy;
        draw();
     }

     /**
       * @return an array of int which is hold the X and Y coordinates of the
       * legend position.
       */
     public int[] getLegendPos(){
        return legendPos;
     }

     /**
       * @return an element of the legend position index property.
       * @param index the index of the array.
       */
     public int getLegendPos(int index){
        return legendPos[index];
     }

     /**
       * Sets the legend position increment in the X direction.
       * @param incr the increment.
       */
     public void setLegendPosXIncr(int incr){
        legendPosXIncr = incr;
        draw();
     }

     /**
       * @return the legend position x increment.
       */
     public int getLegendPosXIncr(){
        return legendPosXIncr;
     }

     /**
       * Sets the legend position increment in the Y direction.
       * @param incr the increment.
       */
     public void setLegendPosYIncr(int num){
        legendPosYIncr = num;
        draw();
     }

     /**
       * @return the legend position y increment.
       */
     public int getLegendPosYIncr(){
        return legendPosYIncr;
     }

    public Dimension getMinimumSize(){
       return new Dimension(300,200);
    }

     /**
       * @return the customizer class.
       */
    public Class getCustomizerClass(){
       return customizerClass;
    }

     /** Sets the customizer class.
       * @param the customizer class.
       */
    public void setCustomizerClass(Class cl){
       customizerClass = cl;
    }

   /** Creates the plot template. This includes drawing the border and the
    * plot title.
    */
   public void createPlotTemplate(){
      setParams();
      if(offScrGc == null) {
         createOffScrImage();
         setOffScrGc();
       }
       offScrGc.setColor(getBackground());
       offScrGc.fillRect(0,0, width,height);
       offScrGc.setColor(getForeground());
       if(font == null) font = new Font("Helevetia", Font.BOLD,fontSize);
       else font = new Font(font.getName(),Font.BOLD,fontSize);
       offScrGc.setFont(font);
       // Draw the border
       offScrGc.drawRect(border.x,
                         border.y+ headerHt,
                         width- 2*border.x,
                         height-2*border.y-headerHt);
      // Draw the plot title
      offScrGc.setFont(new Font("Helevetica", Font.PLAIN,fontSize+1));
      pFont = offScrGc.getFontMetrics();
      if(titleString == null) titleString = new String("Title");
      int strWid = pFont.stringWidth(titleString);
      offScrGc.drawString(titleString, headerPt.x -(int)(strWid/2), headerPt.y );
   }
     /** Sets the pie plot viewport related parameters.
      */
   protected void setParams(){
      if(width <200) fontSize = 6;
      if((width <=256) && (width > 200)){
         fontSize = 8;
      }
      if((width > 256 )&& (width <512)){
          fontSize = 10;
      }
      else {
         fontSize = 12;
      }
      border.x = (int)(borderULHC[0]);
      border.y = (int)(borderULHC[1]);

      headerHt = (int)(HEADER_HT );
      headerPt.x = width/2;
      headerPt.y = border.y + fontSize;

      if((legendPos[0] ==0)&& (legendPos[1]==0)) {
         legendPos[0] = width-border.x- LEGEND_STR_WID*fontSize;
         legendPos[1] = border.y+ headerHt+ 25;
      }

      legPt = new Point(legendPos[0]+legendPosXIncr, legendPos[1]+legendPosYIncr);
      legBoxSize= (int)(LEGEND_BOX_SIZE);

      graphWid = width-(2*border.x)-10;
      graphHt = height -(2*border.y)-headerHt;
      //ULHC of the arc
      orig = new Point(border.x+10, border.y+headerHt+10);
   }

   /** Adds a legend.
     * @param col the color of the legend box.
     * @param str the legend string.
     */
   public void addLegend(Color col, String str){
      if(( offScrGc == null) || (str == null)) return;
      offScrGc.setColor(col);
      int gap = legendGap+legendGapIncr;
      offScrGc.drawRect(legPt.x, legPt.y+ graphNum*gap, legBoxSize, legBoxSize);
      offScrGc.fillRect(legPt.x, legPt.y+ graphNum*gap, legBoxSize, legBoxSize);
      offScrGc.drawString(str, legPt.x + 2*legBoxSize,
                          legPt.y+ legBoxSize+(graphNum*gap));
      if(titleString == null) titleString = new String("Title");
      int strWid = pFont.stringWidth(titleString);
      offScrGc.drawString(titleString, headerPt.x -(int)(strWid/2), headerPt.y );
   }

   /** Resets the plot viewport.
     */
   public void resetPieChart(){
      graphNum =0;
      enableDraw = false;
   }

   protected void processMouseEvent(MouseEvent e){
      int x = e.getX();
      int y = e.getY();
      switch(e.getID()) {
        case MouseEvent.MOUSE_CLICKED :
           if(SwingUtilities.isRightMouseButton(e)){
              popupMenu(x,y);
           }
           break;
        default: break;
      }
   }



 /**  Plots the pie chart. This is a target method for connecting to the Pie Chart bean
    * through plot events.
    * @param e the PlotEvent.
    */
    public void plotPieChart(PlotEvent e){
       y = e.getYValues();
       x = e.getXTextValues();
       reset();
       drawPlot(x,y);
       String xlab = e.getXLabel();
       String ylab = e.getYLabel();
       if((xlab != null) &&( ylab != null)) setTitleString(ylab+ ":"+xlab);
    }


    public Graphics getOffScrGc(){
       return offScrGc;
    }

    /**  Draws the pie chart.
    * @param  x the array of x values.
    * @param  y the array of y values.
    */
    public void drawPieChart(Object xs, Object y){
       if(xs==null) return;
       //x = xs;
       drawPlot(xs,y);
    }


  /** @param x an array of x values. In this case, these are string values.
    *  @param y an arrya of y values.
    */
  public void drawPlot(Object x, Object y){
     this.x =x;
     this.y =y;
     if((x==null)||(y==null)) {
         System.out.println(" x or y null values");
         return;
     }
     xTextValues = Util.toString(x);
     if(xTextValues == null) return;
     int len=  xTextValues.length;

     if(len == 0){
        System.out.println("xTextValues not set");
        return;
      }
     enableDraw=true;
     draw();
   }

   /** Draws the pie chart on the viewport.
     */
   public void draw() {
      if(!enableDraw) return;
      graphNum =0;
      createPlotTemplate();
      int[] degrees = Util.getDegrees(y);
      int len = degrees.length;
      if(len > maxPies) return;
      int st =0;
      int x = orig.x+pieCenterXIncr;
      int y = orig.y-pieCenterYIncr;
      int wid = graphHt-20+pieDiaIncr;
      int ht = wid;
      if(titleString == null) titleString = new String("Title");
        int strWid = pFont.stringWidth(titleString);
        offScrGc.drawString(titleString, headerPt.x -(int)(strWid/2), headerPt.y );

      for(int i=0; i<degrees.length;i++){
          offScrGc.drawArc(x,y,wid, ht, st,degrees[i]);
          offScrGc.setColor(color[i]);
          offScrGc.fillArc(x,y,wid, ht, st,degrees[i]);
          int pers = (int)((double)(degrees[i]*100)/360);
          String str = new String(xTextValues[i] + " "+Integer.toString(pers)+"%");
          addLegend(color[i], str);
          graphNum++;
          st+=degrees[i];
      }
      repaint();
   }

   public int print(Graphics g, PageFormat f, int pageIndex){
      if(pageIndex >= 1) return Printable.NO_SUCH_PAGE;
      Graphics2D g2d = (Graphics2D) g;
      g2d.translate(f.getImageableX(), f.getImageableY());
      draw();
      paint(g2d);
      return Printable.PAGE_EXISTS;
   }

    /** Resets the plot viewport. */
   protected void reset(){
       setOffScrGc();
       graphNum =0;
   }
   protected void popupMenu(int x, int y){
      final int xx =x;
      final int yy =y;
      JPopupMenu jp = new JPopupMenu("");
      jp.setLightWeightPopupEnabled(true);
      add(jp);
      JMenuItem custitem = new JMenuItem("Customize");
      custitem.addActionListener(
         new ActionListener(){
           public void actionPerformed(ActionEvent e){
              try{
                Class cust = getCustomizerClass();
                if(cust != null) launchCustomizer(cust);
              } catch(Exception e1) {}
           }
         }
      );
      JMenuItem printitem = new JMenuItem("Print");
      printitem.addActionListener(
         new ActionListener(){
           public void actionPerformed(ActionEvent e){
              BeanContextServices bcs = (BeanContextServices)plBeanContext;
              if (bcs.hasService(java.awt.print.PrinterJob.class)) {
                  try{
                    if(invokePrinterService()== null){
                       JOptionPane.showMessageDialog(PieChart.this,
                          "Printer Service is currently unavailable");
                     }
                   } catch(Exception e1) {e1.printStackTrace();}
              } else {
                  printPlot();
             }
          }
        }
      );


      JMenuItem saveitem = new JMenuItem("Save");
      saveitem.addActionListener(
         new ActionListener(){
           public void actionPerformed(ActionEvent e){
              BeanContextServices bcs = (BeanContextServices)plBeanContext;
              if (bcs.hasService(com.vistech.util.SaveAsJPEGService.class)) {
                  try{
                    if(invokeSaveImageService() == null)
                       JOptionPane.showMessageDialog(PieChart.this,
                          "SaveAsJPEG Service is currently unavailable");

                  } catch(Exception e1) {e1.printStackTrace();}
              } else saveAsJPEG(offScrImage);
           }
         }
      );

      jp.add(custitem);
      jp.add(printitem);
      jp.add(saveitem);
      jp.show(this,xx,yy);
   }

   protected void printPlot() {
      try{
         PrinterJob pj = PrinterJob.getPrinterJob();
         PageFormat pm = pj.pageDialog(pj.defaultPage());
         pj.setPrintable(this);
         pj.print();
       }catch(Exception e1){e1.printStackTrace();}
   }

   public static String saveAsJPEG(BufferedImage bi) {
      try {
       String filename = launchFileDialog();
       ByteArrayOutputStream boutstream = new ByteArrayOutputStream();
       JPEGImageEncoder enc = JPEGCodec.createJPEGEncoder(boutstream);
       enc.encode(bi);
       FileOutputStream fimage = new FileOutputStream(new File(filename));
       boutstream.writeTo(fimage);
       fimage.close();
       return filename;
       } catch (Exception e) {
       System.out.println(e);
       return null;
      }
   }
   public static String launchFileDialog(){
      Frame fr = new Frame();
      FileDialog fd = new FileDialog(fr,"", FileDialog.SAVE);
      fd.show();
      String dir = fd.getDirectory();
      String file = fd.getFile();
      if((dir == null) || (file == null)) return null;
      String path = dir+file;
      int lastindex = path.lastIndexOf(".");
      if(lastindex <0) path += ".jpg";
      else {
      String sb = path.substring(lastindex);
      if(sb.equals(".jpg") || sb.equals(".jpeg")) return path;
      else {
        System.out.println(path + " enter extension .jpg or .jpeg");
      }
      }
      return path;
   }
   protected void launchCustomizer(Class cl)
                throws InstantiationException,  IllegalAccessException {
      final Object bean = this;
      Component cmp = (Component)cl.newInstance();
      if(!(cmp instanceof java.beans.Customizer)) return;
      Customizer customizer = ((java.beans.Customizer)cmp);
      customizer.setObject(bean);
      customizer.addPropertyChangeListener(
          new PropertyChangeListener() {
            public void propertyChange(PropertyChangeEvent e) {
               Object newVal = e.getNewValue();
               String propName = e.getPropertyName();
               CustomizerUtil.setProperty(bean, propName, newVal);
           }
          }
      );
      JButton jb = new JButton("OK");
      jb.addActionListener(
        new ActionListener() {
          public void actionPerformed(ActionEvent e ){
              JButton bt = (JButton) e.getSource();
              Object jf = bt.getParent();
              if(jf instanceof JFrame)((JFrame)jf).dispose();
          }
        }

      );
      cmp.setVisible(true);
      Dimension dm = cmp.getPreferredSize();
      int wid =400, ht=300;
      if(dm.width > 0) {
         wid = dm.width+15;
         ht = dm.height+15;
      }
      cmp.repaint();
      JFrame fr = new JFrame();
      fr.getContentPane().add("North",cmp);
      fr.getContentPane().add("South",jb);
      fr.pack();
      Dimension d = cmp.getSize();
      fr.setSize(new Dimension(wid,ht+20));
      fr.show();
      fr.repaint();
   }
}
