/*
 *    WMMDiskLoad - A dockapp to monitor disks usage
 *    Copyright (C) 2002  Mark Staggs <me@markstaggs.net>
 *
 *    Based on work by Seiichi SATO <ssato@sh.rim.or.jp>
 *    Copyright (C) 2001,2002  Seiichi SATO <ssato@sh.rim.or.jp>

 *    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., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <signal.h>
#include <sys/stat.h>
#include "dockapp.h"
#include "backlight_on.xpm"
#include "backlight_off.xpm"
#include "backlight2_on.xpm"
#include "backlight2_off.xpm"
#include "parts.xpm"
#include "letters.xpm"

#define FREE(data) {if (data) free (data); data = NULL;}

#define SIZE	    58
#define MAXSTRLEN   512
#define WINDOWED_BG ". \tc #AEAAAE"
#define MAX_HISTORY 16
#define CPUNUM_NONE -1
#define MAXCHARS 8


typedef enum { LIGHTON, LIGHTOFF } light;

typedef struct Partition {
    char             *entry;
    char             *mountp;
    unsigned          percent;
    struct Partition *next;
} Partition;


Pixmap pixmap;
Pixmap backdrop_on;
Pixmap backdrop_off;
Pixmap backdrop1_on;
Pixmap backdrop1_off;
Pixmap backdrop2_on;
Pixmap backdrop2_off;
Pixmap parts;
Pixmap letters;
Pixmap mask;
static char	 *display_name     = "";
static char	 *light_color      = NULL;	/* back-light color */
static unsigned  update_interval   = 5;
static int       background        = 2;
static light     backlight         = LIGHTOFF;
static Bool      switch_authorized = True;
static Bool      show_devs         = False;
static Partition *partitions       = NULL;
static Partition *current          = NULL;

/* prototypes */
static void do_button1(int x, int y);
static void update();
static void switch_light();
static void draw_bargraph(Partition *p, int y);
static void draw_text(const char *text);
static void draw_letters(const char *text, int y);
static void draw_digits(unsigned num);
static void parse_arguments(int argc, char **argv);
static void print_help(char *prog);
static void reload_sizes();
static int  my_system (char *cmd);
static void part_add (Partition **list, const char *value);
static void free_part (Partition **list);
void *xmalloc (size_t size);
char *xstrdup (const char *string);



int main(int argc, char **argv) {
    XEvent event;
    XpmColorSymbol colors[2] = { {"Back0", NULL, 0}, {"Back1", NULL, 0} };
    int ncolor = 0;
    struct sigaction sa;

    sa.sa_handler = SIG_IGN;
#ifdef SA_NOCLDWAIT
    sa.sa_flags = SA_NOCLDWAIT;
#else
    sa.sa_flags = 0;
#endif
    sigemptyset(&sa.sa_mask);
    sigaction(SIGCHLD, &sa, NULL);

    /* Parse CommandLine */
    parse_arguments(argc, argv);

    /* Initialize Application */
    reload_sizes();
    current = partitions;
    dockapp_open_window(display_name, PACKAGE, SIZE, SIZE, argc, argv);
    dockapp_set_eventmask(ButtonPressMask);

    if (light_color) {
        colors[0].pixel = dockapp_getcolor(light_color);
        colors[1].pixel = dockapp_blendedcolor(light_color, -24, -24, -24, 1.0);
        ncolor = 2;
    }

    /* change raw xpm data to pixmap */
    if (dockapp_iswindowed)
        backlight_on_xpm[1] = backlight_off_xpm[1] = WINDOWED_BG;

    if (!dockapp_xpm2pixmap(backlight_on_xpm, &backdrop1_on, &mask, colors, ncolor)) {
        fprintf(stderr, "Error initializing backlit background image.\n");
        exit(1);
    }
    if (!dockapp_xpm2pixmap(backlight_off_xpm, &backdrop1_off, NULL, NULL, 0)) {
        fprintf(stderr, "Error initializing background image.\n");
        exit(1);
    }
    if (!dockapp_xpm2pixmap(backlight2_on_xpm, &backdrop2_on, &mask, colors, ncolor)) {
        fprintf(stderr, "Error initializing backlit background 2 image.\n");
        exit(1);
    }
    if (!dockapp_xpm2pixmap(backlight2_off_xpm, &backdrop2_off, NULL, NULL, 0)) {
        fprintf(stderr, "Error initializing background 2 image.\n");
        exit(1);
    }
    if (!dockapp_xpm2pixmap(parts_xpm, &parts, NULL, colors, ncolor)) {
        fprintf(stderr, "Error initializing parts image.\n");
        exit(1);
    }
    if (!dockapp_xpm2pixmap(letters_xpm, &letters, NULL, colors, ncolor)) {
        fprintf(stderr, "Error initializing parts image.\n");
        exit(1);
    }

    /* shape window */
    if (!dockapp_iswindowed) dockapp_setshape(mask, 0, 0);
    if (mask) XFreePixmap(display, mask);
    if (background == 1) {
        backdrop_on = backdrop1_on;
        backdrop_off = backdrop1_off;
    } else {
        backdrop_on = backdrop2_on;
        backdrop_off = backdrop2_off;
    }

    /* pixmap : draw area */
    pixmap = dockapp_XCreatePixmap(SIZE, SIZE);

    /* Initialize pixmap */
    if (backlight == LIGHTON) 
        dockapp_copyarea(backdrop_on, pixmap, 0, 0, SIZE, SIZE, 0, 0);
    else
        dockapp_copyarea(backdrop_off, pixmap, 0, 0, SIZE, SIZE, 0, 0);

    dockapp_set_background(pixmap);
    dockapp_show();
    update();

    /* Main loop */
    while (1) {
        if (dockapp_nextevent_or_timeout(&event, update_interval * 1000)) {
            /* Next Event */
            switch (event.type) {
                case ButtonPress:
                    switch (event.xbutton.button) {
                        case 1:
                            do_button1(event.xbutton.x, event.xbutton.y);
                            break;
                        case 2:
                            if (event.xbutton.state == ControlMask) {
                                switch (background) {
                                    case 1:
                                        backdrop_on = backdrop2_on;
                                        backdrop_off = backdrop2_off;
                                        background = 2;
                                        break;
                                    case 2:
                                        backdrop_on = backdrop1_on;
                                        backdrop_off = backdrop1_off;
                                        background = 1;
                                        break;
                                }
                            } else {
                                show_devs = !show_devs;
                            }
                            update();
                            break;
                        case 3:
                            switch_authorized = !switch_authorized;
                            break;
                        default:
                            break;
                    }
                    break;
                default: /* make gcc happy */
                    break;
            }
        } else {
            /* Time Out */
            update();
        }
    }

    return 0;
}


static void do_button1(int x, int y) {
    int x1 =  5, y1 = 24, x2 = 13, y2 = 29;
    int x3 = 46, y3 = 24, x4 = 54, y4 = 29;
    if (background == 2) {
        x1 =  5; y1 = 50; x2 = 13; y2 = 55;
        x3 = 47; y3 = 50; x4 = 55; y4 = 55;
    }
    if ( (x > x1) && (x < x2) && (y > y1) && (y < y2) ) {
        if (current) {
            Partition *c = partitions;
            if (current != partitions) {
                while (c->next != current) c = c->next;
            } else {
                if (background == 1)
                    while (c->next != NULL) c = c->next;
            }
            current = c;
            update();
        }
    } else if ( (x > x3) && (x < x4) && (y > y3) && (y < y4) ) {
        if (current) {
            if (current->next)
                current = current->next;
            else {
                if (background == 1)
                    current = partitions;
            }
            update();
        }
    } else {
        switch_light();
    }
}


/* called by timer */
static void update() {
    static light pre_backlight;
    static Bool in_alarm_mode = False;

    /* get current cpu usage in percent */
    reload_sizes();

    /* all clear */
    if (backlight == LIGHTON) 
        dockapp_copyarea(backdrop_on, pixmap, 0, 0, 58, 58, 0, 0);
    else 
        dockapp_copyarea(backdrop_off, pixmap, 0, 0, 58, 58, 0, 0);

    /* draw digit */
    if (current) {
        if (background == 1) {
            draw_text(show_devs ? current->entry : current->mountp);
            draw_digits(current->percent);
        } else {
            draw_bargraph(current, 5);
            if (current->next) {
                draw_bargraph(current->next, 20);
                if (current->next->next)
                    draw_bargraph(current->next->next, 35);
            }
        }
    } else {
        draw_letters("ERROR", 5);
    }

    /* show */
    dockapp_copy2window(pixmap);
}


/* called when mouse button pressed */
static void switch_light() {
    switch (backlight) {
        case LIGHTOFF:
            backlight = LIGHTON;
            break;
        case LIGHTON:
            backlight = LIGHTOFF;
            break;
    }

    update();
}


static void draw_bargraph(Partition *p, int y) {
    char text[MAXCHARS + 1];
    int  max, x, pos = 0;

    bzero(text, MAXCHARS + 1);
    if (show_devs)
        strncpy(text, p->entry ? p->entry : "Error", MAXCHARS);
    else
        strncpy(text, p->mountp ? p->mountp : "Error", MAXCHARS);
    draw_letters(text, y);

    if (backlight == LIGHTON) pos = 50;

    dockapp_copyarea(parts, pixmap, pos, 49, 50, 8, 5, y + 6);
    max = p->percent * 46 / 100;
    for (x = 0 ; x < max ; x++)
        dockapp_copyarea(letters, pixmap, 0, 0, 1, 3, 7 + x, y + 8);
}


static void draw_text(const char *text) {
    char line1[MAXCHARS + 1];
    char line2[MAXCHARS + 1];
    char line3[MAXCHARS + 1];
    int i = 0, j = 0;

    bzero(line1, MAXCHARS + 1);
    bzero(line2, MAXCHARS + 1);
    bzero(line3, MAXCHARS + 1);

    if (text) {
        if (strchr(text, ':') != NULL) {
            for (i = 0 ; i <= strchr(text, ':') - text ; i++) {
                if (i == MAXCHARS - 1) break;
                line1[i] = text[i];
            }
        }
        while (text[i]) {
            if (j < MAXCHARS) {
                line2[j] = text[i];
            } else if (j < MAXCHARS * 2) {
                line3[j - MAXCHARS] = text[i];
            }
            j++;
            i++;
        }
    } else {
        strcpy(line2, "Error");
    }

    if (line1[0] != 0) draw_letters(line1, 5);
    if (line2[0] != 0) draw_letters(line2, 11);
    if (line3[0] != 0) draw_letters(line3, 17);
}


static void draw_letters(const char *text, int y) {
    int i, dx = 0, dy = 5, dz = 0, incr = 6, w = 5, h = 5;
    int x = 5;

    if (backlight == LIGHTON) {
        dx = 5;
        dy = 10;
        dz = 50;
    }
    for (i = 0 ; text[i] ; i ++) {
        int pos = 0;
        if ((text[i] != '/') && (text[i] != ':')) {
            if (isdigit(text[i])) {
                pos = toupper(text[i]) - '0';
                dockapp_copyarea(letters, pixmap,
                        pos * w + dz, 0, w, h, x + i * incr, y);
            } else {
                pos = toupper(text[i]) - 'A';
                dockapp_copyarea(letters, pixmap,
                        pos * w, dy, w, h, x + i * incr, y);
            }
        } else {
            switch (text[i]) {
                case '/': pos = 100; break;
                case ':': pos = 110; break;
            }
            pos += dx;
            dockapp_copyarea(letters, pixmap,
                    pos, 0, w, h, x + i * incr, y);
        }
    }
}


static void draw_digits(unsigned num) {
    unsigned v100, v10, v1;
    int      y = 0;

    if (num < 0) num = 0;

    v100 = num / 100;
    v10  = (num - v100 * 100) / 10;
    v1   = (num - v100 * 100 - v10 * 10);

    if (backlight == LIGHTON) y = 20;

    /* draw digit */
    dockapp_copyarea(parts, pixmap, v1 * 10, y, 10, 20, 29, 34);
    if (v10 != 0)
        dockapp_copyarea(parts, pixmap, v10 * 10, y, 10, 20, 17, 34);
    if (v100 == 1) {
        dockapp_copyarea(parts, pixmap, 10, y, 10, 20, 5, 34);
        dockapp_copyarea(parts, pixmap, 0, y, 10, 20, 17, 34);
    }
}


static void parse_arguments(int argc, char **argv) {
    int i;
    int integer;
    for (i = 1; i < argc; i++) {
        if (!strcmp(argv[i], "--help") || !strcmp(argv[i], "-h")) {
            print_help(argv[0]), exit(0);
        } else if (!strcmp(argv[i], "--version") || !strcmp(argv[i], "-v")) {
            printf("%s version %s\n", PACKAGE, VERSION), exit(0);
        } else if (!strcmp(argv[i], "--display") || !strcmp(argv[i], "-d")) {
            display_name = argv[i + 1];
            i++;
        } else if (!strcmp(argv[i], "--backlight") || !strcmp(argv[i], "-bl")) {
            backlight = LIGHTON;
        } else if (!strcmp(argv[i], "--light-color") || !strcmp(argv[i], "-lc")) {
            light_color = argv[i + 1];
            i++;
        } else if (!strcmp(argv[i], "--interval") || !strcmp(argv[i], "-i")) {
            if (argc == i + 1)
                fprintf(stderr, "%s: error parsing argument for option %s\n",
                        argv[0], argv[i]), exit(1);
            if (sscanf(argv[i + 1], "%i", &integer) != 1)
                fprintf(stderr, "%s: error parsing argument for option %s\n",
                        argv[0], argv[i]), exit(1);
            if (integer < 5)
                fprintf(stderr, "%s: argument %s must be >= 5\n",
                        argv[0], argv[i]), exit(1);
            update_interval = integer;
            i++;
        } else if (!strcmp(argv[i], "--windowed") || !strcmp(argv[i], "-w")) {
            dockapp_iswindowed = True;
        } else if (!strcmp(argv[i], "--broken-wm") || !strcmp(argv[i], "-bw")) {
            dockapp_isbrokenwm = True;
        } else if (!strcmp(argv[i], "--no-blink") || !strcmp(argv[i], "-nb")) {
            switch_authorized = False;
        } else if (!strcmp(argv[i], "--device") || !strcmp(argv[i], "-n")) {
            show_devs = True;
        } else if (!strcmp(argv[i], "--single") || !strcmp(argv[i], "-s")) {
            background = 1;
        } else if (!strcmp(argv[i], "--partition") || !strcmp(argv[i], "-p")) {
            part_add(&partitions, argv[i + 1]);
            i++;
        } else {
            fprintf(stderr, "%s: unrecognized option '%s'\n", argv[0], argv[i]);
            print_help(argv[0]), exit(1);
        }
    }
}


static void print_help(char *prog)
{
    printf("Usage : %s [OPTIONS]\n", prog);
    printf("wmmaiload - Window Maker mails monitor dockapp\n"
           "  -d,  --display <string>        display to use\n"
           "  -bl, --backlight               turn on back-light\n"
           "  -lc, --light-color <string>    back-light color(rgb:6E/C6/3B is default)\n"
           "  -i,  --interval <number>       number of secs between updates (5 is default and minimum)\n"
           "  -h,  --help                    show this help text and exit\n"
           "  -v,  --version                 show program version and exit\n"
           "  -w,  --windowed                run the application in windowed mode\n"
           "  -bw, --broken-wm               activate broken window manager fix\n"
           "  -p,  --partition <device>      show informations for device (may be used more than once)\n"
           "  -n, --device                   show device instead of mount point\n"
           "  -s, --single                   show only one device at a time\n");
    /* OPTIONS SUPP :
           "  -nb, --no-blink                disable blinking\n");
     *  ? -f, --file    : configuration file
     */
}


static void reload_sizes() {
    FILE *file;
    int  i = 0;

    file = popen("df -P", "r");
    while (! feof(file)) {
        char line[MAXSTRLEN + 1];
        bzero(line, MAXSTRLEN + 1);
        fgets(line, MAXSTRLEN, file);
        i++;
        if ((i > 1) && (line[0] != 0)) {
            Partition *part = partitions;
            unsigned long blocs, used, avail;
            unsigned      capacity, j;
            char          device[MAXSTRLEN + 1];
            char          mountp[MAXSTRLEN + 1];
            bzero(device, MAXSTRLEN + 1);
            bzero(mountp, MAXSTRLEN + 1);
            sscanf(line, "%s %d %d %d %d%% %s",
                    device, &blocs, &used, &avail, &capacity, mountp);
            for (j = 0 ; part ; j++) {
                if (strcmp(device, part->entry) == 0) break;
                part = part->next;
            }
            if (part) {
                part->percent = capacity;
                if (! part->mountp) part->mountp = xstrdup(mountp);
            }
        }
    }
    pclose(file);
}


static int my_system (char *cmd) {
    int           pid;
    extern char **environ;

    if (cmd == 0) return 1;
    pid = fork ();
    if (pid == -1) return -1;
    if (pid == 0) {
        pid = fork ();
        if (pid == 0) {
            char *argv[4];
            argv[0] = "sh";
            argv[1] = "-c";
            argv[2] = cmd;
            argv[3] = 0;
            execve ("/bin/sh", argv, environ);
            exit (0);
        }
        exit (0);
    }
    return 0;
}


static void part_add (Partition **list, const char *value) {
    Partition *lst = *list;
    Bool       ok  = True;

    if (! value) return;
    if (! lst) {
        lst   = xmalloc(sizeof(Partition));
        *list = lst;
    } else {
        if (strcmp(value, lst->entry) == 0) ok = False;
        while ((lst->next) && ok) {
            lst = lst->next;
            if (strcmp(value, lst->entry) == 0) ok = False;
        }
        if (! ok) return;
        lst->next = xmalloc(sizeof(Partition));
        lst = lst->next;
    }
    lst->entry   = xstrdup(value);
    lst->mountp  = NULL;
    lst->percent = 0;
    lst->next    = NULL;
}


static void free_part (Partition **list) {
    Partition *lst = *list, *next;
    while (lst) {
        next = lst->next;
        FREE(lst->entry);
        FREE(lst->mountp);
        free(lst);
        lst = next;
    }
    *list = NULL;
}


void *xmalloc (size_t size) {
    void *ret = malloc (size);
    if (ret == NULL) {
        perror ("malloc() ");
        exit (-1);
    } else
        return ret;
}


char *xstrdup (const char *string) {
    char *ret = strdup (string);
    if (ret == NULL) {
        perror ("strdup() ");
        exit (-1);
    } else
        return ret;
}

