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: cTimeVal.java,v 1.2 2002/12/04 01:58:53 alakhian Exp $
 */

/* Import Packages */
import java.lang.Double;
import java.lang.Long;
import java.lang.Math;
import java.text.NumberFormat;
import java.util.StringTokenizer;
import java.util.NoSuchElementException;
import java.lang.StringBuffer;
import java.util.Calendar;
import java.util.Date;
import java.util.TimeZone;

/* Class       - cTimeVal
 * Description - this class is for the coordinates of type timeval. It implements all
 * the necessary methods for manipulation of instances of this type.
 * Extends     - NULL
 * Implements  - Coord
 * NOTES       - this class is the java implementation of the coordinate-type timeval
 * from Tim Shepard's xplot.
 */
public class cTimeVal implements Coord
{
   double tv;        // The full time value, as read from the input file
   long   tv_sec;    // The seconds part of the time value
   long   tv_usec;   // The microseconds part of the time value

   /* Method      - cTimeVal (Constructor)
    * Parameters  - NULL
    * Returns     - NULL
    * Description - this constructor is called when a coordinate of type timeval is
    * created with no initial value. The value is then set to 0 by default.
    */
   public cTimeVal()
     {
	this.zero();
     }

   /* Method      - cTimeVal (Constructor)
    * Parameters  - the string from which the timeval value is to be extracted.
    * Returns     - NULL
    * Description - this constructor is called when a coordinate of type timeval is
    * read in from the file as a string.
    */
   public cTimeVal(String s)
     {
	this.parse(s);
     }

   /* Method      - cTimeVal (Constructor)
    * Parameters  - the object from which the timeval value is to be taken.
    * Returns     - NULL
    * Description - this constructor is called when a coordinate of type timeval is obtained
    * from another coordinate
    */
   public cTimeVal(Coord c)
     {
	cTimeVal k   = (cTimeVal)c;
	this.tv      = k.tv;
	this.tv_sec  = k.tv_sec;
	this.tv_usec = k.tv_usec;
     }

   /* Method      - timevalFix
    * Parameters  - NULL
    * Returns     - NULL
    * Description - adjusts the microseconds and seconds. (Does the correct math)
    */
   private void timevalFix()
     {
	while(this.tv_usec < 0)
	  {
	     this.tv_usec += 1000000;
	     this.tv_sec  -= 1;
	  }

	while(this.tv_usec >= 1000000)
	  {
	     this.tv_usec -= 1000000;
	     this.tv_sec  += 1;
	  }
     }

   /* Method      - set
    * Parameters  - value the coordinate is to be set to.
    * Returns     - NULL
    * Description - sets the value of the coordinate (field d) to the given value.
    */
   public void set(double val)
     {
	this.parse(val);
     }

   /* Method      - get
    * Parameters  - NULL
    * Returns     - the value of the coordinate
    * Description - fetches the value of the calling coordinate and returns it.
    */
   public double get()
     {
	return(this.tv);
     }

   /* Method      - unparse
    * Parameters  - NULL
    * Returns     - the coordinate as a string
    * Description - used to convert the calling coordinate into a string
    */
   public String unparse()
     {
	Date         date     = new Date((long)((this.tv_sec * 1000) + ((double)this.tv_usec / 1000.0)));  // Date takes the time since the "epoch" in milliseconds
	Calendar     cald     = Calendar.getInstance();
	cald.setTime(date);

	String       time     = date.toString();
	StringBuffer timeBuf  = new StringBuffer(time);

	try
	  {
	     if((this.tv_usec == 0) &&
		(cald.get(Calendar.SECOND) == 0) &&
		(cald.get(Calendar.MINUTE) == 0) &&
		(cald.get(Calendar.HOUR)   == 0)
		)
	       {
		  timeBuf.delete(0, 4);
		  timeBuf.delete(7, timeBuf.length());
		  timeBuf.append("midn");
	       }
	     else if((this.tv_usec == 0) &&
		     (cald.get(Calendar.SECOND) == 0) &&
		     (cald.get(Calendar.MINUTE) == 0) &&
		     (cald.get(Calendar.HOUR)   == 12)
		     )
	       {
		  timeBuf.delete(0, 4);
		  timeBuf.delete(7, timeBuf.length());
		  timeBuf.append("noon");
	       }
	     else
	       {
		  timeBuf.delete(0, 11);
		  timeBuf.delete(8, timeBuf.length());

		  if(this.tv_usec != 0)
		    {
		       NumberFormat nf = NumberFormat.getInstance();  // Returns the default number format for the current default locale
		       nf.setGroupingUsed(false);                     // Turn off the grouping (placing commas' every 3 digits)
		       if((this.tv_usec % 100) == 0)
			 {
			    nf.setMaximumIntegerDigits(4);  // Set the fractional part to a max of 10 places
			    String frac = nf.format((this.tv_usec / 100));
			    timeBuf.append(".");
			    timeBuf.append(frac);
			 }
		       else
			 {
			    nf.setMaximumIntegerDigits(6);  // Set the fractional part to a max of 10 places
			    String frac = nf.format(this.tv_usec);
			    timeBuf.append(".");
			    timeBuf.append(frac);
			 }
		    }
	       }

	     time = timeBuf.toString();
	  }
	catch(StringIndexOutOfBoundsException e)
	  {
	     System.out.println("jPlot: {cTimeVal.java : unparse()}: " + e.toString());
//	     System.exit(1);
	     throw new RuntimeException(e);
	  }

	return(time);
     }

   /* Method      - parse
    * Parameters  - the double value from which the double value is to be extracted.
    * Returns     - NULL
    * Description - extracts the double value from the provided string, and sets
    * the member variable d.
    */
   private void parse(double val)
     {
	NumberFormat nf = NumberFormat.getInstance();  // Returns the default number format for the current default locale
	nf.setGroupingUsed(false);                     // Turn off the grouping (placing commas' every 3 digits)
	nf.setMaximumFractionDigits(6);                // Set the fractional part to a max of 6 places
	nf.setMinimumFractionDigits(6);                // Set the fractional part to a min of 6 places
	String s = nf.format(val);
	StringTokenizer st = new StringTokenizer(s, ".");   // "." is the delimiter

	this.zero();                     // Initialize the coordinate
	this.tv = val;                   // Set its double value

	try
	  {
	     int size = st.countTokens();
	     if(size >= 1)               // Set the seconds part
	       this.tv_sec = Long.parseLong(st.nextToken());
	     if(size == 2)               // Set the micorseconds part
	       this.tv_usec = Long.parseLong(st.nextToken());
	  }
	catch(NoSuchElementException e)
	  {
	     System.out.println("jPlot: {cTimeVal.java: parse(string): " + e.toString());
//	     System.exit(0);
       throw new RuntimeException(e);
	  }
     }

   /* Method      - parse
    * Parameters  - the string from which the double value is to be extracted.
    * Returns     - NULL
    * Description - extracts the double value from the provided string, and sets
    * the member variable d.
    */
   public void parse(String s)
     {
	Double dObj = new Double(s);
	StringTokenizer st = new StringTokenizer(s, ".");   // "." is the delimiter

	this.zero();                     // Initialize the coordinate
	this.tv = dObj.doubleValue();    // Set its double value

	try
	  {
	     int size = st.countTokens();
	     if(size >= 1)               // Set the seconds part
	       this.tv_sec = Long.parseLong(st.nextToken());
	     if(size == 2)               // Set the micorseconds part
	       this.tv_usec = Long.parseLong(st.nextToken());
	  }
	catch(NoSuchElementException e)
	  {
	     System.out.println("jPlot: {cTimeVal.java: parse(string): " + e.toString());
       throw new RuntimeException(e);
//	     System.exit(0);
	  }
     }

   /* Method      - zero
    * Parameters  - NULL
    * Returns     - NULL
    * Description - sets the coordinate to zero.
    */
   public void zero()
     {
	this.tv      = 0.0;
	this.tv_sec  = 0;
	this.tv_usec = 0;
     }

   /* Method      - compareTo
    * Parameters  - the coordinate with which the calling coordinate is to be compared.
    * Returns     - result of comparison i.e. -1 for <, 0 for ==, 1 for >.
    * Description - performs the comparison of the coordinates and returns the results.
    */
   public int compareTo(Coord c)
     {
	cTimeVal k = (cTimeVal)c;
	if      (this.tv_sec  > k.tv_sec)   return(1);
	else if (this.tv_sec  < k.tv_sec)   return(-1);
	else if (this.tv_usec > k.tv_usec)  return(1);
	else if (this.tv_usec < k.tv_usec)  return(-1);
	else                                return(0);

     }

   /* Method      - add
    * Parameters  - the coordinate to add to the calling coordinate.
    * Returns     - NULL
    * Description - adds the value of the passed coordinate to the calling coordinate.
    */
   public void add(Coord c)
     {
	cTimeVal k = (cTimeVal)c;
	this.tv_sec  += k.tv_sec;   // Add the seconds
	this.tv_usec += k.tv_usec;  // Add the microseconds

	this.timevalFix();          // Do the corrective math

	String s = new String(this.tv_sec + "." + this.tv_usec);
	Double dObj = new Double(s);
	this.tv = dObj.doubleValue(); // Set the new full value
     }

   /* Method      - subtract
    * Parameters  - the coordinate to subtract from the calling coordinate.
    * Returns     - NULL
    * Description - subtracts the value of the passed coordinate from the calling coordinate.
    */
   public void subtract(Coord c)
     {
	cTimeVal k = (cTimeVal)c;

	this.timevalFix();

	if(k.tv_usec > this.tv_usec)
	  {
	     this.tv_sec  -= 1;
	     this.tv_usec += 1000000;
	  }

	this.tv_sec  = this.tv_sec  - k.tv_sec;
	this.tv_usec = this.tv_usec - k.tv_usec;

	String s = new String(this.tv_sec + "." + this.tv_usec);
	Double dObj = new Double(s);
	this.tv = dObj.doubleValue(); // Set the new full value
     }

   /* Method      - roundUp
    * Parameters  - the rounding range
    * Returns     - NULL
    * Description - rounds-up the calling coordinate.
    */
   public void roundUp(Coord c1, Coord c2)
     {
	cTimeVal k1 = new cTimeVal(c1);
	cTimeVal k2 = new cTimeVal(c2);

	Date         date     = new Date((long)((k1.tv_sec * 1000) + ((double)k1.tv_usec / 1000.0)));  // Date takes the time since the "epoch" in milliseconds
	Calendar     cald     = Calendar.getInstance();
	cald.setTime(date);

	int dstoff = cald.get(Calendar.DST_OFFSET);
	int gmtoff = cald.get(Calendar.ZONE_OFFSET);

	k1.tv_sec += ((gmtoff + dstoff) / 1000);

	if(k2.tv_sec == 0)
	  {
	     this.tv_sec = k1.tv_sec;
	     if((k1.tv_usec % k2.tv_usec) == 0)
	       {
		  this.tv_usec = k1.tv_usec;
	       }
	     else
	       {
		  this.tv_usec = k1.tv_usec + (k2.tv_usec - (k1.tv_usec % k2.tv_usec));
		  if (this.tv_usec >= 1000000)
		    {
		       this.tv_usec -= 1000000;
		       this.tv_sec  += 1;
		    }
	       }
	  }
	else
	  {
	     this.tv_usec = 0;
	     if((k1.tv_sec % k2.tv_sec) == 0)
	       {
		  this.tv_sec = k1.tv_sec;
	       }
	     else
	       {
		  this.tv_sec = k1.tv_sec + (k2.tv_sec - (k1.tv_sec % k2.tv_sec));
	       }
	  }

	this.tv_sec -= ((gmtoff + dstoff) / 1000);

	/* Correct the math */
	this.timevalFix();

	/* Set the double time value too */
	String s = new String(this.tv_sec + "." + this.tv_usec);
	Double dObj = new Double(s);
	this.tv = dObj.doubleValue(); // Set the new full value

     }

   /* Method      - roundDown
    * Parameters  - the rounding range
    * Returns     - NULL
    * Description - rounds-down the calling coordinate.
    */
   public void roundDown(Coord c1, Coord c2)
     {
	cTimeVal k1 = new cTimeVal(c1);
	cTimeVal k2 = new cTimeVal(c2);

	Date         date     = new Date((long)((k1.tv_sec * 1000) + ((double)k1.tv_usec / 1000.0)));  // Date takes the time since the "epoch" in milliseconds
	Calendar     cald     = Calendar.getInstance();
	cald.setTime(date);

	int dstoff = cald.get(Calendar.DST_OFFSET);
	int gmtoff = cald.get(Calendar.ZONE_OFFSET);

	k1.tv_sec += ((gmtoff + dstoff) / 1000);

	if(k2.tv_sec == 0)
	  {
	     this.tv_sec = k1.tv_sec;
	     this.tv_usec = (k1.tv_usec - (k1.tv_usec % k2.tv_usec));
	  }
	else
	  {
	     this.tv_usec = 0;
	     this.tv_sec = k1.tv_sec - (k1.tv_sec % k2.tv_sec);
	  }

	this.tv_sec -= ((gmtoff + dstoff) / 1000);

	/* Correct the math */
	this.timevalFix();

	/* Set the double time value too */
	String s = new String(this.tv_sec + "." + this.tv_usec);
	Double dObj = new Double(s);
	this.tv = dObj.doubleValue(); // Set the new full value
     }

   /* Method      - tick
    * Parameters  - the level
    * Returns     - NULL
    * Description - sets the appropriate tick value. Basically sets the tick marks for
    * the axis.
    * NOTES       - this algorithm is taken (as-it-is) from Tim Shepard's xplot.
    */
   public void tick(int level)
     {
	if((level < 0) ||
	   (level >= timevalTickTable.LENGTH)
	   )
	  {
	     System.out.println("jPlot: {cTimeVal.java: tick(int): level too large");
       throw new RuntimeException();
//	     System.exit(1);
	  }

	this.tv_sec = timevalTickTable.tv_sec[level];
	this.tv_usec = timevalTickTable.tv_usec[level];

	/* Correct the math */
	this.timevalFix();

	/* Set the double time value too */
	String s = new String(this.tv_sec + "." + this.tv_usec);
	Double dObj = new Double(s);
	this.tv = dObj.doubleValue(); // Set the new full value
     }


   /* Method      - subTick
    * Parameters  - the level
    * Returns     - NULL
    * Description - sets the appropriate subtick value. Basically sets the subtick marks for
    * the axis.
    * NOTES       - this algorithm is taken (as-it-is) from Tim Shepard's xplot.
    */
   public int subTick(int level)
     {
	int r = level;

	if((level < 0) ||
	   (level >= timevalTickTable.LENGTH)
	   )
	  {
	     System.out.println("jPlot: {cTimeVal.java: tick(int): level too large");
       throw new RuntimeException();
//	     System.exit(1);
	  }

	r = level + timevalTickTable.subTickOffset[level];

	return(r);
     }

   /* Method      - map
    * Parameters  - the boundary coordinates, and the drawing area size.
    * Returns     - the mapped double value of the calling coordinate.
    * Description - maps the calling coordinate to the cooresponding coordinate on the
    * viewport.
    */
   public double map(Coord c1, Coord c2, double n)
     {
	double r;
	double d;
	double dc;

	cTimeVal k1 = (cTimeVal)c1;
	cTimeVal k2 = (cTimeVal)c2;

	d  = (((double) ((int) (k2.tv_sec  - k1.tv_sec))) * 1000000 +
	      ((double) ((int) (k2.tv_usec - k1.tv_usec))));
	dc = (((double) ((int) (this.tv_sec   - k1.tv_sec))) * 1000000 +
	      ((double) ((int) (this.tv_usec  - k1.tv_usec))));

	r = (dc/d) * (n);

	return(r);
     }

   /* Method      - unmap
    * Parameters  - the boundary coordinates, the size of the drawing area, and the
    * coordinate to be unmapped.
    * Returns     - NULL
    * Description - computes the window coordinates, and sets the calling coordinate to the
    * resultant value.
    */
   public void unmap(Coord c1, Coord c2, double n, double x)
     {
	double d;
	cTimeVal k1 = (cTimeVal)c1;
	cTimeVal k2 = (cTimeVal)c2;

	d = (((double) (k2.tv_sec  - k1.tv_sec)) * 1000000 +
	     ((double) (k2.tv_usec - k1.tv_usec)));
	d /= n;
	d *= x;
	d += ((double) k1.tv_sec * 1000000) + (double) k1.tv_usec;

	this.tv_sec = (long)Math.floor(d/1000000.0);
	this.tv_usec = (long)Math.rint(d - (((double) Math.floor(d/1000000.)) * 1000000.));

	/* Set the double time value too */
	String s = new String(this.tv_sec + "." + this.tv_usec);
	Double dObj = new Double(s);
	this.tv = dObj.doubleValue(); // Set the new full value
     }

   /* Method      - toString
    * Parameters  - NULL
    * Returns     - the string representation of the double value
    * Description - gets the string representing the double value, and returns it.
    */
   public String toString()
     {
	NumberFormat nf = NumberFormat.getInstance();  // Returns the default number format for the current default locale
	nf.setGroupingUsed(false);                     // Turn off the grouping (placing commas' every 3 digits)
	nf.setMaximumFractionDigits(6);                // Set the fractional part to a max of 6 places
	nf.setMinimumFractionDigits(6);                // Set the fractional part to a min of 6 places
	return(nf.format(this.tv));
     }

} // End of class cTimeVal
