// Author: Matt Oefinger - Oefinger Enterprises, Inc.: www.oefingerenterprises.com
//                       - Kairos Companies, LLC: www.kairoscompanies.com

// COPYRIGHT NOTICE: This software is Copyright of Massachusetts Institute of Technology 
// and licensed exclusively to Oefinger Enterprises, Inc.

// ********* All rights reserved. **************

import java.net.*;
import java.io.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;
import java.util.*;
import java.lang.Object.*;

//////////////////////////////////////////////////////////////////////////////////////////
// CUSTOMCANVAS                      CUSTOMCANVAS                          CUSTOMCANVAS //
//////////////////////////////////////////////////////////////////////////////////////////
abstract public class CustomCanvas extends JComponent implements MouseInputListener
    {

    // general global variables
    protected String SERVER_URL;               // init by Init_Server routine, called by hermes_applet.class
    protected JPopupMenu popup=null;           // right-click menu item 
    protected Point point = null;              // most recently clicked point
    protected Point point2 = null;             // current cursor point
    protected Point point3 = null;             // most recent mouse-down point - used in mousedragged event
    protected Point point4 = null;             // current drag point - used in mousedragged event
    protected JLabel glob_label;               // the label at the top of the panel
    protected Vector txtlabels = new Vector(); // list textlabels with x,y locations for rendering
    protected Vector partners = new Vector();  // list of partners is updated per UpdatePartners(), expanded with addPartner 
    protected Vector children = new Vector();  // updated per UpdateChildren(), expanded with addChild
    protected Vector parents = new Vector();   // updated per UpdateParent(), expanded with addParent
    protected JLabel dynamicLabel = null;      // set non-null by addDynamicLabel

    // graphics related global variables
    protected boolean firstDraw = true;        // set to false after first rendering - allows for dynamic screen-size based variables to be created
    protected int grid_x_threshold=0;          // set by setxedge function
    protected int grid_y_threshold=0;          // set by setyedge function
    protected int leftx;                       // init by paintComponent 
    protected int rightx;                      // init by paintComponent
    protected int width;                       // init by paintComponent
    protected int top;                         // init by paintComponent
    protected int bot;                         // init by paintComponent
    protected int gridx=0;                     // pixels/division in x grid spacing - DUMMY INITIALIZATION NECESSARY HERE
    protected int gridy=0;                     // pixels/division in y grid spacing - DUMMY INITIALIZATION NECESSARY HERE
    protected float upperx = -1;               // above upperx UNITS (i.e. accounts for unitsperpixel) is shaded yellow -
    protected float lowerx = -1;               // below lowerx UNITS (i.e. accounts for unitsperpixel) is shaded yellow
    protected float uppery = -1;               // above uppery UNITS (i.e. accounts for unitsperpixel) is shaded yellow
    protected float lowery = -1;               // below lowery UNITS (i.e. accounts for unitsperpixel) is shaded yellow
    protected boolean setupperx=false;         // becomes true under appropriate right-click selection
    protected boolean setuppery=false;         // becomes true under appropriate right-click selection
    protected boolean setlowerx=false;         // becomes true under appropriate right-click selection
    protected boolean setlowery=false;         // becomes true under appropriate right-click selection
    protected boolean autoScaleY = false;      // set to true with autoscale call to polymorphic setLabelParams
    
    // variables for drawing x & y labels
    protected int fudge_y=0;
    protected int unitsperpixel_y_num=-1;
    protected int unitsperpixel_y_den=-1;
    protected String ytype="";
    protected String ylabel="";
    protected int fudge_x=0;
    protected int unitsperpixel_x_num=-1;
    protected int unitsperpixel_x_den=-1;
    protected String xtype="";
    protected String xlabel="";

    // state variables for child & parent
    protected int INDEX_PARENT = -1;
    protected String PARENT_DISPLAY_MODE = "NORMAL";    // may be "ZOOM" or "NORMAL" - set through accessor function setDisplayMode, this reflects state of PARENT, not THIS canvas!
    protected int PARENT_ZOOM_FACTOR;

    // data to be rendered on canvas
    protected Vector signal = new Vector();          


    /////////////////////////////////////////////////////////////////////////////////////////////////////////////
    // PUBLIC ACCESSORS/MODIFIERS 
    /////////////////////////////////////////////////////////////////////////////////////////////////////////////

    public void addPopupMenu()
      {
      this.popup = new JPopupMenu();
      }

    public void addSubMenutoPopup(JMenu submenu)
      {
      popup.add(submenu);
      }

    public JPopupMenu getPopupMenu()
      {
      return this.popup;
      }

    public void addDynamicLabel(JLabel labl)
      {
      this.dynamicLabel = labl;
      }

    public ButtonGroup addGroup()
      {
      ButtonGroup group = new ButtonGroup();
      return group;
      }

    public abstract void addRadio(ButtonGroup group, String name, String status);
    public abstract void addRadiotoSubMenu(JMenu submenu, ButtonGroup group, String name, String status);
    public abstract void addPopupMenuItem(String menuitem);
    public abstract void load(String URL);


    public void addPopupSeparator()
      {
      popup.addSeparator();
      }    

    public void addSubMenu(JMenu submenu)
      {
      popup.add(submenu);
      }

    public void setxedge(int x)
      {
	  this.grid_x_threshold = x;
      }

    public void setyedge(int y)
      {
      this.grid_y_threshold = y;
      }

    // polymorphic function setLabelParams
    public void setLabelParams(int fudge_y, int upp_y_num, int upp_y_den, String ytype, int fudge_x, int upp_x_num, int upp_x_den, String xtype, String ylabel, String xlabel)
      {
      this.fudge_y = fudge_y;
      this.fudge_x = fudge_x;
      this.unitsperpixel_x_num = upp_x_num;
      this.unitsperpixel_y_num = upp_y_num;
      this.unitsperpixel_x_den = upp_x_den;
      this.unitsperpixel_y_den = upp_y_den;
      this.ytype = ytype;
      this.xtype = xtype;
      this.xlabel = xlabel;
      this.ylabel = ylabel;
      }

    public void setLabelParams(int fudge_y, String AutoScaleOption, String ytype, int fudge_x, int upp_x_num, int upp_x_den, String xtype, String ylabel, String xlabel)
      {
      this.fudge_y = fudge_y;
      this.fudge_x = fudge_x;
      this. unitsperpixel_x_num = upp_x_num;
      this. unitsperpixel_x_den = upp_x_den;
      this.ytype = ytype;
      this.xtype = xtype;
      this.xlabel = xlabel;
      this.ylabel = ylabel;
 
      if(AutoScaleOption == "AUTOYSCALE")
	 {this.autoScaleY = true;}
      else
	 {System.out.println("ERROR: Unexpected String value AutoScaleOption in function setLabelParams");}

      }

    public void addPartner(CustomCanvas partner)
      {
      partners.addElement(partner);
      }

    public void addChild(CustomCanvas child)
      {
      children.addElement(child);
      child.Set_INDEX_PARENT(this.grid_x_threshold);    // tell the child the parent's index
      }

    public void addParent(CustomCanvas parent)
      {
      parents.addElement(parent);
      }

    final protected void Set_INDEX_PARENT(int index)
      {
      this.INDEX_PARENT = index;
      }

    ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////



    ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    // PROTECTED FUNCTIONS
    ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

    protected abstract void ParentUpdate(Object param1);
    protected abstract void ParentUpdate(Object param1, Object param2);
    protected abstract void ChildUpdate(Object param1);
    protected abstract void ChildUpdate(Object index1, Object index2);

    protected abstract int ConvertYUnitstoPix(float yval);
    protected abstract int ConvertXUnitstoPix(float xval);
    protected abstract float ConvertYPixtoUnits(int ypixval);
    protected abstract float ConvertXPixtoUnits(int xpixval);

    protected abstract void drawlimits(Graphics2D g);
    protected abstract void init();
    protected abstract void drawSignal(Graphics2D g);     

    public void setParentDisplayMode(String Mode, int PARENT_ZOOM_FACTOR)
      {
      this.PARENT_DISPLAY_MODE = Mode;
      this.PARENT_ZOOM_FACTOR = PARENT_ZOOM_FACTOR;
      }

    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////


    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    // PRIVATE (LOCAL) FUNCTIONS
    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

    // set labels based on canvas size
    protected void setTextLabels(Graphics2D g, int render_offset)
      {
	  int i = (this.grid_x_threshold) + render_offset;
      int spacing;

      // set spacing of x-axis labels 
      if(this.gridx <= 75) 
        {
        int tmp = (int) 75/this.gridx;
        spacing = tmp*this.gridx;
        }
      else
        {spacing = this.gridx;}

      FontMetrics fm = g.getFontMetrics();      //get the font metrics (used for string sizes)
      int stringwidth = fm.stringWidth(this.xlabel);
      int stringheight = fm.getHeight();
      int buffer = 3;

      // x-axis labels
      while((i-render_offset) < (this.rightx-stringwidth-25))
        {
        if(this.xtype == "FLOAT")
 	  {
	      float lablval = (float)(((float)i-(float)(this.grid_x_threshold))*unitsperpixel_x_num/unitsperpixel_x_den);
	      String labl = Float.toString(lablval);
	      this.addText(labl,i-render_offset,this.bot-(this.grid_y_threshold-stringheight)-buffer, Color.BLACK, "CENTER","NO_ROTATE");
          }
        if(this.xtype == "INT")
          {
	      int lablval = (int)(((float)i-(float)(this.grid_x_threshold))*(float)unitsperpixel_x_num/unitsperpixel_x_den);
          String labl = Integer.toString(lablval);
          this.addText(labl,i-render_offset,this.bot-(this.grid_y_threshold-stringheight)-buffer, Color.BLACK, "CENTER","NO_ROTATE");
          }
        i += spacing;
        }

      // y-axis labels
      int j=0;
      i = this.bot-this.grid_y_threshold+this.fudge_y;
      spacing = this.gridy*4;
      while(i > this.top)
        {
	String labl;
        float lablval = (float)((float)j*(float)unitsperpixel_y_num/unitsperpixel_y_den);
        int lablval_int;
        int first_decimal;
        if(lablval > 1000000) 
          {
	  lablval_int = (int)(lablval)/1000000;
          first_decimal = (int)((lablval-1000000*lablval_int)/100000);          
          labl = new String(lablval_int + "." + first_decimal + "M");
          }
        else if(lablval > 1000)
	  {
	  lablval_int = (int)(lablval)/1000;
          first_decimal = (int)((lablval-1000*lablval_int)/100);          
          labl = new String(lablval_int + "." + first_decimal + "k");
	  }
        else
	  {
	  if(lablval == (float)0) {labl = new String((int)lablval + "");}
          else {labl = new String((int)lablval + "");}
	  }
        this.addText(labl, (this.grid_x_threshold)-this.fudge_x, i, Color.BLACK, "CENTER","NO_ROTATE");
        i -= spacing;
        j += spacing;
        }

      // units labels
      this.addText(this.ylabel, this.leftx+stringheight, (this.bot-this.top)/2, Color.BLUE, "CENTER","ROTATE");
      this.addText(this.xlabel, this.rightx-10, this.bot-(this.grid_y_threshold-12) , Color.BLUE, "LEFT","NO_ROTATE");    
      }


    // automatically sets y-axes and scaling based on maximum value in signal vector
    protected void set_y_scale()
      {
      Enumeration enum1 = signal.elements();
      Integer max = new Integer(0);  
      while(enum1.hasMoreElements()) 
        {
        Integer current = (Integer) enum1.nextElement();
        if(current.intValue() > max.intValue()) 
          {max = current;}
        }

      int mag = 1;
      while( (float)max.intValue()/(float)mag > 1 )
        {mag *= 10;}
      mag /= 10;
      while( (float)max.intValue()/(float)mag > 1 )
        {mag *= 5;}
      mag /= 5;
      while( (float)max.intValue()/(float)mag > 1 )
        {mag *= 2;} 
      
      this. unitsperpixel_y_num = mag;
      this. unitsperpixel_y_den = (this.bot-this.grid_y_threshold)-this.top;
      }


  // Draws text labels contained in txtlabels vector
  protected void drawTextLabels(Graphics2D g)
    {
    Enumeration enum1 = txtlabels.elements();

    // save the current color to restore after drawing labels
    Color tmp = g.getColor();
    g.setColor(Color.BLUE);

    // go through each label in txtlabels vector
    while(enum1.hasMoreElements()) {
       TxtLabel currentlabel = (TxtLabel) enum1.nextElement();
       g.setColor(currentlabel.labelcolor);
       FontMetrics fmetrics = g.getFontMetrics();
       int len = fmetrics.stringWidth(currentlabel.txt);

       // non-rotated text label
       if (currentlabel.rotation == "NO_ROTATE")
	 {
         int x = currentlabel.xval;

         if (currentlabel.alignmentType == "RIGHT")
           {
	   x += len;				
           g.drawString(currentlabel.txt, x, currentlabel.yval);
           }
         if (currentlabel.alignmentType == "CENTER")
	   {
	   x -= len/2;				
           g.drawString(currentlabel.txt, x, currentlabel.yval);
	   }
         if (currentlabel.alignmentType == "LEFT")
	   {
           x -= len;
           g.drawString(currentlabel.txt, x, currentlabel.yval);
           }
         g.drawString(currentlabel.txt, x, currentlabel.yval);
         }

       // rotated text label
       if (currentlabel.rotation == "ROTATE")
	 {
	 // after 90 deg rotation, x & y swapped 
	 int x = -currentlabel.yval;   

	 if(currentlabel.alignmentType == "RIGHT")
	    {
	    x += len;
	    }
	 if(currentlabel.alignmentType == "CENTER")
	    {
	    x -= len/2;
	    }
	 if(currentlabel.alignmentType == "LEFT")
	    {
	    x -= len;
	    }

	 double theta = 3.14159/2;
	 g.rotate(-theta);         // rotate canvas clockwise by theta rads
         g.drawString(currentlabel.txt, x, currentlabel.xval);
         g.rotate(theta);         // rotate canvas counter-clockwise by theta rads
         }    

       } // end while loop   


     // restore color
     g.setColor(tmp);
     } 



 //Draws a grid using the current color.
  protected void drawGrid(Graphics2D g, int gridX, int gridY) 
    {
    //Draw vertical lines.
	int x = this.grid_x_threshold;
 
	while (x <= this.rightx) 
	    {
		g.drawLine(x, this.top, x, this.bot-this.grid_y_threshold);
		x += gridX;
	    }
	
	//Draw horizontal lines.
	int y = this.bot-this.grid_y_threshold;
	while (y >= this.top)
	    {
		g.drawLine(this.grid_x_threshold, y, this.rightx, y);
		y -= gridY;
	    }
	
    }
	

    protected void removeText(TxtLabel labl)
      {
      this.txtlabels.removeElement(labl);
      }

    protected void clearTextLabels()
      {
      this.txtlabels.removeAllElements();	  
      }

    protected void addTextLabel(TxtLabel labl)
      {
      this.txtlabels.addElement(labl);
      }

    protected void addText(String txt, int x, int y, Color txtcolor, String align, String rotate)
      {
      TxtLabel tmp = new TxtLabel(txt,x,y,txtcolor,align,rotate);
      this.txtlabels.addElement(tmp);
      }


    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////



    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    // MOUSE LISTENERS - required by Interface MouseInputListener
    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

    public void mouseExited(MouseEvent e) 
        { 
        this.setupperx = false;
        this.setlowerx = false;
        this.setuppery = false;
        this.setlowery = false;
	this.point2 = null;
        this.point3 = null;
        this.point4 = null;
        this.repaint();
        e.consume();
        } 

    public void mouseReleased(MouseEvent e) {e.consume();}

    public void mouseEntered(MouseEvent e) {e.consume();}

    public void mousePressed(MouseEvent e) 
       { 
	    if(this.point3 == null) {this.point3 = new Point(e.getX(),e.getY());}
	   else {this.point3.x = e.getX(); this.point3.y = e.getY(); this.repaint();}
       e.consume();
       }

    public void mouseDragged(MouseEvent e) 
       { 
       if(this.point3 != null)
	   {
	   if(this.point4 == null) {this.point4 = new Point(e.getX(),e.getY());}
           else {
	      this.point4.x = e.getX();
	      this.point4.y = e.getY();
	      }
           this.repaint();
           }
       e.consume();
       }    

 }  
 //////////////////////////////////////////////////////////////////////////////////////////
 // END CUSTOMCANVAS                END CUSTOMCANVAS                    END CUSTOMCANVAS //
 //////////////////////////////////////////////////////////////////////////////////////////



