/* $Id: glbiff.cc,v 1.23 2000/04/19 04:32:06 mac Exp $ */

/*
 * glbiff -- A Mesa/OpenGL-based `xbiff' substitute
 * Copyright (C) 2000  Maciej Kalisiak
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * 
 * This program 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
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 * 
 */

#include <stdio.h>
#include <stdlib.h>
#include <strings.h>

#include <unistd.h>
#include <sys/time.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/param.h>
#include <errno.h>

#include <GL/gl.h>
#include <GL/glx.h>

#include <X11/Xlib.h>

#include <iostream>

#include "draw.h"
#include "glbiff.h"
#include "mail.h"
#include "astro.h"
#include "cfg.h"

///////////////////////// structures
struct timed_cb_item {
  long	ms_delta;
  void (*cb)(void);
  timed_cb_item* next;
};
  
///////////////////////// globals

// if you don't like these defaults, change them, or use command lines 
// switches to choose different actions
char* cmd_mail_reader = strdup("xterm -e elm");	// default: bring up elm
char* cmd_new_mail = strdup("echo -n \a");	// default: ring bell
char* file_config = "~/.glbiffrc";	// default: ".glbiffrc" in home dir 
char* geom = "100x100";			// default: nice'n'small

int mail_count=0;		// determines how many evelopes will be drawn
bool unreadmail=false;		// is there any unread mail?

bool mail_reader_up = false;	// is the mail reader up?
pid_t child_pid;		// pid of mail-reader when it's up

// bezier patch control points of the round top of mailbox
GLfloat mbox_top_ctrlpts[4][4][3] = 
	{
	  {{0,0,0}, {0,0,1}, {1,0,1}, {1,0,0}},
	  {{0,0.33,0}, {0,0.33,1}, {1,0.33,1}, {1,0.33,0}},
	  {{0,0.66,0}, {0,0.66,1}, {1,0.66,1}, {1,0.66,0}},
	  {{0,1,0}, {0,1,1}, {1,1,1}, {1,1,0}},
	};

// bezier curve control points for the mailbox door
GLfloat mbox_door_ctrlpts[4][3] =
	{
	  {0,0,0}, {0,0,1}, {1,0,1}, {1,0,0},
	};

// X11 stuff
Display *dpy=NULL;
Window win=0;

timed_cb_item* sched_root = new timed_cb_item;
//sched_root->ms_delta=0;

timed_cb_item* callme_root = new timed_cb_item;

////////////////////////// code
/*
 * new_mail_arrived(): 	gets called whenever the arrival of new mail has
 * 			been detected.
 */
void new_mail_arrived(void) {
  if( cmd_new_mail )
    system(cmd_new_mail);
}

/*
 * check_mail(): this is the function to call when you need to reassess
 * 		the status of all mailboxes
 */
void check_mail(void) {

#ifdef DEBUG
  printf("-- checking mail\n");
#endif
  
  bool anymail, newmail;
  mail_count = check_all_mailboxes( anymail, unreadmail, newmail );
  if( newmail )
    new_mail_arrived();
  refresh();
}

void refresh(void) {
  redraw( dpy, win );
}

void set_alarm(long millisecs) {
#ifdef DEBUG
  printf("-- setting alarm for %ld ms\n", millisecs);
#endif
  
  struct itimerval itim;
  itim.it_interval.tv_sec = 0;
  itim.it_interval.tv_usec = 0;
  itim.it_value.tv_sec = millisecs / 1000;
  itim.it_value.tv_usec = (millisecs % 1000) * 1000;

  if (!itim.it_value.tv_usec)
    itim.it_value.tv_usec = 1;
  
  if (setitimer(ITIMER_REAL, &itim, NULL)) {
    char mess[256];
    sprintf(mess, "Problem setting the itimer to %ld secs, %ld usecs "
		  "(in set_alarm())",
             (long)itim.it_value.tv_sec, (long)itim.it_value.tv_usec );
    perror(mess);
  }
}

void timed_callback( void (*cb)(void), long millisecs ) {
#ifdef DEBUG
  printf("-- timed callback requested (%ld ms)\n", millisecs);
#endif
  
  timed_cb_item* p = sched_root;
  while( p->next ) {
    if( p->next->ms_delta > millisecs ) {
      break;
    } else {
      millisecs -= p->next->ms_delta;
      p = p->next;
    }
  }

  timed_cb_item* t = new timed_cb_item;
  t->next = p->next;
  t->ms_delta = millisecs;
  t->cb = cb;
  p->next = t;
  if( t->next )
    t->next->ms_delta -= t->ms_delta;
  // (re)start the timer if the requested callback is at front of list
  // WARNING: if item just placed is first, we have lost info on any time
  // elapsed on the previous first item; TODO: fix
  if( sched_root->next == t )
    set_alarm( t->ms_delta );
}

/*
 * alarm_handler(): handles SIGALRM
 */
void alarm_handler( int ) {
 
#ifdef DEBUG
  printf("Handling SIGALRM.\n");
#endif

  // assume that the ALARM was caused by the first item on the schedule
  timed_cb_item* p = sched_root->next;
  if( !p ) {
    printf("Unknown cause for alarm; schedule is hosed?\n");
    exit(1);
  }
  sched_root->next = p->next;

  if( sched_root->next )
    set_alarm( sched_root->next->ms_delta );

  // keep the mail_check alarm sequence going
  if( p->cb == check_mail )
    timed_callback( check_mail, check_interval*1000 );

  // place the callback on the "callme" list
  timed_cb_item* cur = callme_root;
  while( cur->next )
    cur = cur->next;
  cur->next = p;
  p->next = NULL;
}

/*
 * child_sig_handler(): handles SIGCHLD
 * 		It's main purpose is to watch for the mail reader
 * 		shutting down, and then taking appropriate action.
 */
void child_sig_handler( int ) {

#ifdef DEBUG
  printf("Handling SIGCHLD.\n");
#endif

  if( !child_pid ) {

#ifdef DEBUG
    printf("Child trying to handle a SIGCHILD. Ignoring...\n");
#endif

    return;
  }

  if( mail_reader_up ) {
    int status;
    pid_t res = waitpid( child_pid, &status, WNOHANG );
#ifdef DEBUG
    printf("waitpid() returned %d (child_pid==%d).\n",res,child_pid);
#endif

    if( res==child_pid ) {
#ifdef DEBUG
      printf("Detected mail program shutting down.\n");
#endif
      mail_reader_up = false;
      fDoorOpen = false;
      fLookHeadOn = false;
      check_mail();		// to reflect changes made by user in mboxes
      refresh();
    }
  }
}


/*
 * mouse_handler()
 *
 * handles mouse button events
 */
void mouse_event( int button, bool button_release ) {
  if( button_release && button==Button1 ) {
    // update mailbox status
    check_mail();
      
    if( !mail_reader_up ) {
      fDoorOpen = true;
      fLookHeadOn = true;
      refresh();

      // launch mail reader ...
      mail_reader_up = true;
      child_pid = fork();
      if( child_pid ) {	// parent thread
	// do nothing (for now)
#ifdef DEBUG
	printf("Parent doing nothing in fork.\n");
#endif
      } else {
	system( cmd_mail_reader );
	exit(0);
      }
    }
  }
  if( button_release && button==Button3 ) {
    if( !mail_reader_up ) {	// if mail reader is up, leave it alone
      // update mailbox status
      check_mail();
      
      if( !fDoorOpen ) {
	fDoorOpen = true;
	//	fXformOn = true;
	refresh();
      } else {
	fDoorOpen = false;
	//	fXformOn = false;
	refresh();
      }
    }
  }
}


/*
 * general_init()
 *
 * some basic initialization of Mesa/OpenGL; also installs the signal 
 * handlers.
 */
void general_init(void)
{
  /////////////////// OpenGL setup
  
  // setup lights
  GLfloat ambient[] = { 0.3, 0.3, 0.3, 1.0 };
  GLfloat diffuse[] = { 1.0, 1.0, 1.0, 1.0 };
//   GLfloat specular[] = { 1.0, 1.0, 1.0, 1.0 };
  GLfloat position[] = { 0.5, 1, 3.0, 0.0 };

  GLfloat lmodel_ambient[] = { 0.5, 0.5, 0.5, 1.0 };
  GLfloat local_view[] = { 0.0 };

  glLightfv(GL_LIGHT0, GL_AMBIENT, ambient);
  glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuse);
//   glLightfv(GL_LIGHT0, GL_SPECULAR, specular);
  glLightfv(GL_LIGHT0, GL_POSITION, position);
  glLightModelfv(GL_LIGHT_MODEL_AMBIENT, lmodel_ambient);
  glLightModelfv(GL_LIGHT_MODEL_LOCAL_VIEWER, local_view);

  ///// gotta figure out how to properly do this...
  //  glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, GL_TRUE);

  glFrontFace(GL_CW);
  glEnable(GL_LIGHTING);
  glEnable(GL_LIGHT0);
  glEnable(GL_AUTO_NORMAL);
  glEnable(GL_NORMALIZE);
  glEnable(GL_DEPTH_TEST);
  glDepthFunc(GL_LESS);

  glMap2f(GL_MAP2_VERTEX_3, 0, 1, 3, 4, 0, 1, 12, 4, (float*)mbox_top_ctrlpts);
  glMapGrid2f(DOME_SEGS, 0.0, 1.0, DOME_SEGS, 0.0, 1.0);

  glMap1f(GL_MAP1_VERTEX_3, 0, 1, 3, 4, (float*)mbox_door_ctrlpts);

  // texture stuff
  make_check_image();
  glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
  glGenTextures(1,&texName);
  glBindTexture(GL_TEXTURE_2D, texName);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
  glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, check_image_w,
	       check_image_h, 0, GL_RGBA, GL_UNSIGNED_BYTE, check_image);
  
  ////////// non-OGL stuff

  // read the configuration
  read_configuration(cfg_file());
  
  /////// signal handlers
  struct sigaction sig;

  // SIGALRM setup (used for waking up glbiff to check mailboxes again)
  sig.sa_handler = alarm_handler;
  sig.sa_flags = 0;
  sigemptyset( &sig.sa_mask );
  if( sigaction(SIGALRM, &sig, NULL) )
    printf("Error setting signal handler for SIGALRM (%d).\n", errno);
#ifdef DEBUG  
  else
    printf("SIGALRM handler installed ok.\n");
#endif

  // SIGCHLD setup (so that glbiff knows when email prog exits)
  sig.sa_handler = child_sig_handler;
  sig.sa_flags = 0;
  sigemptyset( &sig.sa_mask );
  if( sigaction(SIGCHLD, &sig, NULL) )
    printf("Error setting signal handler for SIGCHLD (%d).\n", errno);
#ifdef DEBUG  
  else
    printf("SIGCHLD handler installed ok.\n");
#endif  

  // start the timer running (this has to be done after SIGALRM setup!)
  struct itimerval itim;
  itim.it_interval.tv_sec = check_interval;
  itim.it_interval.tv_usec = 0;
  itim.it_value.tv_sec = check_interval;
  itim.it_value.tv_usec = 0;
  if(setitimer(ITIMER_REAL, &itim, NULL)) {
    char mess[256];
    sprintf(mess, "Problem setting the itimer to %ld secs, %ld usecs "
		  "(in general_init())",
             (long)itim.it_value.tv_sec, (long)itim.it_interval.tv_usec);
    perror(mess);
  }
#ifdef DEBUG  
  else
    fprintf(stderr, "Timer initialized ok.\n");
#endif  
}

/*
 * resize()
 *
 * This function gets called when the window is resized; (w,h) are the
 * new width and height.
 */
void resize(unsigned int w, unsigned int h)
{
  // typical
  glViewport(0, 0, w, h);
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();

  double D=0.2;
  glFrustum(-D,D,-D,D,0.5,100);
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
}

void syntax(void)
{
  cout << "Usage: glbiff [-h] [-v] [-n arg] [-m arg] [-f arg]" << endl
       << "       (see manpage for the details)" << endl;
  exit(0);
}

void version(void)
{
  cout << "glbiff v" << VERSION << endl;
  exit(0);
}

// stolen (and slightly modified) from "glxdemo.c" (in Mesa's ./xdemos)
static Window make_rgb_db_window(Display *dpy, int x, int y,
				 unsigned int width, unsigned int height)
{
  int attrib[] = { GLX_RGBA,
		   GLX_RED_SIZE, 1,
		   GLX_GREEN_SIZE, 1,
		   GLX_BLUE_SIZE, 1,
		   GLX_DOUBLEBUFFER,
		   GLX_DEPTH_SIZE, 1,
		   None };
  int scrnum;
  XSetWindowAttributes attr;
  unsigned long mask;
  Window root;
  Window win;
  GLXContext ctx;
  XVisualInfo *visinfo;

  scrnum = DefaultScreen(dpy);
  root = RootWindow(dpy, scrnum);

  visinfo = glXChooseVisual(dpy, scrnum, attrib);
  if(!visinfo) {
    printf("Error: couldn't get an RGB, Double-buffered visual\n");
    exit(1);
  }

  /* window attributes */
  attr.background_pixel = 0;
  attr.border_pixel = 0;
  attr.colormap = XCreateColormap(dpy, root, visinfo->visual, AllocNone);
  attr.event_mask = StructureNotifyMask | ExposureMask
    | ButtonPressMask | ButtonReleaseMask;
  mask = CWBackPixel | CWBorderPixel | CWColormap | CWEventMask;

//  cout << "Creating window at (" << x << "," << y << ")" << endl;
  win = XCreateWindow(dpy, root, x, y, width, height,
		      0, visinfo->depth, InputOutput,
		      visinfo->visual, mask, &attr);


  ctx = glXCreateContext(dpy, visinfo, NULL, True);

  glXMakeCurrent(dpy, win, ctx);

  return win;
}


// stolen (and slightly modified) from "glxdemo.c" (in Mesa's ./xdemos)
// and the select code from the "select" man page
static void event_loop( Display *dpy )
{
  for(;;) {
    while( XPending(dpy) ) {
      XEvent event;
      XNextEvent( dpy, &event );

      switch (event.type) {
	case Expose:
	  redraw( dpy, event.xany.window );
	  break;
	case ConfigureNotify:
	  resize( event.xconfigure.width, event.xconfigure.height );
	  break;
	case ButtonPress:
	  mouse_event( event.xbutton.button, false );
	  break;
	case ButtonRelease:
	  mouse_event( event.xbutton.button, true );
	  break;
      }
    }

    // see if any callbacks are awaiting invocation
    while (callme_root->next) {
#ifdef DEBUG
      printf("-- Calling a callback.\n");
#endif
      timed_cb_item* p = callme_root->next;
      p->cb();
      callme_root->next = p->next;
      delete p;
    }

    // wait a bit (or until a signal is received)
    fd_set rfds;
    struct timeval tv;
    int retval;

    // Watch stdout (fd 1) to see when it has input (never, which is
    // what we want). Is there a better way of doing this?
    FD_ZERO(&rfds);
//    FD_SET(1, &rfds);		// don't need this, and now it does not eat
//    cpu when the *term is killed from which this glbiff was started...
    const long sel_timeout_usec = 1000;
    tv.tv_sec = 0;
    tv.tv_usec = sel_timeout_usec;
    retval = select(2, &rfds, NULL, NULL, &tv);
    // Don't rely on the value of tv now! (according to "select" man
    // page, which is where this is ripped from)
  }
}


int main( int argc, char** argv )
{
  // parse the command line args
  for(int arg=1; arg<argc; arg++) {
    // show the command syntax
    if(!strcmp(argv[arg], "--help") || !strcmp(argv[arg], "-h")) {
      syntax();
    } else if(!strcmp(argv[arg], "--version") || !strcmp(argv[arg], "-v")) {
      version();
    } else if(!strcmp(argv[arg], "--newmail") || !strcmp(argv[arg], "-n")) {
      // what external program to run when new mail arrives
      arg++;
      if(arg>=argc)
	syntax();
      cmd_new_mail = strdup(argv[arg]);
    } else if(!strcmp(argv[arg], "--mailprog") || !strcmp(argv[arg], "-m")) {
      // what mail program to start when glbiff is left clicked
      arg++;
      if(arg>=argc)
	syntax();
      cmd_mail_reader = strdup(argv[arg]);
    } else if(!strcmp(argv[arg], "--cfgfile") || !strcmp(argv[arg], "-f")) {
      // which configuration file to read
      arg++;
      if(arg>=argc)
	syntax();
      file_config = strdup(argv[arg]);
    } else if(!strcmp(argv[arg], "--geometry") 
    		|| !strcmp(argv[arg], "-geometry")
    		|| !strcmp(argv[arg], "-geom")
		|| !strcmp(argv[arg], "-g")
		) {
      // X11 geometry specs
      arg++;
      if(arg>=argc)
	syntax();
      geom = strdup(argv[arg]);
    } else
      fprintf(stderr, "Ignoring unkown switch %s\n", argv[arg]);
  }

  // drop any path from the executable name
  char* slash=strrchr(argv[0], '/');
  if(slash) {
    strcpy(argv[0], slash+1);
  }

  // open the display
  dpy = XOpenDisplay(NULL);

  // parse the geometry string
  int geom_x=0, geom_y=0;
  unsigned int geom_w=100, geom_h=100;
  int geom_rc = XParseGeometry(geom, &geom_x, &geom_y, &geom_w, &geom_h);

//  int flags, x, y, width, height, i;
//  flags = XParseGeometry(geometry, &x, &y,
//      (unsigned int *) &width, (unsigned int *) &height);
  if (XValue & geom_rc && XNegative & geom_rc)
    geom_x = DisplayWidth(dpy, DefaultScreen(dpy)) + geom_x - geom_w;

  if (YValue & geom_rc && YNegative & geom_rc)
    geom_y = DisplayHeight(dpy, DefaultScreen(dpy)) + geom_y - geom_h;

  // bring up the window
  win = make_rgb_db_window(dpy, geom_x, geom_y, geom_w, geom_h);

  // configure the window title, class hint, etc...
  XStoreName(dpy, win, "glbiff");

  XClassHint *chint = XAllocClassHint();
  if (!chint) {
    cerr << "Couldn't XAllocClassHint()." << endl;
    exit(1);
  }
  chint->res_name="glbiff";
  chint->res_class="GLbiff";
  XSetClassHint( dpy, win, chint );
  XFree( chint );

  XWMHints* wmhints = XAllocWMHints();
  if (!wmhints) {
    cerr << "Couldn't XAllocWMHints()." << endl;
    exit(1);
  }
  wmhints->flags = WindowGroupHint;
  wmhints->window_group = win;
  XSetWMHints( dpy, win, wmhints );
  XFree( wmhints );

  XSetCommand( dpy, win, argv, argc );
  
  general_init();

//  XSizeHints sizeHints = {0};
//  sizeHints.width = geom_w;
//  sizeHints.height = geom_h;
//  sizeHints.x = geom_x;
//  sizeHints.y = geom_y;
//  XSetStandardProperties(dpy, win, "gLBIFF", "GLBIFF",
//      None, argv, argc, &sizeHints);

  XMapWindow (dpy, win);

  XMoveWindow (dpy, win, geom_x, geom_y);

  // start the check_mail trigger sequence 
  // (have mail checked *right away*)
  timed_callback( check_mail, 0 );

  event_loop( dpy );
  return 0;
}

  


syntax highlighted by Code2HTML, v. 0.9.1