/* XPENGUINS - cool little penguins that walk along the tops of your windows
 * Copyright (C) 1999, 2000  Robin Hogan
 *
 *  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
 *
 *
 * XPENGUINS is a kind of a cross between lemmings and xsnow - penguins
 * fall from the top of the screen and walk across the tops of windows.
 * The images are take from `Pingus', a lemmings-type game for Linux.
 * All the nasty X calls are in `toon.c', so xpenguins.c should be easily
 * modifiable to animate any small furry (or feathery) animal of your choice.
 * 
 * Robin Hogan <R.J.Hogan@reading.ac.uk>
 * Project started 22 November 1999
 * This version 29 October 2000: xpenguins.c is not a standalone program
 *   souce file, but contains functions which can be called from the
 *   xpenguins_applet.
 */

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>

/* C structures defined here */
#include "toon.h"
#include "vroot.h"

/*
 * The penguin shapes are defined here - includes links to all the xpm images.
 * If you want to have something else falling from the top of your screen (the
 * possibilities are endless!) then point the following line somewhere else. 
 */
#include "penguins/def.h"

#define MAX_PENGUINS 256
#define DEFAULT_DELAY 50
#define JUMP_DISTANCE 8
#define RandInt(maxint) ((int) ((maxint)*((float) rand()/(RAND_MAX+1.0))))

#define XPENGUINS_VERSION "1.2"
#define XPENGUINS_AUTHOR "Robin Hogan"
#define XPENGUINS_DATE "22 May 2000"

#define DEBUG fprintf(stderr,"xpenguins.c: %d\n",__LINE__)

/* A few macros... */

#define InitPenguin(penguin) \
   ToonSetType(penguin, PENGUIN_FALLER, PENGUIN_FORWARD, \
         TOON_UNASSOCIATED); \
   ToonSetPosition(penguin, RandInt(ToonDisplayWidth() \
         - PENGUIN_DEFAULTWIDTH), 1-PENGUIN_DEFAULTHEIGHT); \
   ToonSetAssociation(penguin, TOON_UNASSOCIATED); \
   ToonSetVelocity(penguin, RandInt(2)*2-1, 3)

#define MakeClimber(penguin) \
   ToonSetType(penguin, PENGUIN_CLIMBER, (penguin)->direction, \
         TOON_DOWN); \
   ToonSetAssociation(penguin, (penguin)->direction); \
   ToonSetVelocity((penguin),0,-4)

#define MakeWalker(penguin) \
   ToonSetType(penguin, PENGUIN_WALKER, \
         (penguin)->direction,TOON_DOWN); \
   ToonSetAssociation(penguin, TOON_DOWN); \
   ToonSetVelocity(penguin, 4*((2*(penguin)->direction)-1), 0)

#define MakeFaller(penguin) \
   ToonSetVelocity(penguin, ((penguin)->direction)*2-1, 3); \
   ToonSetType(penguin, PENGUIN_FALLER, \
         PENGUIN_FORWARD,TOON_DOWN); \
   ToonSetAssociation(penguin, TOON_UNASSOCIATED)


/* Global variables */
int penguin_number = 8;
Toon penguin[MAX_PENGUINS];
unsigned long penguin_sleep_usec=DEFAULT_DELAY*1000;
char penguin_prefd[MAX_PENGUINS]; /* preferred direction; -1 means none */
char penguin_prefclimb[MAX_PENGUINS]; /* climbs when possible */
char penguin_holdon[MAX_PENGUINS];
char penguin_active = 0;

int
xpenguins_init(char *display_name) {
  if (!penguin_active) {
    int i;
    unsigned long configure_mask = TOON_SIDEBOTTOMBLOCK;

    /* reset random-number generator */
    srand(time((long *) NULL));
    ToonOpenDisplay(display_name);

    /* Set up various preferences: Edge of screen is solid,
     * and if a signal is caught then exit the main event loop */
    ToonConfigure(configure_mask);

    /* Set the distance the window can move (up, down, left, right)
     * and penguin can still cling on */
    ToonSetMaximumRelocate(16,16,16,16);

    /* Send the pixmaps to the X server - penguin_data should have been 
     * defined in penguins/def.h */
    ToonInstallData(penguin_data,PENGUIN_TYPES);

    /* initialise penguins */
    for (i=0;i<penguin_number;i++) {
      penguin_prefd[i] = -1;
      penguin_prefclimb[i] = 0;
      penguin_holdon[i] = 0;
      InitPenguin(penguin+i);
    }

    /* Find out where the windows are - should be done 
     * just before beginning the event loop */
    ToonLocateWindows();
  }
  penguin_active = 1;
  return(1);
}

void
xpenguins_ignorepopups(char yn) {
  if (yn) {
    ToonConfigure(TOON_NOSOLIDPOPUPS);
  }
  else {
    ToonConfigure(TOON_SOLIDPOPUPS);
  }
  if (penguin_active) {
    ToonCalculateAssociations(penguin,penguin_number);
    ToonLocateWindows();
    ToonRelocateAssociated(penguin,penguin_number);
  }
}

void
xpenguins_set_number(int n) {
  int i;
  if (penguin_active) {
    if (n > penguin_number) {
      int i;
      if (n > MAX_PENGUINS) {
	n = MAX_PENGUINS;
      }
      for (i = penguin_number; i<n; i++) {
	InitPenguin(penguin+i);			
      }
    }
    else if (n < penguin_number) {
      if (n < 1) {
	n = 1;
      }
      for (i = n; i < penguin_number; ++i) {
	ToonSetType(penguin+i,PENGUIN_EXPLOSION,
		    PENGUIN_FORWARD,TOON_HERE);
      }
      ToonErase(penguin, penguin_number);
      ToonDraw(penguin, penguin_number);
      ToonFlush();
      ToonSleep(penguin_sleep_usec);
      ToonErase(penguin, penguin_number);
      ToonDraw(penguin, n);
    }
  }
  penguin_number = n;
}

void
xpenguins_pause_frame() {
  if (!penguin_active) {
    return;
  }

  /* check if windows have moved */
  if ( ToonWindowsMoved() ) {
    ToonErase(penguin,penguin_number);
    ToonDraw(penguin,penguin_number);
    ToonLocateWindows();
  }
}

void
xpenguins_frame() {
  int status, i, direction;
  if (!penguin_active) {
    return;
  }

  /* check if windows have moved, and flush the display */
  if ( ToonWindowsMoved() ) {
    /* if so, check for squashed toons */
    ToonCalculateAssociations(penguin,penguin_number);
    ToonLocateWindows();
    ToonRelocateAssociated(penguin,penguin_number);
  }
  for (i=0;i<penguin_number;i++) {
    if (!penguin[i].active) {
      InitPenguin(penguin+i);
      continue;
    }
    else {
      if (ToonBlocked(penguin+i,TOON_HERE)) {
	ToonSetType(penguin+i,PENGUIN_EXPLOSION,
		    PENGUIN_FORWARD,TOON_HERE);
	ToonSetAssociation(penguin+i, TOON_UNASSOCIATED);
      }

      status=ToonAdvance(penguin+i,TOON_MOVE);
      switch (penguin[i].type) {
      case PENGUIN_FALLER:
	if (status != TOON_OK) {
	  if (ToonBlocked(penguin+i,TOON_DOWN)) {
	    if (penguin_prefd[i]>-1)
	      penguin[i].direction=penguin_prefd[i];
	    else
	      penguin[i].direction=RandInt(2);
	    MakeWalker(penguin+i);
	    penguin_prefd[i]=-1;
	  }
	  else {
	    if (RandInt(2)) {
	      ToonSetVelocity(penguin+i,-penguin[i].u,3);
	    }
	    else {
	      penguin[i].direction = penguin[i].u>0;
	      MakeClimber(penguin+i);
	    }
	  }
	}
	break;

      case PENGUIN_TUMBLER:
	if (status != TOON_OK) {
	  if (penguin_prefd[i]>-1)
	    penguin[i].direction=penguin_prefd[i];
	  else
	    penguin[i].direction=RandInt(2);
	  MakeWalker(penguin+i);
	  penguin_prefd[i]=-1;
	}
	else if (penguin[i].v < 8) {
	  penguin[i].v +=1;
	}
	break;

      case PENGUIN_WALKER:
	if (status != TOON_OK) {
	  if (status == TOON_BLOCKED) {
	    /* Try to step up... */
	    int u = penguin[i].u;
	    if (!ToonOffsetBlocked(penguin+i, u, -JUMP_DISTANCE)) {
	      ToonMove(penguin+i, u, -JUMP_DISTANCE);
	      ToonSetVelocity(penguin+i, 0, JUMP_DISTANCE-1);
	      ToonAdvance(penguin+i, TOON_MOVE);
	      ToonSetVelocity(penguin+i, u, 0);
	    }
	    else {
	      /* Blocked! We can turn round, fly or climb... */
	      switch (RandInt(8)*(1-penguin_prefclimb[i])) {
	      case 0:
		MakeClimber(penguin+i);
		break;
	      case 1:
		ToonSetType(penguin+i,PENGUIN_FLOATER,
			    PENGUIN_FORWARD,TOON_DOWN);
		ToonSetAssociation(penguin+i, TOON_UNASSOCIATED);
		ToonSetVelocity(penguin+i,RandInt(5)
				* (-penguin[i].u/4),-3);
		break;
	      default:
		penguin[i].direction = (!penguin[i].direction);
		MakeWalker(penguin+i);
	      }
	    }
	  }
	}
	else if (!ToonBlocked(penguin+i,TOON_DOWN)) {
	  /* Try to step down... */
	  ToonSetVelocity(penguin+i, 0, JUMP_DISTANCE);
	  status=ToonAdvance(penguin+i,TOON_MOVE);
	  if (status == TOON_OK) {
	    penguin_prefd[i]=penguin[i].direction;
	    ToonSetType(penguin+i, PENGUIN_TUMBLER,
			PENGUIN_FORWARD,TOON_DOWN);
	    ToonSetAssociation(penguin+i, TOON_UNASSOCIATED);
	    ToonSetVelocity(penguin+i, 0, 1);
	    penguin_prefclimb[i]=0;
	  }
	  else {
	    ToonSetVelocity(penguin+i, 4*((2*penguin[i].direction)-1), 0);
	  }
	}
	break;

      case PENGUIN_CLIMBER:
	direction = penguin[i].direction;
	if (penguin[i].y < 0) {
	  penguin[i].direction = (!direction);
	  MakeFaller(penguin+i);
	  penguin_prefclimb[i]=0;
	}
	else if (status == TOON_BLOCKED) {
	  /* Try to step out... */
	  int v = penguin[i].v;
	  int xoffset = (1-direction*2) * JUMP_DISTANCE;
	  if (!ToonOffsetBlocked(penguin+i, xoffset, v)) {
	    ToonMove(penguin+i, xoffset, v);
	    ToonSetVelocity(penguin+i, -xoffset-(1-direction*2), 0);
	    ToonAdvance(penguin+i, TOON_MOVE);
	    ToonSetVelocity(penguin+i, 0, v);
	  }
	  else {
	    penguin[i].direction = (!direction);
	    MakeFaller(penguin+i);
	    penguin_prefclimb[i]=0;
	  }
	}
	else if (!ToonBlocked(penguin+i,direction)) {
	  if (ToonOffsetBlocked(penguin+i, ((2*direction)-1)
				* JUMP_DISTANCE, 0)) {
	    ToonSetVelocity(penguin+i, ((2*direction)-1)
			    * (JUMP_DISTANCE-1), 0);
	    ToonAdvance(penguin+i, TOON_MOVE);
	    ToonSetVelocity(penguin+i, 0, -4);
	  }
	  else {
	    MakeWalker(penguin+i);
	    ToonSetPosition(penguin+i, penguin[i].x+(2*direction)-1,
			    penguin[i].y);
	    penguin_prefd[i]=direction;
	    penguin_prefclimb[i]=1;
	  }
	}
	break;

      case PENGUIN_FLOATER:
	if (penguin[i].y < 0) {
	  penguin[i].direction = (penguin[i].u>0);
	  MakeFaller(penguin+i);
	}
	else if (status != TOON_OK) {
	  if (ToonBlocked(penguin+i,TOON_UP)) {
	    penguin[i].direction = (penguin[i].u>0);
	    MakeFaller(penguin+i);
	  }
	  else {
	    ToonSetVelocity(penguin+i,-penguin[i].u, -3);
	  }
	}
	break;

      case PENGUIN_EXPLOSION:
	if (!penguin_holdon[i]) {
	  penguin_holdon[i] = 1;
	}
	else {
	  penguin[i].active=0;
	  penguin_holdon[i]=0;
	}
      }
    }
  }
  /* First erase them all, then draw them all - should reduce flickering */
  ToonErase(penguin,penguin_number);
  ToonDraw(penguin,penguin_number);
  ToonFlush();
  /* pause */
  /* ToonSleep(penguin_sleep_usec);*/
}


/* Exit sequence... */
void
xpenguins_exit() {
  int i, n;
  if (!penguin_active) {
    return;
  }
  /* Nice exit sequence... */
  for (i=0;i<penguin_number;i++) {
    if (penguin[i].active) {
      ToonSetType(penguin+i,PENGUIN_BOMBER,
                  PENGUIN_FORWARD,TOON_DOWN);
    }
  }
  for (n=0;n<penguin_data[PENGUIN_BOMBER].nframes;n++) {
    ToonErase(penguin,penguin_number);
    ToonDraw(penguin,penguin_number);
    ToonFlush();
    for (i=0;i<penguin_number;i++) {
      ToonAdvance(penguin+i,TOON_FORCE);
    }
    ToonSleep(penguin_sleep_usec);
  }
  ToonErase(penguin,penguin_number);
  ToonCloseDisplay();
  penguin_active = 0;
}

