/*                               -*- Mode: C -*- 
 * cwusage.c -- report on the disk usage.
 * 
 * Author          : 
 * Created On      : Tue Aug  3 17:03:26 1999
 * Last Modified By: Peter Bosch
 * Last Modified On: Sat Dec 11 20:35:04 1999
 * Status          : Unknown, Use with caution!
 * 
 * Unless other notices are present in any part of this file
 * explicitly claiming copyrights for other people and/or 
 * organizations, the contents of this file is fully copyright 
 * (C) 1999 Peter Bosch, all rights reserved.
 */

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <errno.h>
#include <assert.h>
#include <stdarg.h>
#include <X11/Xlib.h>

#define DEFAULTWIDTH	(5 * 128)
#define ROWHEIGHT	15
#define INFOW		100

#include "cwif.h"
#include "info.h"
#include "button.h"
#include "gray50.bm"

typedef struct {
  int		dp_num;
  cwftabentry_t	dp_entry;
  cwdinode_t	*dp_inode;
} perdp_t;

static char *progname;
static char *clockwise = CLOCKWISE;
static char *fgstr  = "#000000";
static char *bgstr  = "#B2C0DC";
static char *histr  = "#C6D5E2";
static char *lostr  = "#8B99B5";

static char *colortab[] = { 
  "red", "green", "blue", "orange", "pink", "gray",
  "azure", "SeaGreen", "cyan", "yellow green", "goldenrod", 
  "firebrick", "dark salmon", "deep pink", "dark violet", "tomato" 
};

static int  verbose;
static int  bpp, depth, fg, bg, hi, lo, yellow, black, white, needreload;;
static uint32_t blocksz;

extern int optind;
extern char *optarg;

static void load_disks(int fd, cwinfo_t *info);
static perdp_t *load_dynpars(int fd, int *ndynpars);
static int alloc_color(Display *display, Colormap cmap, char *colorname);
static void paint_disks(uint8_t *cbuffer, int *ldisks, cwinfo_t *info, 
			int width, int rheight, int color);
static void paint_blocks(uint8_t *cbuffer, int y, uint32_t blockno,
			 uint32_t nblocks, int width, int rheight, int color);
static void paint_dynpars(Display *display, Colormap cmap, uint8_t *cbuffer, 
			  int *ldisks, perdp_t *dynpars, int ndynpars, 
			  int width, int rheight);
static void control_buttons(Display *display, Window win, GC gc, 
			    XFontStruct *mf, Pixmap stipple, int rheight);
static void draw_disks(Display *display, Window win, GC gc, XFontStruct *mf,
		       cwinfo_t *info, int width, int rheight);
static void draw_control(Display *display, Window *win, GC gc, XFontStruct *mf,
			 Pixmap stipple, Colormap cmap, perdp_t *dynpars, 
			 int ndynpars, int rheight);
static void message(Display *display, Window win, GC gc, XFontStruct *mf,
		    int x, int y, int w, int h, int fg, int bg,
		    char *format, ...);

static void
usage()
{
  fprintf(stderr, "Usage: %s [-c clockwise] [-verbose]\n", progname);
  exit(1);
}

int
main(int argc, char **argv)
{
  char *dpy = NULL;
  Display *display;
  GC gc;
  Window cwin, dwin;
  XEvent e;
  XFontStruct *mf;
  XImage *xi;
  Colormap cmap;
  Pixmap stipple;
  int opt, fd, done, ndynpars, height, n, ldisks[NUMDISKS];
  perdp_t *dynpars;
  cwinfo_t info;
  uint8_t *cbuffer;

  progname = argv[0];
  while ((opt = getopt(argc, argv, "vc:?")) != EOF) {
    switch (opt) {
    case 'v':
      verbose = 1;
      break;
    case 'c':
      clockwise = optarg;
      break;
    case '?':
      usage();
    }
  }
  if (optind != argc) usage();

  fd = open(clockwise, O_RDONLY);
  if (fd < 0) {
    fprintf(stderr, "%s: Cannot open %s: %s\n",
	    progname, clockwise, strerror(errno));
    exit(1);
  }

  /* Create an empty control and block usage window */
  if (dpy == NULL) dpy = getenv("DISPLAY");
  if (dpy == NULL) dpy = ":0.0";
  if ((display = XOpenDisplay(dpy)) == NULL) {
    fprintf(stderr, "%s: Cannot open display %s\n", progname, dpy);
    exit(1);
  }
  depth  = DefaultDepth(display, DefaultScreen(display));
  gc     = DefaultGC(display, DefaultScreen(display));
  cmap   = DefaultColormap(display, DefaultScreen(display));

  switch (depth) {
  case 8:
    bpp = sizeof(uint8_t);
    break;
  case 16:
    bpp = sizeof(uint16_t);
    break;
  case 24:
    bpp = sizeof(uint32_t);
    break;
  default:
    fprintf(stderr, "%s: Unsupported depth (%d)\n", progname, depth);
    exit(1);
  }

  fg     = alloc_color(display, cmap, fgstr);
  bg     = alloc_color(display, cmap, bgstr);
  hi     = alloc_color(display, cmap, histr);
  lo     = alloc_color(display, cmap, lostr);
  yellow = alloc_color(display, cmap, "yellow");
  black  = alloc_color(display, cmap, "black");
  white  = alloc_color(display, cmap, "white");

  printf("'q' = %d\n", XKeycodeToKeysym(display, 'q', 0));

  /* Get the disk configuration */
  load_disks(fd, &info);
  memset(ldisks, 0, sizeof(int) * NUMDISKS);
  for (height = ROWHEIGHT, n = 0; n != NUMDISKS; n++)
    if (MAJOR(info.info_pd[n].ipd_kdev) != 0 ||
	MINOR(info.info_pd[n].ipd_kdev) != 0) {
      ldisks[info.info_pd[n].ipd_ldisk] = height + ROWHEIGHT;
      height += (1 + numblocks(info.info_pd[n].ipd_nblocks, DEFAULTWIDTH)) * 
	ROWHEIGHT;
      blocksz = info.info_pd[n].ipd_blocksz;
    }

  /* Allocate a color buffer for this window */
  cbuffer = (uint8_t *)malloc(DEFAULTWIDTH * height * bpp);
  memset(cbuffer, black, DEFAULTWIDTH * height * bpp);
  paint_disks(cbuffer, ldisks, &info, DEFAULTWIDTH, ROWHEIGHT, white);

  dwin = XCreateWindow(display, RootWindow(display, DefaultScreen(display)), 
		       0, 0, DEFAULTWIDTH, height, 10, depth,
		       InputOutput,
		       DefaultVisual(display, DefaultScreen(display)),
		       0, NULL);

  stipple = XCreatePixmapFromBitmapData(display, dwin, gray50_bits,
					gray50_width, gray50_height,
					1L, 0L, 1);

  xi = XCreateImage(display, 
		    DefaultVisual(display, DefaultScreen(display)),
		    depth, ZPixmap, 0, cbuffer, DEFAULTWIDTH, height, 
		    8, 0);	
  XPutImage(display, dwin, gc, xi, 0, 0, 0, 0, DEFAULTWIDTH, height);

  if ((mf = XLoadQueryFont(display, "8x13")) == NULL) {
    fprintf(stderr, "%s: cannot open 8x13 font", progname);
    exit(1);
  }
  XSetFont(display, gc, mf->fid);

  XSelectInput(display, dwin, 0xFFFFFF);
  XMapWindow(display, dwin);
  XStoreName(display, dwin, progname);

  control_buttons(display, dwin, gc, mf, stipple, ROWHEIGHT);

  cwin = -1;

  dynpars = load_dynpars(fd, &ndynpars);
  paint_dynpars(display, cmap, cbuffer, ldisks, dynpars, ndynpars, 
		DEFAULTWIDTH, ROWHEIGHT);
  draw_control(display, &cwin, gc, mf, stipple, cmap, dynpars, ndynpars, 
	       ROWHEIGHT);

  done = 0;
  while (!done) {
    XNextEvent(display, &e);

    switch (e.type) {
    case ButtonPress:
      if (e.xany.window == dwin)
	b_press(&e.xbutton);
      break;
    case Expose:
      if (e.xany.window == dwin) {
	XPutImage(display, dwin, gc, xi, 0, 0, 0, 0, DEFAULTWIDTH, height);
	draw_disks(display, dwin, gc, mf, &info, DEFAULTWIDTH, ROWHEIGHT);
	b_redrawall();
      }
      else if (e.xany.window == cwin)
	i_redrawall();
      break;
    case KeyPress:
      break;
    default:
      break;
    }

    if (needreload) {
      needreload = 0;
      XDestroyWindow(display, cwin);

      for (n = 0; n != ndynpars; n++)
	free(dynpars[n].dp_inode);
      free(dynpars);
      i_destroyall();

      memset(cbuffer, black, DEFAULTWIDTH * height * bpp);
      paint_disks(cbuffer, ldisks, &info, DEFAULTWIDTH, ROWHEIGHT, white);
      dynpars = load_dynpars(fd, &ndynpars);
      paint_dynpars(display, cmap, cbuffer, ldisks, dynpars, ndynpars, 
		    DEFAULTWIDTH, ROWHEIGHT);
      draw_control(display, &cwin, gc, mf, stipple, cmap, dynpars, ndynpars, 
		   ROWHEIGHT);
    }
  }
  XDestroyWindow(display, dwin);
  XDestroyWindow(display, cwin);
  exit(0);
}

static int
alloc_color(Display *display, Colormap cmap, char *colorname)
{
  XColor color;

  if (!XParseColor(display, DefaultColormap(display, DefaultScreen(display)),
		   colorname, &color)) 
    return -ENOENT;
  
  if (!XAllocColor(display, DefaultColormap(display, DefaultScreen(display)), 
		   &color))
    return -EINVAL;
  return color.pixel;
}

static void
load_disks(int fd, cwinfo_t *info)
{
  if (ioctl(fd, CW_GETINFO, info) < 0) {
    fprintf(stderr, "_loaddisks: Cannot get configuration info from %s: %s\n",
	    clockwise, strerror(errno));
    exit(1);
  }
}

static perdp_t *
load_dynpars(int fd, int *ndynpars)
{
  int entry, ndps;
  perdp_t *dps;

  if (lseek(fd, 0, SEEK_SET) < 0) {
    fprintf(stderr, "load_dynpars: Cannot rewind %s: %s\n",
	    strerror(errno), clockwise);
    exit(1);
  }

  ndps = entry = 0;
  dps  = NULL;
  while (1) {
    cwftabentry_t ft;
    int rv;

    if ((rv = read(fd, (void *)&ft, sizeof(cwftabentry_t))) < 0) {
      fprintf(stderr, "load_dynpars: Cannot read ftab entry from %s: %s\n",
	      progname, clockwise, strerror(errno));
      exit(1);
    }
    
    if (rv == 0) break;

    if (rv != sizeof(cwftabentry_t)) {
      fprintf(stderr, 
	      "load_dynpars: Partially read ftab entry from %s: requested %d, got %d\n",
	      clockwise, sizeof(cwftabentry_t), rv);
      exit(1);
    }
    if (ft.ft_magic != CW_FTMAGIC) {
      entry++;
      continue;
    }

    if (ndps == 0)
      dps = (perdp_t *)malloc(sizeof(perdp_t));
    else
      dps = (perdp_t *)realloc(dps, (ndps + 1) * sizeof(perdp_t));
    
    dps[ndps].dp_num = entry++;
    memcpy(&dps[ndps].dp_entry, &ft, sizeof(cwftabentry_t));
    dps[ndps].dp_inode = (cwdinode_t *)malloc(ft.ft_isize);

    if (dps[ndps].dp_inode == NULL) {
      fprintf(stderr, "load_dynpars: Cannot allocate %d bytes for inode\n",
	      ft.ft_isize);
      exit(1);
    }

    if ((rv = ioctl(fd, CW_INODE(dps[ndps].dp_num), dps[ndps].dp_inode)) < 0) {
      fprintf(stderr, "load_dynpars: Cannot get inode from %s: %s\n",
	      clockwise, strerror(errno));
      exit(1);
    }

    if (verbose)
      printf("load_dynpars: dp %d, %s\n",
	     dps[ndps].dp_num, dps[ndps].dp_entry.ft_name);
    ndps++;
  }

  *ndynpars = ndps;
  return dps;
}

static void
paint_disks(uint8_t *cbuffer, int *ldisks, cwinfo_t *info, 
	    int width, int rheight, int color)
{
  int n;

  for (n = 0; n != NUMDISKS; n++) {
    if (MAJOR(info->info_pd[n].ipd_kdev) == 0 &&
	MINOR(info->info_pd[n].ipd_kdev) == 0) 
      continue;

    paint_blocks(cbuffer, ldisks[info->info_pd[n].ipd_ldisk], 0, 
		 info->info_pd[n].ipd_nblocks, width, rheight, color);
  }
}

static void
paint_dynpars(Display *display, Colormap cmap, uint8_t *cbuffer, 
	      int *ldisks, perdp_t *dynpars, int ndynpars, 
	      int width, int rheight)
{
  int n;

  for (n = 0; n != ndynpars; n++) {
    int color, y, i;

    y = ldisks[dynpars[n].dp_entry.ft_idisk];
    paint_blocks(cbuffer, y, dynpars[n].dp_entry.ft_iaddr,
		 numblocks(numblocks(dynpars[n].dp_entry.ft_isize, 512),
			   blocksz), width, rheight, yellow);

    color = 
      alloc_color(display, cmap, 
		  colortab[dynpars[n].dp_num % 
			  (sizeof(colortab) / sizeof(colortab[0]))]);

    for (i = 0; i != dynpars[n].dp_entry.ft_isize / sizeof(cwdinode_t); i++)
      paint_blocks(cbuffer, ldisks[dynpars[n].dp_inode[i].i_ldisk],
		   dynpars[n].dp_inode[i].i_daddr, 1, width, rheight, color);
  }
}

static void
paint_blocks(uint8_t *cbuffer, int y, uint32_t blockno, uint32_t nblocks,
	     int width, int rheight, int color)
{
  blockno /= blocksz;
  y       += (blockno / width) * rheight;
  blockno %= width;

  while (nblocks > 0) {
    int _width, _x, _y;

    _width = (blockno + nblocks > width)? width - blockno: nblocks;
    for (_x = blockno; _x != blockno + _width; _x++)
      for (_y = y; _y != y + rheight - 1; _y++) {
	switch (bpp) {
	case 1:
	  cbuffer[_y * width + _x] = color;
	  break;
	case 2:
	  ((uint16_t *)cbuffer)[_y * width + _x] = color;
	  break;
	case 4:
	  ((uint32_t *)cbuffer)[_y * width + _x] = color;
	  break;
	}
      }

    nblocks -= _width;
    y       += rheight;
    blockno  = (blockno + _width) % width;
  }
}

static void
draw_disks(Display *display, Window win, GC gc, XFontStruct *mf,
	   cwinfo_t *info, int width, int rheight)
{
  int x, y, n;

  /* Paint the meta information in the window */
  for (y = rheight, n = 0; n != NUMDISKS; n++)
    if (MAJOR(info->info_pd[n].ipd_kdev) != 0 ||
	MINOR(info->info_pd[n].ipd_kdev) != 0) {
      int bno;

      x = 0;
      message(display, win, gc, mf, x, y, INFOW, rheight, black, white,
	      "%02d", info->info_pd[n].ipd_ldisk);
      x += INFOW;

      message(display, win, gc, mf, x, y, INFOW, rheight, black, white,
	      info->info_pd[n].ipd_dtype);
      x += INFOW;

      message(display, win, gc, mf, x, y, INFOW, rheight, black, white,
	      "%04d", info->info_pd[n].ipd_blocksz);
      x += INFOW;

      message(display, win, gc, mf, x, y, INFOW, rheight, black, white,
	      "%04d", info->info_pd[n].ipd_nblocks);
      x += INFOW;

      message(display, win, gc, mf, x, y, INFOW, rheight, black, white,
	      "%04X", info->info_pd[n].ipd_kdev);
      x += INFOW;
      y += rheight;

      for (bno = 0; bno < info->info_pd[n].ipd_nblocks; bno += 256) {
	int _x, _y, tw;
	char s[20];

	sprintf(s, "%dMB", (bno * info->info_pd[n].ipd_blocksz) / (M / 512));
	_y = (bno / width) * rheight + (rheight >> 1) -
	  (mf->ascent + mf->descent) / 2 + mf->ascent;
	_x = (bno % width) - (XTextWidth(mf, s, strlen(s)) >> 1);
	if (_x > 0)
	  XDrawString(display, win, gc, _x, y + _y, s, strlen(s));
      }
      y += numblocks(info->info_pd[n].ipd_nblocks, width) * rheight;
    }
}

static void
message(Display *display, Window win, GC gc, XFontStruct *mf,
	int x, int y, int w, int h, int fg, int bg,
	char *format, ...)
{
  char buf[1024];
  va_list args;
  int ix, iy;

  va_start(args, format);
  vsprintf(buf, format, args);
  va_end(args);
    
  XSetForeground(display, gc, bg);
  XFillRectangle(display, win, gc, x - 1, y - 1, w, h);

  XSetForeground(display, gc, fg);
  XDrawRectangle(display, win, gc, x - 1, y - 1, w, h);

  ix = x + w / 2 - XTextWidth(mf, buf, strlen(buf)) / 2;
  iy = y + h / 2 - (mf->ascent + mf->descent) / 2 + mf->ascent;

  XDrawString(display, win, gc, ix, iy, buf, strlen(buf));
}

static void
draw_control(Display *display, Window *win, GC gc, XFontStruct *mf, 
	     Pixmap stipple, Colormap cmap, perdp_t *dynpars, 
	     int ndynpars, int rheight)
{
  int maxtext, n, h;

  for (maxtext = n = 0; n != ndynpars; n++)
   if (XTextWidth(mf, dynpars[n].dp_entry.ft_name, 
		  strlen(dynpars[n].dp_entry.ft_name)) > maxtext)
     maxtext = XTextWidth(mf, dynpars[n].dp_entry.ft_name,
			  strlen(dynpars[n].dp_entry.ft_name));
  
  *win = XCreateWindow(display,
			RootWindow(display, DefaultScreen(display)),
			0, 0, maxtext + 30, ndynpars * rheight, 10, depth,
			InputOutput,
			DefaultVisual(display, DefaultScreen(display)),
			0, NULL);
  XSelectInput(display, *win, 0xFFFFFF);
  XMapWindow(display, *win);
  XStoreName(display, *win, progname);

  for (n = 0; n != ndynpars; n++) {
    int color;

    color = 
      alloc_color(display, cmap, 
		  colortab[dynpars[n].dp_num % 
			  (sizeof(colortab) / sizeof(colortab[0]))]);
    
    i_new(display, *win, gc, mf, stipple, 0, n * rheight, maxtext + 30, 
	  rheight, fg, color, lo, hi, 1, dynpars[n].dp_entry.ft_name);
  }
}

static void
quit()
{
  exit(0);
}

static void
reload()
{
  printf("Reloading\n");
  needreload = 1;
}

static void
control_buttons(Display *display, Window win, GC gc, XFontStruct *mf,
		Pixmap stipple, int rheight)
{
  int x;

  x = 0;
  (void)b_new(display, win, gc, mf, stipple, x, 0, INFOW, rheight,
	      black, white, lo, hi, 1, "Quit", 0, 0, 0, (b_f)quit,
	      NULL, NULL);
  x += INFOW;

  (void)b_new(display, win, gc, mf, stipple, x, 0, INFOW, rheight,
	      black, white, lo, hi, 1, "Reload", 0, 0, 0, (b_f)reload,
	      NULL, NULL);
  x += INFOW;
}
