#include <stdio.h>        // printf, fprintf, fscanf, fopen, fclose
#include <unistd.h>       // sleep, close
#include <string.h>       // strncpy
#include <time.h>         // strftime
#include <sys/statvfs.h>  // statvfs
#include <linux/socket.h> // socket
#include <sys/ioctl.h>    // ioctl
#include <net/if.h>       // ifreq
#include <netinet/in.h>   // in_addr
#include <arpa/inet.h>    // inet_ntoa
#include <stdarg.h>       // vsnprintf
#include <libg15render.h>
#include <g15daemon_client.h>

int screen;                     // g15 screen device
g15canvas canvas, empty_canvas; // g15 canvas

#define CLOCKLEN 22 // length of clock buffer
#define LINELEN 256 // length of line buffer

void g15printf(g15canvas *canvas, int x, int y, const char *format, ...)
{
  static char line[LINELEN];
  va_list ap;
  // print into the buffer
  va_start(ap, format);
  vsnprintf(line, LINELEN, format, ap);
  va_end(ap);
  // print to the canvas
  g15r_renderString(canvas, line, 0, G15_TEXT_SMALL, x, y);
}

/* UPDATE_CLOCK: returns seconds until next change */

int update_clock ()
{
  const char *format = "%a %d %b, %H:%M:%S"; // format of date output
  char clockbuf[CLOCKLEN];                   // buffer for clock string
  time_t time_now; struct tm tm_now;         // current time 

  time(&time_now);
  if (localtime_r(&time_now, &tm_now) != NULL &&
      strftime(clockbuf, CLOCKLEN, format, &tm_now) == 20)
    g15r_renderString(&canvas, clockbuf, 0, G15_TEXT_LARGE, 0, 0);

  return 60 - tm_now.tm_sec;
}

/* UPDATE_PROCINFO */

void update_procinfo ()
{
  FILE *F;
  double oneminute, fiveminute, fifteenminute; // 1,5 & 15 minute load averages
  int running, total = 0;                      // number of running processes
  int uptime = 0;                              // uptime
  int thrm;                                    // current CPU temperature
  // read data from /proc
  if ((F = fopen("/proc/loadavg", "r")) != NULL)
    {
      fscanf(F, "%lf %lf %lf %u/%u ", &oneminute, &fiveminute,
	     &fifteenminute, &running, &total);
      fclose(F);
    }
  if ((F = fopen("/proc/uptime", "r")) != NULL)
    {
      fscanf(F, "%u.", &uptime);
      fclose(F);
    }
  if (total && uptime) // output result
    {
      int days = uptime / (60 * 60 * 24);
      int hours = uptime / (60 * 60) % 24;
      int minutes = uptime / 60 % 60;
      char clockbuf[CLOCKLEN];

      if (days > 1)
	snprintf(clockbuf, CLOCKLEN, "%u days %u:%02u", days, hours, minutes);
      else if (days == 1)
	snprintf(clockbuf, CLOCKLEN, "%u day %u:%02u", days, hours, minutes);
      else
	snprintf(clockbuf, CLOCKLEN, "%u:%02u", hours, minutes);

      g15printf(&canvas, 23, 13, "%2.2f, %2.2f, %2.2f up %s",
		oneminute, fiveminute, fifteenminute, clockbuf);
      g15printf(&canvas, 23, 19, "%u total, %u running", total, running);
    }
  if ((F = fopen("/proc/acpi/thermal_zone/THRM/temperature", "r")) != NULL)
    {
      fscanf(F, "%*s%d", &thrm);
      g15printf(&canvas, 133, 19, "CPU %dC",thrm);
      fclose(F);
    }
  else if ((F = fopen("/proc/acpi/thermal_zone/THM/temperature", "r")) != NULL)
    {
      fscanf(F, "%*s%d", &thrm);
      g15printf(&canvas, 133, 19, "CPU %dC",thrm);
      fclose(F);
    }
}

/* UPDATE_MEMORY */

void update_memory()
{
  FILE *F;
  int memtotal = 0, memfree = 0, buffers = 0, cached = 0, memused = 0;

  if ((F = fopen("/proc/meminfo", "r")) != NULL)
    {
      fscanf(F, "MemTotal: %u kB\n", &memtotal);
      fscanf(F, "MemFree: %u kB\n", &memfree);
      fscanf(F, "Buffers: %u kB\n", &buffers);
      fscanf(F, "Cached: %u kB\n", &cached);
      fclose(F);
    }
  // calculate memory used by programs
  memused = memtotal - memfree - buffers - cached;
  if (memtotal > memused && memused > 0)
    g15printf(&canvas, 23, 25,"%dMB/%dMB", memused / 1024, memtotal / 1024);
    g15r_drawBar(&canvas, 84, 27, 159, 28, 1, memused, memtotal, 1);

}

/* UPDATE_DISK */

void update_disk ()
{
  struct statvfs s; // filesystem information for /
  if (statvfs("/home/", &s) != -1)
    g15printf(&canvas, 23, 31,"%.1lfGB/%.1lfGB",
		    (double)(s.f_blocks - s.f_bavail) * s.f_bsize / (1024 * 1024 * 1024),
		    (double)s.f_blocks * s.f_bsize / (1024 * 1024 * 1024));
    g15r_drawBar(&canvas, 84, 33, 159, 34, 1,
		 s.f_blocks - s.f_bavail, s.f_blocks, 1);
}

/* UPDATE_NET */

char *gateway_dev (void)
{
  FILE *F;
  static char linebuf[LINELEN];
  int destination, gateway;

  // scan for the default route with gateway set and destination ip = 0.0.0.0
  if ((F = fopen("/proc/net/route", "r")) != NULL)
    {
      while (fgets(linebuf, LINELEN, F))
	if (fscanf(F, "%s %x %x 00", linebuf, &destination, &gateway) == 3
	    && destination == 0 && gateway != 0)
	  {
	    fclose(F);
	    return linebuf;
	  }
      fclose(F);
    }
  return NULL;
}

char *get_ipaddr (const char *dev)
{
  char *result = NULL;
  struct ifreq ifr;
  int fd;

  // open a socket and use ioctl to query the source ip address
  if (dev && (fd = socket(PF_INET, SOCK_DGRAM, 0)) != -1)
    {
      strncpy(ifr.ifr_name, dev, IFNAMSIZ);
      if (! ioctl(fd, SIOCGIFADDR, &ifr))
	result = inet_ntoa(((struct sockaddr_in *)&(ifr.ifr_ifru.ifru_addr))
			   ->sin_addr);
      close(fd);
    }
  return result;
}

char *get_wireless_info (const char *dev)
{
  FILE *F;
  static char linebuf[LINELEN];
  int link, level;

  if (dev && (F = fopen("/proc/net/wireless", "r")) != NULL)
    {
      while (fgets(linebuf, LINELEN, F))
	if (fscanf(F, "%s %*x %d%*c %d%*c", linebuf, &link, &level) == 3
	    && strncmp(dev, linebuf, strlen(dev)) == 0)
	  {
	    snprintf(linebuf, 200, "%d%% %ddBm", link, level);
	    fclose(F);
	    return linebuf;
	  }
      fclose(F);
    }
  return NULL;
}

void update_net()
{
  char *dev, *ipaddr, *winfo;

  dev = gateway_dev();
  ipaddr = get_ipaddr(dev);
  winfo = get_wireless_info(dev);

  g15printf(&canvas, 23, 37, "%s %s %s",
	    dev ? dev : "no default route", // device name or error message
	    ipaddr ? ipaddr : "",           // ip address if available
	    winfo ? winfo : "");            // wireless info if available
}

int main (int argc, char **argv)
{
  int delay;

  if ((screen = new_g15_screen(G15_G15RBUF)) < 0)
    {
      fprintf(stderr, "Unable to connect to G15daemon\n");
      return 1;
    }
  g15r_initCanvas(&empty_canvas);
  g15r_clearScreen(&empty_canvas, G15_COLOR_WHITE);
  g15r_drawLine(&empty_canvas, 0, 9, 159, 9, 1);
  g15printf(&empty_canvas, 2, 13, "load:");
  g15printf(&empty_canvas, 2, 19, "task:");
  g15printf(&empty_canvas, 2, 25, " ram:");
  g15printf(&empty_canvas, 2, 31, "home:");
  g15printf(&empty_canvas, 2, 37, " net:");

  // It's business time!
  while (1)
    {
      // clear canvas
      memcpy(&canvas, &empty_canvas, sizeof(g15canvas));

      // fill in the blanks
      update_procinfo();
      update_memory();
      update_disk();
      update_net();
      delay = update_clock();

      g15_send(screen, (char*)canvas.buffer, G15_BUFFER_LEN);

      // sleep for 5 seconds or until the clock needs to be updated
      sleep ( delay < 5 ? delay : 5);
    }
}

