package jplot;
/*
 * This file is part of jPlot
 *
 * jPlot is a Java version of Tim Shepard's xplot which is distributed
 * under the M.I.T. license (as below).
 *
 * Tim Shepard's original xplot is an amazing piece of software. It was
 * designed to be extraordinarily fast and efficient and as such, it has
 * only a very thin user interface. Those design goals, however, make
 * xplot extremely difficult to port to new architectures and also make it
 * difficult to use for the casual user. jPlot was designed to address
 * these limitations while keeping as much of the original efficiency as
 * possible. We thank Tim Shepard for his hard work on xplot and hope that
 * jPlot can live up to his high standards.
 *
 * jPlot was developed by Avinash Lakhiani at the Internetworking Research
 * Lab (IRG), Ohio University, as part of Dr. Shawn Ostermann's tcptrace
 * project. The main goal behind the development of jPlot was to make the
 * plotting tool used with tcptrace more portable. As a result, jPlot
 * retains many of the original algorithms implemented in xplot, and as per
 * the terms and conditions of the M.I.T. license, continue to remain under
 * that license. The M.I.T license is as follows:
 *
 * --- M.I.T License Begin ---
 *
 * Copyright 1992,1993 by the Massachusetts Institute of Technology.
 *                   All rights reserved.
 *
 * THIS SOFTWARE IS PROVIDED "AS IS", AND M.I.T. MAKES NO REPRESENTATIONS
 * OR WARRANTIES, EXPRESS OR IMPLIED.  By way of example, but not
 * limitation, M.I.T. MAKES NO REPRESENTATIONS OR WARRANTIES OF
 * MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR PURPOSE OR THAT THE USE
 * OF THE LICENSED SOFTWARE OR DOCUMENTATION WILL NOT INFRINGE ANY THIRD
 * PARTY PATENTS, COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS.
 *
 * The name of the Massachusetts Institute of Technology or M.I.T. may
 * NOT be used in advertising or publicity pertaining to distribution of
 * the software.  Title to copyright in this software and any associated
 * documentation shall at all times remain with M.I.T., and USER agrees
 * to preserve same.
 *
 * --- M.I.T. License End ---
 *
 * jPlot also has a lot of added features that are not part of the original
 * xplot code. Those sections of code are distributed under the following
 * BSD License:
 *
 * --- BSD License Begin ---
 *
 * Copyright (c) 2002, 2003 by Avinash Lakhiani
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *   - Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *   - Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in
 *     the documentation and/or other materials provided with the
 *     distribution.
 *   - Neither the name of Ohio University nor the names of its contributors
 *     may be used to endorse or promote products derived from this software
 *     without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * --- BSD License End ---
 *
 * You should have received a file named "DISTINCTION" along with this
 * distribution, that clearly identifies all the algorithms that were
 * retained from the original xplot code, and remain under the M.I.T.
 * license. The rest of the code was written for jPlot, and falls under
 * the BSD license.
 *
 * jPlot is free software; you can redistribute it and/or modify it under the
 * terms and conditions of both the above licenses.
 *
 * jPlot is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE.  See the above licenses for more details.
 *
 * Author:  Avinash Lakhiani
 *          Internetworking Research Group
 *          Ohio University
 *          Athens, OH
 *          avinash.lakhiani@ohiou.edu
 *          http://www.tcptrace.org/jPlot
 *
 */

/*
 * $Id: Plotter.java,v 1.2 2002/12/04 01:58:52 alakhian Exp $
 */

/* Import Packages */
import java.awt.*;
import java.awt.geom.*;
import java.util.*;
import java.io.*;
import java.lang.String;
import java.util.List;

/* Class       - Plotter
 * Description - this class holds all the attributes for a plotter, and all its commands
 * Extends     - NULL
 * Implements  - NULL
 */
public abstract class Plotter
{
   private boolean isMargin;         // Indicates if an extra margin is required around the plot
   protected List commands;          // List of plotting commands read from the file
   protected int xType;              // x-axis type
   protected int yType;              // y-axis type
   protected String xUnits;          // x-axis units
   protected String yUnits;          // y-axis units
   protected double aspectRatio;     // Aspect ration, if specified in the file, else set to 0.0
   private int viewNo;             // view number, used for zooming
   protected Coord xLeft[];          // left boundary of the particular view
   protected Coord xRight[];         // right boundary of the particular view
   protected Coord yTop[];           // top boundary of the particular view
   protected Coord yBottom[];        // bottom boundary of the particular view
   protected Point2D origin;         // origin of this plot
   protected Point2D size;           // set size of the viewport
   protected Point2D mainSize;       // actual size of the viewport
   protected Point dragStart;        // mouse drag start point
   protected Point dragEnd;          // mouse drag end point
   protected Color defaultColor;     // default plotting color {set to black in constructor, changed if specified in file}
   protected Color currentColor;     // current plotting color {changes with specification on each line}
   protected Color foregroundColor;  // plotter foreground color
   protected Color backgroundColor;  // plotter background color
   private int state;              // plotter state

   /* Method      - Plotter (default constructor)
    * Parameters  - plotter filename
    * Returns     - NULL
    * Description - sets the plotter filename
    */
   public Plotter(boolean isMargin)
     {
	/* Initializations */
	commands        = new ArrayList();
	xUnits          = new String("");
	yUnits          = new String("");
	aspectRatio     = 0.0;
	setViewNo(0);
	xLeft           = new Coord[Constant.NUMVIEWS];
	yBottom         = new Coord[Constant.NUMVIEWS];
	xRight          = new Coord[Constant.NUMVIEWS];
	yTop            = new Coord[Constant.NUMVIEWS];
	origin          = new Point2D.Double(Constant.DEFAULT_ORIGIN_X, Constant.DEFAULT_ORIGIN_Y);
	mainSize        = new Point2D.Double(0.0, 0.0);
	size            = new Point2D.Double(0.0, 0.0);
	dragStart       = new Point(0, 0);
	dragEnd         = new Point(0, 0);
	defaultColor    = Color.white;
	currentColor    = Color.white;
	foregroundColor = Color.white;
	backgroundColor = Color.black;
	setState(State.NORMAL);
	this.isMargin   = isMargin;
     }

   protected abstract int readInput();


  /* Method      - parseError
    * Parameters  - line number, input line, error message
    * Returns     - NULL
    * Description - prints the error message for the error that occured during input parsing,
    * along with the line number and input line.
    */
   protected void parseError(int lineNo, String line, String error, String plotFile)
   {
     // TODO error message
     System.out.println("Plotter.java (file: "+ plotFile + "): in line number " +
			   lineNo + " : " + error + "\n\toffending line: " + line
			   );
     throw new Error("Plotter.java (file: "+ plotFile + "): in line number " +
         lineNo + " : " + error + "\n\toffending line: " + line);

     } // End of method parseError()

   /* Method      - parseColor
    * Parameters  - color string
    * Returns     - result of operation {parsedcolor - successful, null - unsuccessful}
    * Description - parses the color string passed to it, and returns the appropriate java
    * color.
    */
   protected Color parseColor(String sColor)
     {
	String sColors[] = {"white", "green", "red", "blue", "yellow", "purple", "orange", "magenta", "pink", "gray20"};

	Color Colors[] = {
	     Color.white, Color.green, Color.red, Color.blue, Color.yellow, new Color((float)0.7, (float)0.3, (float)1.0), new Color((float)1.0, (float)0.7, (float)0.0), Color.magenta, Color.pink, new Color((float)0.2, (float)0.2, (float)0.2)
	};

	for(int i = 0; i < sColors.length; i++)
	  {
	     if(sColor.compareToIgnoreCase(sColors[i]) == 0)
	       return(Colors[i]);
	  }

	return(null);

     } // End of method parseColor()

   /* Method      - newCoord
    * Parameters  - coordinate value as a string
    * Returns     - newly created coordinate object
    * Description - this method creates a coordinate object appropriate for the specified
    * axis type.
    */
   protected Coord newCoord(int type, String s)
     {
	Coord c = null;

	switch(type)
	  {
	   case cType.U_INT:
	     c = new cUnsigned(s);
	     break;

	   case cType.INT:
	     c = new cSigned(s);
	     break;

	   case cType.DOUBLE:
	     c = new cDouble(s);
	     break;

	   case cType.TIMEVAL:
	     c = new cTimeVal(s);
	     break;

	   case cType.DTIME:
	     c = new cDTime(s);
	     break;

	   default:
	     Error("Plotter.java {method newCoord(int , String)}: unknown coord type");
	  }

	return(c);

     } // End of method newCoord

   /* Method      - compute Range
    * Parameters  - NULL
    * Returns     - NULL
    * Description - computes the window limits within which the plot is to be displayed.
    */
   protected void computeRange()
     {
	try
	  {
	     boolean virgin = true;
	     int size = commands.size();
	     Command cmd = null;
	     for(int i = 0; i < size; i++)
	       {
		  cmd = (Command)commands.get(i);
		  if((cmd.type == cmdType.TITLE)  ||
		     (cmd.type == cmdType.XLABEL) ||
		     (cmd.type == cmdType.YLABEL)
		     )
		    continue;

		  switch(cmd.type)
		    {
		     case cmdType.DLINE     :
		     case cmdType.LINE      :
		       if(virgin || (cmd.xb.compareTo(xLeft[getViewNo()]) < 0))
			 xLeft[getViewNo()].set(cmd.xb.get());
		       if(virgin || (cmd.xb.compareTo(xRight[getViewNo()]) > 0))
			 xRight[getViewNo()].set(cmd.xb.get());
		       if(virgin || (cmd.yb.compareTo(yBottom[getViewNo()]) < 0))
			 yBottom[getViewNo()].set(cmd.yb.get());
		       if(virgin || (cmd.yb.compareTo(yTop[getViewNo()]) > 0))
			 yTop[getViewNo()].set(cmd.yb.get());
		       virgin = false;
		     case cmdType.X         :
		     case cmdType.DOT       :
		     case cmdType.PLUS      :
		     case cmdType.BOX       :
		     case cmdType.DIAMOND   :
		     case cmdType.UTICK     :
		     case cmdType.DTICK     :
		     case cmdType.LTICK     :
		     case cmdType.RTICK     :
		     case cmdType.HTICK     :
		     case cmdType.VTICK     :
		     case cmdType.UARROW    :
		     case cmdType.DARROW    :
		     case cmdType.LARROW    :
		     case cmdType.RARROW    :
		     case cmdType.INVISIBLE :
		     case cmdType.TEXT      :
		       if(virgin || (cmd.xa.compareTo(xLeft[getViewNo()]) < 0))
			 xLeft[getViewNo()].set(cmd.xa.get());
		       if(virgin || (cmd.xa.compareTo(xRight[getViewNo()]) > 0))
			 xRight[getViewNo()].set(cmd.xa.get());
		       if(virgin || (cmd.ya.compareTo(yBottom[getViewNo()]) < 0))
			 yBottom[getViewNo()].set(cmd.ya.get());
		       if(virgin || (cmd.ya.compareTo(yTop[getViewNo()]) > 0))
			 yTop[getViewNo()].set(cmd.ya.get());
		       virgin = false;
		       break;
		     default                :
		       Error("jPlot: {DrawPlot.java: computeRange()}: unknown command type");
		    }
	       }

	     /* This is to fix the window so that the labels can be seen correctly.
	      * (In xplot the labels would get clipped-off the edge, if they were at end points in the graph).
	      * Just add 10% to the existing range.
	      */
	     if(isMargin)
	       {
		  xLeft[getViewNo()].set(xLeft[getViewNo()].get() - Math.rint((xRight[getViewNo()].get() - xLeft[getViewNo()].get()) * Constant.MARGIN));
		  xRight[getViewNo()].set(xRight[getViewNo()].get() + Math.rint((xRight[getViewNo()].get() - xLeft[getViewNo()].get()) * Constant.MARGIN));
		  yBottom[getViewNo()].set(yBottom[getViewNo()].get() - Math.rint((yTop[getViewNo()].get() - yBottom[getViewNo()].get()) * Constant.MARGIN));
		  yTop[getViewNo()].set(yTop[getViewNo()].get() + Math.rint((yTop[getViewNo()].get() - yBottom[getViewNo()].get()) * Constant.MARGIN));
	       }
	  }
	catch(IndexOutOfBoundsException e)
	  {
	     System.out.println(e.toString());
	  }

     } // End of method computeRange()


   /* Method      - bumpCoord
    * Parameters  - axis type and the coordinate to bump
    * Returns     - NULL
    * Description - This method bumps the coordinates of the axis to the correct range values, in order to
    * get the axis ticks correct. (Used in conjunction with the "tick" method)
    * NOTES       - This algorithm is taken as-is from Tim Shepard's xplot.
    */
   protected void bumpCoord(int type, Coord c)
     {
	Coord cCopy = null;   // temporary coordinates used to bump coordinate c
	Coord     t = null;
	int level = 0;        // level used to compute tick marks

	switch(type)
	  {
	   case cType.U_INT:
	     t     = new cUnsigned();
	     cCopy = new cUnsigned(c);
	     break;

	   case cType.INT:
	     t     = new cSigned();
	     cCopy = new cSigned(c);
	     break;

	   case cType.DOUBLE:
	     t     = new cDouble();
	     cCopy = new cDouble(c);
	     break;

	   case cType.TIMEVAL:
	     t     = new cTimeVal();
	     cCopy = new cTimeVal(c);
	     break;

	   case cType.DTIME:
	     t     = new cDTime();
	     cCopy = new cDTime(c);
	     break;

	   default:
	     Error("DrawPlot.java {method bumpCoord(int , Coord)}: unknown coord type");
	  }

	do
	  {
	     t.tick(level++);
	     c.add(t);
	  } while(c.compareTo(cCopy) == 0);

     } // End of method bumpCoord()


   /* Method      - makeAxis
    * Parameters  - NULL
    * Returns     - NULL
    * Description - creates the axis for this plotter. (called from DrawPlot.java: {method sizeWindow()})
    */
   public void makeAxis()
     {
	Command cmd = null;
	Color     c = null;
	Coord    x1 = newCoord(xType, "0");
	Coord    y1 = newCoord(yType, "0");
	Coord    x2 = newCoord(xType, "0");
	Coord    y2 = newCoord(yType, "0");

	x1.set(xLeft[getViewNo()].get())  ;  y1.set(yTop[getViewNo()].get());
	x2.set(xLeft[getViewNo()].get())  ;  y2.set(yBottom[getViewNo()].get());
	/* arguments for this constructor
	 * Command(type, position, mappped, decoration, color, coord1, coord2, text)
	 */
	cmd = new Command(cmdType.LINE, Position.UNDEFINED, false, true, c, x1, y1, x2, y2, "");
	commands.add(0, cmd);

	/* New coordinates are created since we would land up modifying the x-axis values (above), if modified directly */
	x1 = newCoord(xType, "0");
	y1 = newCoord(yType, "0");
	x2 = newCoord(xType, "0");
	y2 = newCoord(yType, "0");

	x1.set(xLeft[getViewNo()].get())  ;  y1.set(yBottom[getViewNo()].get());
	x2.set(xRight[getViewNo()].get()) ;  y2.set(yBottom[getViewNo()].get());
	/* arguments for this constructor
	 * Command(type, position, mappped, decoration, color, coord1, coord2, text)
	 */
	cmd = new Command(cmdType.LINE, Position.UNDEFINED, false, true, c, x1, y1, x2, y2, "");
	commands.add(0, cmd);

	/* Set the tick marks on the axis */
	setTicks(xType, xLeft[getViewNo()], xRight[getViewNo()], true);   // flag indicates horizontal axis
	setTicks(yType, yBottom[getViewNo()], yTop[getViewNo()], false);  // flag indicates vertical axis, no horizontal

     } // End of method makeAxis()


   /* Method      - setTicks
    * Parameters  - begining and end of the axis, and a flag indicating whether it is horizontal/vertical axis.
    * Returns     - NULL
    * Description - this method takes the begining and end of the axis and sets the appropriate tick marks on it.
    * It is smart enough to compute the number of ticks that may fit, and set them with the appropriate tick labels.
    * NOTES       - this algorithm has been taken as-is from Tim Shepard's xplot.
    */
   public void setTicks(int type, Coord first, Coord last, boolean horizontal)
     {
	int level             = 0;
	int subLevel          = 0;
	int count             = 0;
	int subCount          = 0;
	int maxExtraSubticks  = 0;
	Coord at   = newCoord(type, "0");
	Coord step = newCoord(type, "0");

	if(horizontal)
	  maxExtraSubticks = Constant.MAXTICKS + 1;
	else
	  maxExtraSubticks = (Constant.MAXTICKS + 1) * 2;

	/* Start with the smallest tick level */
	for(level = 0; ; level++)
	  {
	     step.tick(level);
	     at.roundUp(first, step);

	     for (count=1; count <= Constant.MAXTICKS; count++)
	       {
		  at.add(step);
		  if (at.compareTo(last) > 0)
		    break;
	       } /* count is now number of ticks that fit */

	     /* if <=  max allowed, use this level */
	     if(count <= Constant.MAXTICKS) break;
	  }

	step.tick(level);
	at.roundUp(first, step);

	while(at.compareTo(last) <= 0)
	  {
	     if(horizontal)         // flag indicates labels are to be placed with ticks
	       doXTick(at, true);
	     else
	       doYTick(at, true);

	     at.add(step);
	  }

	subLevel = first.subTick(level);
	if(subLevel != level)
	  {
	     step.tick(subLevel);
	     at.roundUp(first, step);

	     for (subCount = 1; subCount - count <= maxExtraSubticks; subCount++)
	       {
		  at.add(step);
		  if(at.compareTo(last) > 0)
		    break;
	       }

	     if(subCount - count <= maxExtraSubticks)
	       {
		  at.roundUp(first, step);
		  while(at.compareTo(last) <= 0)
		    {
		       if(horizontal)         // flag indicates labels are NOT to be placed with ticks
			 doXTick(at, false);
		       else
			 doYTick(at, false);

		       at.add(step);
		    }
	       }
	  }

     } // End of method setTicks()

   /* Method      - doXTick
    * Parameters  - tick position and a flag indicating whether a label is to be placed with the tick.
    * (labels are not placed with subticks).
    * Returns     - NULL
    * Description - creates a new command that places a tick at the specified position, and possibly a label.
    */
   private void doXTick(Coord r, boolean labelFlag)
     {
	Command cmd = null;
	Color     c = null;
	Coord    x1 = newCoord(xType, "0");
	Coord    y1 = newCoord(yType, "0");

	x1.set(r.get())  ;  y1.set(yBottom[getViewNo()].get());
	/* arguments for this constructor
	 * Command(type, position, mappped, decoration, color, coord1, coord2, text)
	 */
	cmd = new Command(cmdType.DTICK, Position.UNDEFINED, false, true, c, x1, y1, null, null, "");
	commands.add(0, cmd);

	if(labelFlag == true)
	  {
	     String text = r.unparse();
	     cmd = new Command(cmdType.TEXT, Position.BELOW, false, true, c, x1, y1, null, null, (text + " " + xUnits));
	     commands.add(0, cmd);
	  }

     } // End of method doXTick()

   /* Method      - doYTick
    * Parameters  - tick position and a flag indicating whether a label is to be placed with the tick.
    * (labels are not placed with subticks).
    * Returns     - NULL
    * Description - creates a new command that places a tick at the specified position, and possibly a label.
    */
   private void doYTick(Coord r, boolean labelFlag)
     {
	Command cmd = null;
	Color     c = null;
	Coord    x1 = newCoord(xType, "0");
	Coord    y1 = newCoord(yType, "0");

	x1.set(xLeft[getViewNo()].get())  ;  y1.set(r.get());
	/* arguments for this constructor
	 * Command(type, position, mappped, decoration, color, coord1, coord2, text)
	 */
	cmd = new Command(cmdType.LTICK, Position.UNDEFINED, false, true, c, x1, y1, null, null, "");
	commands.add(0, cmd);

	if(labelFlag == true)
	  {
	     String text = r.unparse();
	     cmd = new Command(cmdType.TEXT, Position.LEFT, false, true, c, x1, y1, null, null, (text + " " + yUnits));
	     commands.add(0, cmd);
	  }

     } // End of method doYTick()

   /* Method      - reSync
    * Parameters  - NULL
    * Returns     - NULL
    * Description - resyncs the plotter data with the current state of the file. Basically re-reads the
    * data from the file.
    */
   public void reSync()
     {
	/* Initializations */
	commands        = new ArrayList();
	xUnits          = new String("");
	yUnits          = new String("");
	aspectRatio     = 0.0;
	setViewNo(0);
	xLeft           = new Coord[Constant.NUMVIEWS];
	yBottom         = new Coord[Constant.NUMVIEWS];
	xRight          = new Coord[Constant.NUMVIEWS];
	yTop            = new Coord[Constant.NUMVIEWS];
	origin          = new Point2D.Double(Constant.DEFAULT_ORIGIN_X, Constant.DEFAULT_ORIGIN_Y);
	dragStart       = new Point(0, 0);
	dragEnd         = new Point(0, 0);
	defaultColor    = Color.white;
	currentColor    = Color.white;
	foregroundColor = Color.white;
	backgroundColor = Color.black;
	setState(State.NORMAL);
	this.isMargin   = isMargin;
	/* Read the commands from the file */
	readInput();


     } // End of method reSync()

   /* Method      - Error
    * Parameters  - error message
    * Returns     - NULL
    * Description - prints the error message and exits.
    */
   private void Error(String msg)
     {
	System.out.println(msg);
  throw new RuntimeException(msg);
//	System.exit(1);
     }

  public void setViewNo(int viewNo) {
    this.viewNo = viewNo;
  }

  public int getViewNo() {
    return viewNo;
  }

  public void setState(int state) {
    this.state = state;
  }

  public int getState() {
    return state;
  }

} // End of class Plotter
