/*
 * This program will attempt to parse the temperature from hermes.ai.mit.edu
 * and display it in a window.
 *
 *      Created by:   Tom Coppeto  (tjcoppet@athena.mit.edu)
 *
 *      Copyright (c) 1988 Softbone, CNI.
 *      Permission to copy and distribute this software is granted
 *      if this copyright notice is maintained.
 *
 *      $Source: /afs/.net.mit.edu/tools/src/xtherm/RCS/xtherm.c,v $
 *      $Author: tom $
 *
 */

#ifndef lint
static char rcsid[] = "$Header: /afs/.net.mit.edu/tools/src/xtherm/RCS/xtherm.c,v 1.4 93/03/01 17:27:31 tom Exp $";
#endif

#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <stdio.h>
#include <signal.h>
#include <setjmp.h>
#include "xtherm.h"
#include <sys/time.h>

/*
 * Useful Globals
 */

int Black, White;
Window Rootwin;
Display *display;
int CELSIUS;

Agent agent;
extern char *location;

static jmp_buf env;
static int hangup_handler(), alarm_handler();
static int last_update = 0;

main(argc,argv)
     int argc;
     char *argv[];
{
  Thermometer t;
  WInfo wininfo;
  XEvent event;
  struct itimerval it, disable;

  /*
   *  Parse command args-> returns in wininfo
   */
  parser(&argc,argv,&wininfo);
  
  /*
   *  Create the thermometer gadget
   */
  t = thermometer_create(&wininfo);

  /*
   * Get the temperature from hermes.ai.mit.edu
   */
  temperature_measure(&t);

  /*
   * Draw the gadget
   */

  thermometer_draw(&t);
  XFlush(display);

  /*
   * interrupt to measure temp again
   */

  signal(SIGHUP, hangup_handler);

  bzero(&disable,  sizeof(disable));  
  bzero((char *) &it, sizeof(it));
  it.it_value.tv_sec = 300;
  
  if(setjmp(env))
    {
      temperature_measure(&t);
      thermometer_draw(&t);
    }

  signal(SIGALRM, alarm_handler);
  setitimer(ITIMER_REAL, &it, (struct itimerval *) NULL);

  /*
   * Event loop:
   *        refresh for expose events
   *        resample if time_to_measure
   */

  for(EVER)
    {
      XNextEvent(display, &event);

      if(event.type == ButtonRelease)
	{
	  if((time(0) - last_update) > 5)
	    {
	      setitimer(ITIMER_REAL, &disable, (struct itimerval *) NULL);
	      temperature_measure(&t);
	      setitimer(ITIMER_REAL, &it, (struct itimerval *) NULL);
	      signal(SIGALRM, alarm_handler);
	    }
	  thermometer_draw(&t);
	}
      else 
	if(event.type == Expose)
	  thermometer_draw(&t);
    }
}




/**********
 Function:    thermometer_create()
 Description: creates the thermometer gadget from the
              passed info
 Returns:     A thermometer
***********/

Thermometer
thermometer_create(info)
     WInfo *info;
{
  Thermometer t;
  WInfo inforv;

  t.w = window_create(info);
  t.gc = gc_create(t.w,info);

  inforv = *info;
  inforv.background = info->foreground;
  inforv.foreground = info->background;  
  t.gcrv = gc_create(t.w,&inforv);

  XSelectInput(display, t.w, 
	       ButtonPressMask |
	       ButtonReleaseMask |
	       StructureNotifyMask |
	       ExposureMask);

  if(CELSIUS)
    {
      t.gage_max = 100;
      t.gage_min = 0;
    }
  else
    {
      t.gage_max = 100;
      t.gage_min = 0;
    }

  t.tic_inc = 20;
  t.info = info;
  t.type = FAHRENHEIT;
  return(t);
}






/*********
 Function:    thermometer_draw()
 Description: draws the thermometer & gage
**********/

thermometer_draw(t)
     Thermometer *t;
{
  XWindowAttributes wat;
  double bulb_width, bulb_height;
  double bulb_x,     bulb_y;
  double tube_width, tube_height;
  double tube_x,     tube_y;
  double gage_width;
  double gage_x;
  int    ticx1,      ticx2,      ticy;
  double mark,       inc;
  int    range,     space,       i,j;
  int    mark_x;
  char   mark_s[5];
    
  /*
   * Flush display so that the window has stabilized before
   * sampling the attributes
   */

  XFlush(display);         
  XClearWindow(display,t->w);

  /*
   * Get the geometry of the gadget
   */

  XGetWindowAttributes(display,t->w,&wat);
  wat.height -=6;

  /*
   *  Map window if not mapped
   */

  if (wat.map_state == IsUnmapped)
    {
      XMapWindow(display, t->w);
      XFlush(display);
    }

  /*
   * Draw Base Bulb
   */

  bulb_width = wat.width * .5;
  bulb_height = wat.height * .1;
  bulb_x = wat.width * .31;
  bulb_y = wat.height * .83;

  XFillArc(display,t->w,t->gc,(int)bulb_x,(int)bulb_y,
	   (int)bulb_width,(int)bulb_height,
	   0,23040);

  /*
   * Draw the tube with arbitrary fudge factors
   */

  tube_width  = wat.width  * .24;
  tube_height = wat.height * .8;
  tube_x      = wat.width  * .45;
  tube_y      = wat.height * .1;

  XDrawRectangle(display,t->w,t->gc,
		 (int)tube_x,(int)tube_y,
		 (int)tube_width,(int)tube_height);
  
   
  gage_width = tube_width;
  gage_x = tube_x;


  /*
   * Draw the thing
   */
  

  /*
   * The range of the thermometer scales
   */
  range     = t->gage_max - t->gage_min;

  /*
   * scale_compute() will return the best increment to use
   * for the thermometer based on the font size of the labels
   */
  inc       = scale_compute((int)tube_height,
			    t->info->fstruct->ascent +
			    t->info->fstruct->descent,
			    range);
  
  /* 
   * determine the actual spacing between tics
   */
  space     = (tube_height * inc  / (double) range) * .9;

  /*
   * Find the position of the bottommost tic
   * (must also take into account that the bulb and
   * rectangular gage overlap)
   */
  ticy      = (int) tube_height + tube_y - bulb_height;

  /*
   * Determine the starting and ending x postitions of the gage
   */
  ticx1     = tube_x;
  ticx2     = tube_x + .4 * tube_width;

  /* 
   * Mark will keep track of the numerical value of the scale
   */
  mark      = t->gage_min;
  sprintf(mark_s,"%d",(int)mark);

  /* 
   * Determine the starting position of the scale label
   */
  mark_x  = ticx1 - XTextWidth(t->info->fstruct,mark_s,
			       strlen(mark_s)) - 5;


  /*
   * Fill in the gap between the bulb and the gage
   */
  i = bulb_height;  
  XFillRectangle(display,t->w, t->gc,
		 (int) gage_x, ticy,
		 (int) gage_width, i);
  
  /*
   * Loop upwards to draw gage and tics simultaneously
   */
  while(ticy > tube_y)
    {
      /*
       * Draw up to the nearest space
       */
      if (t->temperature >= mark)
	{
	  if(mark > t->gage_min)
	    {
	      /*
	       * draw the gage
	       */
	      XFillRectangle(display,t->w, t->gc,
			     (int) gage_x, ticy,
			     (int) gage_width, space);
	      
	      /*
	       * draw the tic
	       */
	      
	      XDrawLine(display,t->w,t->gcrv,ticx1, ticy, ticx2, ticy);
	    }
	  
	  /*
	   * Are we at the top?
	   */
	  if((mark + inc) > t->temperature)
	    {
	      /*
	       * find the difference between the current
	       * location and the desired location
	       */
	      
	      i   = (t->temperature - mark) / inc * space;
	      j   = ticy - i;
	      
	      /*
	       * Fill it in
	       */
	      XFillRectangle(display,t->w,t->gc,
			     (int) gage_x, j,
			     (int) gage_width, i);
	    }				
	}
      
      /*
       * draw the ticks, in rv
       */  
      else
	XDrawLine(display,t->w,t->gc,ticx1, ticy, ticx2, ticy);
	
      /*
       * draw the number label
       */
      XDrawImageString(display,t->w,t->gc,
		       mark_x, ticy,
		       mark_s,strlen(mark_s));

      /* 
       * inc the marker, and recalc label position
       */

      mark += inc;
      sprintf(mark_s,"%d",(int) mark); 
      mark_x  = ticx1 - XTextWidth(t->info->fstruct,
				   mark_s,strlen(mark_s)) -5;
      ticy -= space;
    }

	
  /*
   * print the temp at the bottom
   */

  if(t->error)
    sprintf(mark_s,"ERROR");
  else
    {
      if(CELSIUS)
	sprintf(mark_s,"%d C",t->temperature);
      else
	sprintf(mark_s,"%d F",t->temperature);
    }

  ticx1 = (wat.width - XTextWidth(t->info->fstruct,
				  mark_s,strlen(mark_s)))/2;
  ticy  = wat.height - (t->info->fstruct->ascent -
			t->info->fstruct->descent)/2 + 5;
  
  XDrawImageString(display,t->w,t->gc,ticx1,ticy,mark_s,strlen(mark_s));  

  /*
   * print location on top
   */

  ticy = (t->info->fstruct->ascent - t->info->fstruct->descent)/2 + 10;
  if(location)
    {
      ticx1 = (wat.width - XTextWidth(t->info->fstruct,
				  location,strlen(location)))/2;
      XDrawImageString(display,t->w,t->gc,ticx1,ticy,location,
		       strlen(location)); 
    }
  else
    {
      ticx1 = (wat.width - XTextWidth(t->info->fstruct,
				      agent.name,strlen(agent.name)))/2;
      XDrawImageString(display,t->w,t->gc,ticx1,ticy,agent.name,
		       strlen(agent.name));  
    }
  XFlush(display);
}





/**********
 Function:    window_create()
 Description: creates a simple window with some modifications
 Returns:     the window id
***********/

Window
window_create(info)
     WInfo *info;
{
  Window w;
  XSetWindowAttributes changes;
  XSizeHints size_hint;
  XClassHint class_hint;

  size_hint.flags = PMinSize;
  size_hint.min_width = 5;
  size_hint.min_height = 10;
  if(info->geometry == 0)
	size_hint.flags |= PSize;
  else
     {
       size_hint.flags |= USSize;
       size_hint.flags |= USPosition;
     }

       size_hint.width = info->width;
       size_hint.height = info->height;
       size_hint.x = info->x;
       size_hint.y = info->y;
  
  /*
   * Simply create the window
   */

  w = XCreateSimpleWindow(display,Rootwin,info->x,info->y,
			  info->width, info->height,
			  info->border_width,
			  info->border,info->background);
  /*
   * If width && height are set, override redirect
   * ie: let it pop up when mapped
   */
  

  changes.backing_store = WhenMapped;

  XChangeWindowAttributes(display,w, 
			  CWBackingStore, &changes);


  /*
   * Name the window and icon
   */

  XSetStandardProperties(display,w,"xtherm","xtherm",None,NULL,0,&size_hint);
  class_hint.res_name = "xtherm";
  class_hint.res_class = "XTherm";
  XSetClassHint(display,w,&class_hint);

  return(w);
}





/*********
 Function:    gc_create()
 Description: creates gc based upon info
 Returns:     gc
**********/

GC
gc_create(w,info)
     Window w;
     WInfo *info;
{
  GC gc;
  XGCValues gcvalues;

  gcvalues.foreground = info->foreground;
  gcvalues.background = info->background;
  gcvalues.font = info->font;
  gcvalues.line_width = 1;
  gc = XCreateGC(display, w , GCFont | 
		 GCForeground | GCBackground |
		 GCLineWidth,
		 &gcvalues);
  
  
  return(gc);
}





/**********
 Function:    scale_compute()
 Description: returns the mimimum inc for given stuff
              starts at 1,2,5,10,15,20...
 Returns:     inc
**********/

scale_compute(height,space,delta)
     int height,space,delta;
{
  int inc;

  inc = 1;
  if((height * inc / delta) > space)
    return(inc);
  inc = 2;
  if((height * inc / delta) > space)
    return(inc);
  inc = 0;
  while(1)
    {
      if((height * inc / delta) > space)
	return(inc);
      if(inc < 100)
	inc += 10;
      else
	if(inc < 500)
	  inc += 100;
	else 
	  inc += 500;
    }
}




/**********
 Function:     parser()
 Description:  parses arguments 
 Returns:      info
***********/

parser(argc,argv,info)
     int *argc;
     char *argv[];
     WInfo *info;
{
  char displayname[100], *getenv();  
  char fontname[50], *fp = fontname;
  int i,x,y,w,h;
  XColor screen, exact;
  char *dpy;
  unsigned long laddr = 0;
  char *name;
  struct hostent *hp = (struct hostent *)NULL;

  /*
   * Open display
   */
  
  if(!(dpy = getenv("DISPLAY")))
    {
      fprintf(stderr, "cannot open display\n");
      exit(1);
    }

  strcpy(displayname,dpy);
  display = XOpenDisplay(displayname);
  
  /*
   * Get Display stuff
   */
  
  Black = XBlackPixel(display,0);
  White = XWhitePixel(display,0);
  Rootwin = XRootWindow(display,0);

  /*
   * Set Defaults
   */

  strcpy(fontname,"6x10");
  info->background = White;
  info->border = Black;
  info->foreground = Black;
  info->x = 50;
  info->y=  50;
  info->height = 480;
  info->width = 80;
  info->border_width = 1;
  info->geometry = 0;

/*  info->background = (int) XGetDefault(display, "xtherm", "background");
  info->border     = (int) XGetDefault(display, "xtherm", "border");
  info->foreground = (int) XGetDefault(display, "xtherm", "foreground");*/
  fp               = (char *) XGetDefault(display, "xtherm", "font");
  if(fp != (char *) NULL)
    strcpy(fp,fontname);

  bzero(&agent, sizeof(agent));

  /*
   * Parse arguments
   */
  
  for (i=1;i<*argc;i++)
    {

      if(argv[i][0]=='=')
	{
	  XParseGeometry(argv[i++],&x,&y,(unsigned int *)&w,(unsigned int *)&h);
	  info->x = x;
	  info->y = y;
	  info->width = w;
	  info->height = h;
	  info->geometry = 1;
	  continue;
	}

      if(argv[i][0]=='-')
	{
	  
	  if(!strcmp(argv[i],"-rv"))
	    {
	      info->background = Black;
	      info->foreground = White;
	      info->border = White;
	      continue;
	    } 
	  if(!strcmp(argv[i],"-fn"))
	    {
	      strcpy(fontname,argv[++i]);
	      continue;
	    }
	  
	  if(!strcmp(argv[i],"-bw"))
	    {
	      info->border_width = atoi(argv[++i]);
	      continue;
	    }
	  
	  if(!strcmp(argv[i],"-bg"))
	    {	      
	      XAllocNamedColor(display,
				DefaultColormap(display, 0),
				argv[++i], &screen, &exact);
	      info->background = screen.pixel;
	      continue;
	    }
	  
	  if(!strcmp(argv[i],"-fg"))
	    {	      
	      XAllocNamedColor(display,
				DefaultColormap(display, 0),
				argv[++i], &screen, &exact);
	      info->foreground = screen.pixel;
	      continue;
	    }

	  if(!strcmp(argv[i],"-bd"))
	    {	      
	      XAllocNamedColor(display,
				DefaultColormap(display, 0),
				argv[++i], &screen, &exact);
	      info->border = screen.pixel;
	      continue;
	    }

	  if(!strcmp(argv[i],"-t"))
	    {
	      printf("You expect this to work here?\n");
	      exit(1);
	    }
	  if(!strcmp(argv[i],"-c"))
	    CELSIUS = 1;
	  if(!strcmp(argv[i],"-help"))
	    usage(argv[0]);
	  continue;
	}

      /* do host */
      strcpy(agent.name, argv[i]);
      name = agent.name;
      if((*name >= '0') && (*name <= '9'))
	if((laddr = inet_addr(name)) > 0)
	  hp = gethostbyaddr((char *) &laddr, 4, AF_INET);
      
      if((hp == (struct hostent *) NULL) && (laddr == 0))
	if(!(hp = gethostbyname(name)))
	  {
	    fprintf(stderr, "Cannot resolve name \"%s\".\n", name);
	    exit(1);
	  }
      
      if(laddr > 0)
	agent.addr.s_addr = laddr;
      else
	bcopy(hp->h_addr, &(agent.addr.s_addr), hp->h_length);
    
    }
  
  if(!agent.name[0])
    {
      fprintf(stderr, "no agent specified\n");
      exit(1);
    }
  info->font = XLoadFont(display,fontname);
  info->fstruct = XLoadQueryFont(display, fontname);
}





/**********
 Function:    temperature_measure()
 Description: fingers weather@hermes for the temp.
***********/

temperature_measure(t)
     Thermometer *t;
{
  int tv;

  t->error = 0;
  tv = temperature_query(&agent);
  if(tv == -999)  /* don't you hate bogosity? */
    {
      t->error = 1;
      return;
    }
  if(!CELSIUS)
    t->temperature = (tv * 9/5) + 32;
  else
    t->temperature = tv;

  last_update = time(0);

  /*
   * update scale dynamically 
   * defaults hard coded for now (yeah, right)
   */

  if(t->temperature > 100)
    t->gage_max = (t->temperature/10 + 2) * 10;
  else
    t->gage_max = 100;
  if(t->temperature < 0)
    t->gage_min = (t->temperature/10 + 2) * 10;
  else
    t->gage_min = 0;

  return;
}



/**********
 Function:    usage()
 Description: prints program usage and exits
**********/

usage(prg)    /*ARGSUSED*/
     char *prg;
{
  printf("usage:  %s [-rv] [-c] [-t] [=widthxheight+xoff+yoff]\n           [-fn <font>] [-bw <bdr width] [-help] <agent>\n");
  exit(1);
}



/**********
 Function:    hangup_handler()
 Description: called when SIGHUP is detected
**********/

static int
hangup_handler(a)    /*ARGSUSED*/
     int a;
{
  signal(SIGALRM,SIG_DFL);
  exit(1);
}



/**********
 Function:    alarm_handler()
 Description: called when alarm sounds- sets
              the time_to_measure flag
**********/

static int
alarm_handler(a)  /*ARGSUSED*/
     int a;
{
  longjmp(env, 1);
}
















