/*
 * Xbox LED load monitor.
 * Copyright (C) Thomas "ShALLaX" Pedley (gentoox@shallax.com)
 *
 * 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 <fcntl.h>
#include "i2c-dev.h"
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "xbox.h"
#include <getopt.h>
#include <syslog.h>
#include <signal.h>

char *progname = NULL;
double x = 1.00, y = 2.00, z = 3.00;
char *L, *M, *H, *U;
int pid, verbosity;

int fd;
char *fname;
char *devfsname = "/dev/i2c/0";
char *nondevfsname = "/dev/i2c-0";

void checkPIC() {
	if ((fd = open(devfsname, O_RDWR)) < 0) {
		if ((fd=open(nondevfsname,O_RDWR))<0) {
			fprintf(stderr, "Error - failed to open EITHER /dev/i2c/0 or /dev/i2c-0\n    - you must be root and have the correct i2c modules loaded.\n");
			exit(1);
		} else {
			fname = malloc (strlen(nondevfsname));
			strncpy (fname, nondevfsname, strlen(nondevfsname));
		}
	} else {
		fname = malloc (strlen(devfsname));
		strncpy (fname, devfsname, strlen(devfsname));
	}
}


/* Prototypes. */
void loadavg(double *av1, double *av5, double *av15);
void setLED(char *pattern);
void updateLED();
void lockCheck();
void lockCreate();
void lockRemove();
void signalHandler(int sig);
void help();
void seekHelp();
int shutupCheck();

int main(int argc, char *argv[]) {
	int c;
   int pollInterval = 10;
	checkPIC();
   if ((fd = open(fname, O_RDWR)) < 0) {
      fprintf(stderr, "Error in open(%s): %s\n", fname, strerror(errno));
      lockRemove();
      return(1);
   }

   if (ioctl(fd,I2C_SLAVE_FORCE,PIC_ADDRESS) < 0) {
      fprintf(stderr,"Error: Could not set address to %d: %s\n",PIC_ADDRESS,strerror(errno));
      lockRemove();
      return(1);
   }

   close(fd);

   pid = getpid();
   verbosity = 0;

	L=malloc(5);
	M=malloc(5);
	H=malloc(5);
	U=malloc(5);

   progname = argv[0];

   strcpy(L, "gggg");
   strcpy(M, "ooox");
   strcpy(H, "rrrr");
   strcpy(U, "rxrx");

   while (1) {
      /* Arguments for the programme. */
      int option_index = 0;
      static struct option long_options[] = {
         {"lowload", 1, 0, 'x'},
         {"midload", 1, 0, 'y'},
         {"highload", 1, 0, 'z'},
         {"lowled", 1, 0, 'L'},
         {"midled", 1, 0, 'M'},
         {"highled", 1, 0, 'H'},
         {"uberled", 1, 0, 'U'},
         {"verbosity", 1, 0, 'v'},
         {"poll", 1, 0, 'p'},
         {"help", 0, 0, 'h'},
         {0, 0, 0, 0}
      };

      c = getopt_long(argc, argv, "x:y:z:L:M:H:U:v:p:?::h", long_options, &option_index);

      if (c == -1) {
         break;
      }
      switch (c) {
         case 'x':
            x = atof(optarg);
            if (x < 0) {
               fprintf(stderr, "Negative load is never going to happen.\n");
               seekHelp();
               exit(1);
            }
            
            break;
         case 'y':
            y = atof(optarg);
            if (y <= x) {
               fprintf(stderr, "midload is less than or the same as lowload\n");
               seekHelp();
               exit(1);
            }
            break;
         case 'z':
            z = atof(optarg);
            if (z <= y || z <= x) {
               fprintf(stderr, "highload is less than or the same as  midload or lowload\n");
               seekHelp();
               exit(1);
            }
            break;
         case 'L':
            memset(L, 0, 5);
            strncpy(L, optarg, 4);
            if (strspn(L, "rgox") != 4) {
               fprintf(stderr, "lowled contains invalid characters.\n");
               seekHelp();
               exit(1);
            } else if (strlen(L) != 4) {
               fprintf(stderr, "lowled contains an invalid number of characters.\n");
               seekHelp();
               exit(1);
            }
            break;
         case 'M':
            memset(M, 0, 5);
            strncpy(M, optarg, 4);
            if (strspn(M, "rgox") != 4) {
               fprintf(stderr, "midled contains invalid characters.\n");
               seekHelp();
               exit(1);
            } else if (strlen(M) != 4) {
               fprintf(stderr, "midled contains an invalid number of characters.\n");
               seekHelp();
               exit(1);
            }
            break;
         case 'H':
            memset(H, 0, 5);
            strncpy(H, optarg, 4);
            if (strspn(H, "rgox") != 4) {
               fprintf(stderr, "highled contains invalid characters.\n");
               seekHelp();
               exit(1);
            } else if (strlen(H) != 4) {
               fprintf(stderr, "highled contains an invalid number of characters.\n");
               seekHelp();
               exit(1);
            }
            break;
         case 'U':
            memset(U, 0, 5);
            strncpy(U, optarg, 4);
            if (strspn(U, "rgox") != 4) {
               fprintf(stderr, "uberled contains invalid characters.\n");
               seekHelp();
               exit(1);
            } else if (strlen(U) != 4) {
               fprintf(stderr, "uberled contains an invalid number of characters.\n");
               seekHelp();
               exit(1);
            }
            break;
         case 'v':
            verbosity = atoi(optarg);
            if (verbosity < 0) {
               fprintf(stderr, "verbosity too small.\n");
               seekHelp();
               exit(1);
            } else if (verbosity > 2) {
               fprintf(stderr, "verbosity too large.\n");
               seekHelp();
               exit(1);
            }
            break;
         case 'p':
            pollInterval = atoi(optarg);
            if (pollInterval < 1) {
               fprintf(stderr, "pollInterval too small.\n");
               seekHelp();
               exit(1);
            } else if (pollInterval > 300) {
               fprintf(stderr, "pollInterval too large.\n");
               seekHelp();
               exit(1);
            }
            break;
         case '?':
            seekHelp();
            exit(0);
            break;
         case 'h':
            help();
            break;
         /* Should never occur */
         default:
            break;
      }

   }

   /* Check the lockfile. */
   lockCheck();
 
   /* Create the lock file. */
   lockCreate();

   /* SIG handlers. */
   signal(SIGTERM, signalHandler);
   signal(SIGUSR1, signalHandler);
   signal(SIGQUIT, signalHandler);

 
   /* Begin forking. */
   switch(fork()) {
      case -1: /* Error occurred */
         fprintf(stderr,"%s: error forking off daemon process - %s",progname, strerror(errno));
         remove("/var/lock/loadmonitor.lock");
         exit(1);
      case 0: /* Forked child */
         pid = getpid();
         chdir("/");
         close (STDIN_FILENO);
         close (STDOUT_FILENO);
         close (STDERR_FILENO);
         break;
      default: /* Parent process */
         printf("Loadmonitor running.\n");
         if (verbosity >= 0 && verbosity < 1) {
            openlog(progname,LOG_NDELAY,LOG_LOCAL0);
            syslog(LOG_INFO,"loadmonitor[%i]: Started.", pid);
            closelog();
         } else if (verbosity >= 2) {
            openlog(progname,LOG_NDELAY,LOG_LOCAL0);
            syslog(LOG_INFO,"loadmonitor[%i]: Started - pollInterval: %d seconds - LowLoad: %.2f - MidLoad: %.2f - HighLoad: %.2f - LowLED: %s - MidLED: %s - HighLED: %s - UberLED: %s - Verbosity: %d", pid, pollInterval, x, y, z, L, M, H, U, verbosity);
            closelog();
         }

         openlog(progname,LOG_NDELAY,LOG_LOCAL0);
         syslog(LOG_INFO,"loadmonitor[%i]: child forked successfully.", pid);
         closelog();
         return 0;
   }

	while(1) {
      updateLED();
      sleep(pollInterval);
   }

}

void updateLED() {
   double av1, av5, av15;

   if(shutupCheck() == 0) {
      loadavg(&av1, &av5, &av15);
      openlog(progname,LOG_NDELAY,LOG_LOCAL0);
      if (av1 < x) {
         if (verbosity > 0 && verbosity <= 1) {
            syslog(LOG_INFO,"loadmonitor[%i]: Setting Low LED pattern (%s)", pid, L);
         } else if (verbosity >= 2) {
            syslog(LOG_INFO,"loadmonitor[%i]: Load (%0.2f) - Setting Low LED pattern (%s)", pid, av1, L);
         }
         setLED(L);
      } else if (av1 < y && av1 >= x) {
         if (verbosity > 0 && verbosity <= 1) {
            syslog(LOG_INFO,"loadmonitor[%i]: Setting Mid LED pattern (%s)", pid, M);
         } else if (verbosity >= 2) {
            syslog(LOG_INFO,"loadmonitor[%i]: Load (%0.2f) - Setting Mid LED pattern (%s)", pid, av1, M);
         }
         setLED(M);
      } else if (av1 < z && av1 >= y) {
         if (verbosity > 0 && verbosity <= 1) {
           syslog(LOG_INFO,"loadmonitor[%i]: Setting High LED pattern (%s)", pid, H);
         } else if (verbosity >= 2) {
            syslog(LOG_INFO,"loadmonitor[%i]: Load (%0.2f) - Setting High LED pattern (%s)", pid, av1, H);
         }
         setLED(H);
      } else if (av1 > z) {
         if (verbosity > 0 && verbosity <= 1) {
            syslog(LOG_INFO,"loadmonitor[%i]: Setting Low LED pattern (%s)", pid, U);
         } else if (verbosity >= 2) {
            syslog(LOG_INFO,"loadmonitor[%i]: Load (%0.2f) - Setting Uber LED pattern (%s)", pid, av1, U);
         }
         setLED(U);
      }
      closelog();
   }
}
void loadavg(double *av1, double *av5, double *av15) {
   char *loadavg_file = "/proc/loadavg";
   FILE *loadavg_fp = loadavg_fp = fopen(loadavg_file, "r");

   if (fscanf(loadavg_fp, "%lf %lf %lf", av1, av5, av15) < 3) {
      fprintf(stderr, "Invalid file: '%s'\n", loadavg_file);
   }

   fclose(loadavg_fp);
}

void setLED(char *pattern) {
   char *x;
   int fd, mode, reg, r, g;

   if ((fd = open(fname, O_RDWR)) < 0) {
      fprintf(stderr, "Error in open(%s): %s\n", fname, strerror(errno));
   }

   if (ioctl(fd,I2C_SLAVE_FORCE,PIC_ADDRESS) < 0) {
      fprintf(stderr,"Error: Could not set address to %d: %s\n",PIC_ADDRESS,strerror(errno));
   }

   mode = 1;
   x = pattern;
   r = g = 0;
   while (*x) {
      r *= 2;
      g *= 2;
      switch (*x) {
      case 'r' : r++; break;
      case 'g' : g++; break;
      case 'o' : r++; g++; break;
      }
      x++;
   }
   reg = ((r<<4) & 0xF0) + (g & 0xF);

   i2c_smbus_write_byte_data(fd,LED_MODE,mode);
   i2c_smbus_write_byte_data(fd,LED_REGISTER,reg);

   close(fd);
}

/* Check to see if a lockfile is in place, if it is, quit. */
void lockCheck() {
   FILE *fp;
   if ((fp = fopen("/var/lock/loadmonitor.lock", "r")) != NULL) {
      fprintf(stderr, "loadmonitor is already running or was killed.\nPlease kill the process (if it isnt dead already) and delete \n'/var/lock/loadmonitor.lock'\n");
      exit(1);
   }
}

/* Create the lockfile. */
void lockCreate() {
   FILE *fp;
   if ((fp = fopen("/var/lock/loadmonitor.lock", "w")) == NULL) {
      fprintf(stderr, "Cant open lockfile to write, are you root?\nDoes the directory '/var/lock' exist?\n");
      exit(1);
   } else {
      fclose(fp);
   }
}


/* Remove the lockfile */
void lockRemove() {
   remove("/var/lock/loadmonitor.lock");
}

/* Check to see if any of the silencing files exist. */
int shutupCheck() {
   FILE *fp;
   if ((fp = fopen("/tmp/.shutupled", "r")) != NULL) {
      if (verbosity >= 2) {
         openlog(progname,LOG_NDELAY,LOG_LOCAL0);
         syslog(LOG_INFO,"loadmonitor[%i]: /tmp/.shutupled exists - not changing LED pattern.", pid);
         closelog();
      }
      return 1;
   }

   if ((fp = fopen("/tmp/.receivingmail", "r")) != NULL) {
      if (verbosity >= 2) {
         openlog(progname,LOG_NDELAY,LOG_LOCAL0);
         syslog(LOG_INFO,"loadmonitor[%i]: /tmp/.receivingmail exists - not changing LED pattern.", pid);
         closelog();
      }
      return 1;
   }

   return 0;
}

/* Handle various signals. */
void signalHandler(int sig) {
   switch (sig) {
      /* SIGTERM kills the programme, cleans up, and sets the LED to 'gggg'. */
      case SIGTERM:
         openlog(progname,LOG_NDELAY,LOG_LOCAL0);
         syslog(LOG_INFO,"loadmonitor[%i]: Terminated - LED set to 'gggg'.", pid);
         closelog();
         /* Remove lockfile */
         remove("/var/lock/loadmonitor.lock");
         setLED("gggg");
         exit(0);
         break;
      /* SIGUSR1 set the LED now. */
      case SIGUSR1:
         updateLED();
         break;
      /* SIGQUIT kills the programme, cleans up, but doesnt change the LED. */
      case SIGQUIT:
         openlog(progname,LOG_NDELAY,LOG_LOCAL0);
         syslog(LOG_INFO,"loadmonitor[%i]: Terminated - LED unchanged.", pid);
         closelog();
         /* Remove lockfile */
         remove("/var/lock/loadmonitor.lock");
         exit(0);
         break;
      default:
         /* Should never get here. */
         break;
   }
}

void help() {
   printf("Loadmonitor by ShALLaX (c)\nThis tool allows you to set the Xbox's LED based on system load.\n");
   printf("\nSimple usage:\n");
   printf(" Just run the programme to use the built in defaults.\n");
   printf(" -h, --help        This screen\n");
   printf("\nAdvanced usage:\n");
   printf(" Switch           Description                       Values       Defaults\n");
   printf(" --------------------------------------------------------------------------\n");
   printf(" -x, --lowload    Set the low load threshold        See below    1.00\n");
   printf(" -y, --midload    Set the mid load threshold        See below    2.00\n");
   printf(" -z, --highload   Set the high load threshold       See below    3.00\n");
   printf(" -L, --lowled     Set the low load LED pattern      See below    'gggg'\n");
   printf(" -M, --midled     Set the mid load LED pattern      See below    'ooox'\n");
   printf(" -H, --highled    Set the high load LED pattern     See below    'rrrr'\n");
   printf(" -U, --uberled    Set the uber load LED pattern     See below    'rxrx'\n");
   printf(" -v, --verbosity  Set the daemon verbosity          (0, 1, 2)    0\n");
   printf(" -p, --poll       Set the poll interval (seconds)   (1 -> 300)   10\n");
   printf("\nRules:\n");
   printf(" Each subsequential load threshold must be higher than the last.\n");
   printf(" Load thresholds may be decimals but must be greater than or equal to 0.\n");
   printf(" Each LED pattern may only be 4 characters long and may only consist of the\n  characters:");
   printf(" 'r', 'g', 'o' and 'x' (where 'r' is red, 'g' is green, 'o' is\n  orange and 'x' is off).\n");
   printf("\n\n");
   printf("How it works:\n");
   printf(" lowled is shown when the load is below the low load threshold.\n");
   printf(" midled is shown when the load is between the low and mid load thresholds.\n");
   printf(" highled is shown when the load is between the mid and high load thresholds.\n");
   printf(" uberled is shown when the load is above the high load threshold.\n");
   printf("\nVerbosity\n");
   printf(" 0 = No logging except for at start and shutdown.\n");
   printf(" 1 = Initial settings output on run.\n");
   printf(" 2 = As above + syslog entries each time the LED changes (regardless of \n     previous state).\n");
   printf("\nInfo:\n");
   printf(" SIGTERM = Kill daemon and return LED to 'gggg'.\n SIGQUIT = Kill the daemon but don't change the LED.\n");
   printf(" SIGUSR1 = Update the LED now!\n");
   printf(" The LED will not update when any of the following files are present:\n");
   printf("  /tmp/.receivingmail\n");
   printf("  /tmp/.shutupled\n");
   exit(0);
}

void seekHelp() {
   fprintf(stderr, "See %s --help for details.\n", progname);
}

