//
//  Copyright (c) 1994, 1995, 2002, 2006 by Mike Romberg ( mike.romberg@noaa.gov )
//
//  This file may be distributed under terms of the GPL
//
//
// $Id$
//
#include <unistd.h>
#include <stdlib.h>
#include <sys/time.h>
#include "snprintf.h"
#include "general.h"
#include "xosview.h"
#include "meter.h"
#include "MeterMaker.h"
#if (defined(XOSVIEW_NETBSD) || defined(XOSVIEW_FREEBSD) || defined(XOSVIEW_OPENBSD))
#include "kernel.h"
#endif

static const char * const versionString = "xosview version: " XOSVIEW_VERSION;

static const char NAME[] = "xosview@";

#if !defined(__GNUC__)

#define MIN(x,y)		\
(				\
    x < y ? x : y		\
)

#define MAX(x,y)		\
(				\
    x > y ? x : y		\
)

#else

#define MIN(x,y)		\
({				\
    const typeof(x) _x = x;	\
    const typeof(y) _y = y;	\
				\
    (void) (&_x == &_y); 	\
				\
    _x < _y ? _x : _y;		\
})

#define MAX(x,y)		\
({				\
    const typeof(x) _x = x;	\
    const typeof(y) _y = y;	\
				\
    (void) (&_x == &_y); 	\
				\
    _x > _y ? _x : _y;		\
})

#endif // sgi

double MAX_SAMPLES_PER_SECOND = 10;

CVSID("$Id$");
CVSID_DOT_H(XOSVIEW_H_CVSID);


XOSView::XOSView( char * instName, int argc, char *argv[] ) : XWin(),
						xrm(Xrm("xosview", instName)){
  // Check for version arguments first.  This allows
  // them to work without the need for a connection
  // to the X server
  checkVersion(argc, argv);

  setDisplayName (xrm.getDisplayName( argc, argv));
  openDisplay();  //  So that the Xrm class can contact the display for its
		  //  default values.
  //  The resources need to be initialized before calling XWinInit, because
  //  XWinInit looks at the geometry resource for its geometry.  BCG
  xrm.loadAndMergeResources (argc, argv, display_);
  XWinInit (argc, argv, NULL, &xrm);
#if 1	//  Don't enable this yet.
  MAX_SAMPLES_PER_SECOND = atof(getResource("samplesPerSec"));
  if (!MAX_SAMPLES_PER_SECOND)
    MAX_SAMPLES_PER_SECOND = 10;
#endif
  usleeptime_ = (unsigned long) (1000000/MAX_SAMPLES_PER_SECOND);
  if (usleeptime_ >= 1000000) {
    /*  The syscall usleep() only takes times less than 1 sec, so
     *  split into a sleep time and a usleep time if needed.  */
    sleeptime_ = usleeptime_ / 1000000;
    usleeptime_ = usleeptime_ % 1000000;
  } else { sleeptime_ = 0; }
#if (defined(XOSVIEW_NETBSD) || defined(XOSVIEW_FREEBSD) || defined(XOSVIEW_OPENBSD))
  BSDInit();	/*  Needs to be done before processing of -N option.  */
#endif

  hmargin_  = atoi(getResource("horizontalMargin"));
  vmargin_  = atoi(getResource("verticalMargin"));
  vspacing_ = atoi(getResource("verticalSpacing"));
  hmargin_  = MAX(0, hmargin_);
  vmargin_  = MAX(0, vmargin_);
  vspacing_ = MAX(0, vspacing_);

  checkArgs (argc, argv);  //  Check for any other unhandled args.
  xoff_ = hmargin_;
  yoff_ = 0;
  nummeters_ = 0;
  meters_ = NULL;
  name_ = "xosview";
  _isvisible = false;
  _ispartiallyvisible = false;
  exposed_once_flag_ = 0;

  expose_flag_ = 1;

  //  set up the X events
  addEvent( new Event( this, ConfigureNotify,
		      (EventCallBack)&XOSView::resizeEvent ) );
  addEvent( new Event( this, Expose,
		      (EventCallBack)&XOSView::exposeEvent ) );
  addEvent( new Event( this, KeyPress,
		      (EventCallBack)&XOSView::keyPressEvent ) );
  addEvent( new Event( this, VisibilityNotify,
                      (EventCallBack)&XOSView::visibilityEvent ) );
  addEvent( new Event( this, UnmapNotify,
                      (EventCallBack)&XOSView::unmapEvent ) );

  // add or change the Resources
  MeterMaker mm(this);

  // see if legends are to be used
  checkOverallResources ();

  // add in the meters
  mm.makeMeters();
  for (int i = 1 ; i <= mm.n() ; i++)
    addmeter(mm[i]);

  if (nummeters_ == 0)
  {
    fprintf (stderr, "No meters were enabled!  Exiting...\n");
    exit (0);
  }

  //  Have the meters re-check the resources.
  checkMeterResources();

  // determine the width and height of the window then create it
  figureSize();
  init( argc, argv );
  title( winname() );
  iconname( winname() );
  dolegends();
  resize();
}

void XOSView::checkVersion(int argc, char *argv[]) const
    {
    for (int i = 0 ; i < argc ; i++)
        if (!strncasecmp(argv[i], "-v", 2)
          || !strncasecmp(argv[i], "--version", 10))
            {
            std::cerr << versionString << std::endl;
            exit(0);
            }
    }

void XOSView::figureSize ( void ) {
  if ( legend_ ){
    if ( !usedlabels_ )
      xoff_ = textWidth( "XXXXX" );
    else
      xoff_ = textWidth( "XXXXXXXXX" );

    yoff_ = caption_ ? textHeight() + textHeight() / 4 : 0;
  }
  static int firsttime = 1;
  if (firsttime) {
    firsttime = 0;
    width_ = findx();
    height_ = findy();
  }
  else
  {
  }
}

void XOSView::checkMeterResources( void ){
  MeterNode *tmp = meters_;

  while ( tmp != NULL ){
    tmp->meter_->checkResources();
    tmp = tmp->next_;
  }
}

int XOSView::newypos( void ){
  return 15 + 25 * nummeters_;
}

void XOSView::dolegends( void ){
  MeterNode *tmp = meters_;
  while ( tmp != NULL ){
    tmp->meter_->docaptions( caption_ );
    tmp->meter_->dolegends( legend_ );
    tmp->meter_->dousedlegends( usedlabels_ );
    tmp = tmp->next_;
  }
}

void XOSView::addmeter( Meter *fm ){
  MeterNode *tmp = meters_;

  if ( meters_ == NULL )
    meters_ = new MeterNode( fm );
  else {
    while ( tmp->next_ != NULL )
      tmp = tmp->next_;
    tmp->next_ = new MeterNode( fm );
  }
  nummeters_++;
}

int XOSView::findx( void ){
  if ( legend_ ){
    if ( !usedlabels_ )
      return textWidth( "XXXXXXXXXXXXXXXXXXXXXXXX" );
    else
      return textWidth( "XXXXXXXXXXXXXXXXXXXXXXXXXXXXX" );
  }
  return 80;
}

int XOSView::findy( void ){
  if ( legend_ )
    return 10 + textHeight() * nummeters_ * ( caption_ ? 2 : 1 );

  return 15 * nummeters_;
}

void XOSView::checkOverallResources() {
  //  Check various resource values.

  //  Set 'off' value.  This is not necessarily a default value --
  //    the value in the defaultXResourceString is the default value.
  usedlabels_ = legend_ = caption_ = 0;

  setFont();

   // use captions
  if ( isResourceTrue("captions") )
      caption_ = 1;

  // use labels
  if ( isResourceTrue("labels") )
      legend_ = 1;

  // use "free" labels
  if ( isResourceTrue("usedlabels") )
      usedlabels_ = 1;
}

const char *XOSView::winname( void ){
  char host[100];
  gethostname( host, 99 );
  static char name[101];	/*  We return a pointer to this,
				    so it can't be local.  */
  snprintf( name, 100, "%s%s", NAME, host);
  //  Allow overriding of this name through the -title option.
  return getResourceOrUseDefault ("title", name);
}


void  XOSView::resize( void ){
  int spacing = vspacing_+1;
  int topmargin = vmargin_;
  int rightmargin = hmargin_;
  int newwidth = width_ - xoff_ - rightmargin;
/*
  int newheight = (height_ - (10 + 5 * (nummeters_ - 1) +
                              nummeters_ * yoff_)) / nummeters_;
*/
  int newheight =
        (height_ -
         (topmargin + topmargin + (nummeters_-1)*spacing + nummeters_*yoff_)
        ) / nummeters_;
  newheight = (newheight >= 2) ? newheight : 2;


  int counter = 1;
  MeterNode *tmp = meters_;
  while ( tmp != NULL ) {
/*
    tmp->meter_->resize( xoff_, 5 * counter + counter * yoff_ +
                         (counter - 1) * newheight, newwidth, newheight );
*/
    tmp->meter_->resize( xoff_,
                         topmargin + counter*yoff_ + (counter-1)*(newheight+spacing),
                        newwidth, newheight );
    tmp = tmp->next_;

    counter++;
  }
}


XOSView::~XOSView( void ){
  MeterNode *tmp = meters_;
  while ( tmp != NULL ){
    MeterNode *save = tmp->next_;
    delete tmp->meter_;
    delete tmp;
    tmp = save;
  }
}

void XOSView::reallydraw( void ){
  XOSDEBUG("Doing draw.\n");
  clear();
  MeterNode *tmp = meters_;

  while ( tmp != NULL ){
    tmp->meter_->draw();
    tmp = tmp->next_;
  }
  flush();

  expose_flag_ = 0;
}

void XOSView::draw ( void ) {
  if (hasBeenExposedAtLeastOnce() && isAtLeastPartiallyVisible())
    reallydraw();
  else {
    if (!hasBeenExposedAtLeastOnce()) {
      XOSDEBUG("Skipping draw:  not yet exposed.\n");
    } else if (!isAtLeastPartiallyVisible()) {
      XOSDEBUG("Skipping draw:  not visible.\n");
    }
  }
}
void XOSView::keyrelease( char *ch ){
/*  WARNING:  This code is not called by anything.  */
(void) ch;  /*  To avoid gcc warnings.  */
}

void XOSView::run( void ){
  int counter = 0;

  while( !done_ ){
    checkevent();

    if (_isvisible){
      MeterNode *tmp = meters_;
      while ( tmp != NULL ){
        if ( tmp->meter_->requestevent() )
          tmp->meter_->checkevent();
        tmp = tmp->next_;
      }

      flush();
    }
#ifdef HAVE_USLEEP
    /*  First, sleep for the proper integral number of seconds --
     *  usleep only deals with times less than 1 sec.  */
    if (sleeptime_) sleep((unsigned int)sleeptime_);
    if (usleeptime_) usleep( (unsigned int)usleeptime_);
#else
    usleep_via_select ( usleeptime_ );
#endif
    counter = (counter + 1) % 5;
  }
}

void XOSView::usleep_via_select( unsigned long usec ){
  struct timeval time;

  time.tv_sec = (int)(usec / 1000000);
  time.tv_usec = usec - time.tv_sec * 1000000;

  select( 0, 0, 0, 0, &time );
}

void XOSView::keyPressEvent( XKeyEvent &event ){
  char c = 0;
  KeySym key;

  XLookupString( &event, &c, 1, &key, NULL );

  if ( (c == 'q') || (c == 'Q') )
    done_ = 1;
}

void XOSView::checkArgs (int argc, char** argv) const
{
  //  The XWin constructor call in the XOSView constructor above
  //  modifies argc and argv, so by this
  //  point, all XResource arguments should be removed.  Since we currently
  //  don't have any other command-line arguments, perform a check here
  //  to make sure we don't get any more.
  if (argc == 1) return;  //  No arguments besides X resources.

  //  Skip to the first real argument.
  argc--;
  argv++;
  while (argc > 0 && argv && *argv)
  {
    switch (argv[0][1]) {
      case 'n': //  Check for -name option that was already parsed
		//  and acted upon by main().
		if (!strncasecmp(*argv, "-name", 6))
		{
		  argv++;	//  Skip arg to -name.
		  argc--;
		}
		break;
#if (defined(XOSVIEW_NETBSD) || defined(XOSVIEW_FREEBSD) || defined(XOSVIEW_OPENBSD))
      case 'N': if (strlen(argv[0]) > 2)
      		  SetKernelName(argv[0]+2);
		else
		{
		  SetKernelName(argv[1]);
		  argc--;
		  argv++;
		}
		break;
#endif
	      /*  Fall through to default/error case.  */
      default:
      		std::cerr << "Ignoring unknown option '" << argv[0] << "'.\n";
	  	break;
    }
    argc--;
    argv++;
  }
}

void XOSView::exposeEvent( XExposeEvent &event ) {
  _isvisible = true;
  if ( event.count == 0 )
  {
    expose_flag_++;
    draw();
  }
  XOSDEBUG("Got expose event.\n");
  if (!exposed_once_flag_) { exposed_once_flag_ = 1; draw(); }
}

void XOSView::resizeEvent( XEvent & ) {
  resize();
  expose_flag_++;
  draw();
}


void XOSView::visibilityEvent( XVisibilityEvent &event ){
  _ispartiallyvisible = false;
  if (event.state == VisibilityPartiallyObscured){
    _ispartiallyvisible = true;
  }

  if (event.state == VisibilityFullyObscured){
    _isvisible = false;
  }
  else {
    _isvisible = true;
  }
  XOSDEBUG("Got visibility event; %d and %d\n",
      _ispartiallyvisible, _isvisible);
}

void XOSView::unmapEvent( XUnmapEvent & ){
  _isvisible = false;
}
