/* clock.c 
 * Version 0.0
 *
 * Userland program utilizing /dev/pit.
 *
 * This code's under the GNU General Public License.
 *
 * Written by Andy Goth <unununium@openverse.com> and Zorica Majdov, 2003.
 *
 * Assignment for CSE 3442-001 lab 5.
 * Dr. Roger Walker, University of Texas at Arlington */

#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/poll.h>
#include <sys/time.h>
#include <termios.h>
#include <unistd.h>

#include "../kernel/pit.h"

#define BACKSPACE 127
#define CLEAR 27
#define RETURN 10

/* --- Function prototypes. --- */

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

static void display(char* format, ...);
static void display_time(void);

static void handle_timer(void);
static void handle_keyboard(void);

static void timer_init(void);
static void timer_cleanup(void);
static void timer_stop(void);
static void timer_start(void);

static void cleanup(void);

/* --- Global variables. --- */

/* The current time. */
static volatile int hours, minutes, seconds, tenths;

/* If nonzero, the timer's tickin'. */
static int timer_running = 1;

/* The terminal settings prior to running this program. */
static struct termios old_mode;

/* File descriptor for /dev/pit. */
static int pit_fd = -1;

/* Filename of /dev/pit. */
static       char* devpit_fname;
static const char* default_devpit_fname = "/dev/pit";

/* The divisor value to send to the 8253. */
/* static  pit_divisor_t divisor = {100, 100, 10}; */
static  pit_divisor_t divisor = {2, 2, 2};

/* --- Function definitions. --- */

/* Where it's at. */
int main(int argc, char** argv)
{
    struct termios mode;
    struct pollfd ufds[2];

    /* Parse command line arguments. */
    if (argc == 1) {
        devpit_fname = strdup(default_devpit_fname);
    } else if (argc == 2) {
        devpit_fname = strdup(argv[1]);
    } else if (argc > 2) {
        printf("Usage: %s [device]\n", argv[0]);
        return EXIT_FAILURE;
    }

    /* Disable echo.  Enable raw mode. */
    if (isatty(0)) {
        tcgetattr(0, &mode);
        old_mode = mode;
        mode.c_lflag &= ~ECHO;
        mode.c_lflag &= ~ICANON;
        tcsetattr(0, TCSANOW, &mode);
    }

    /* Register exit handler. */
    atexit(cleanup);

    /* Start the timer. */
    timer_init();
    timer_running = 1;

    /* Prepare for poll, below. */
    ufds[0].fd = 0;      ufds[0].events = POLLIN;
    ufds[1].fd = pit_fd; ufds[1].events = POLLIN;

    /* Update the screen. */
    display_time();

    /* Event loop. */
    while (1) {
        /* Wait for keyboard or timer. */
        int ret = poll(ufds, 2, -1);

        /* Error? */
        if (ret == -1) {
            if (errno == EINTR) {
                /* Interrupted by signal... try again. */
                continue;
            } else {
                /* Something Bad. */
                perror("poll");
                exit(EXIT_FAILURE);
            }
        }

        /* Nothing? */
        if (ret == 0) continue;

        /* Keyboard junk. */
        if (ufds[0].revents & POLLIN) {
            handle_keyboard();
            if (--ret == 0) continue;
        }

        /* Timer tick. */
        if (ufds[1].revents & POLLIN) {
            handle_timer();
            if (--ret == 0) continue;
        }

        /* Shouldn't happen. */
        fprintf(stderr, "Hmm...???\n");
        exit(EXIT_FAILURE);
    }
    
    /* Should never get here... */
    return EXIT_SUCCESS;
}

/* Prints a line of text to the screen. */
static void display(char* format, ...)
{
    va_list ap;
    va_start(ap, format);
    printf("\r");
    vprintf(format, ap);
    fflush(stdout);
    va_end(ap);
}

/* Prints the current time. */
static void display_time(void)
{
    display("%s: %02d:%02d:%02d.%01d", timer_running ? "running" : "paused ", 
            hours, minutes, seconds, tenths);
}

/* Usage:
 *
 * clock [device]
 *
 *    device   Overrides the 8253 device filename.  (optional)
 *
 * Keys:
 *
 *    C        Pause or resume timer.
 *    Z        Set timer to zero.
 *    S        Set timer to a user-supplied value.
 *    Q        Terminate program. */

/* User pressed a key.  Or something. */
static void handle_keyboard(void)
{
    int ret, running;
    int caret = 0;
    char buf[] = "------";

    /* Process keyboard input. */
    switch (toupper(getchar())) {
    case 'C':
        /* Pause or resume timer. */
        if (timer_running) {
            timer_running = 0;
            timer_stop();
        } else {
            timer_running = 1;
            timer_start();
        }
        display_time();
        break;
    case 'Z':
        /* Reset timer to zero. */
        hours = minutes = seconds = tenths = 0;
        display_time();
        break;
    case 'S':
        /* Set the timer to a user-supplied value. */
        running = timer_running;
        timer_running = 0;
        timer_stop();

        while (1) {
            display("input  : %.2s:%.2s:%.2s.0\e[%dG",
                    buf + 0, buf + 2, buf + 4, caret + caret / 2 + 10);

again:          ret = getchar();
            if (ret == BACKSPACE) {
                if (caret > 0) {
                    caret--;
                    buf[caret] = '-';
                }
            } else if (ret == CLEAR) {
                caret = 0;
                strcpy(buf, "------");
            } else if (ret == RETURN) {
                if (caret == 6) break;
            } else if (ret >= '0' && ret <= '9') {
                if (caret == 6) goto again;
                if (caret == 2 || caret == 4) {
                    if (ret >= '6') goto again;
                }
                buf[caret] = ret;
                caret++;
            }
        }
        hours   = (buf[0] - '0') * 10 + (buf[1] - '0') * 1;
        minutes = (buf[2] - '0') * 10 + (buf[3] - '0') * 1;
        seconds = (buf[4] - '0') * 10 + (buf[5] - '0') * 1;
        tenths  = 0;

        timer_running = running;
        if (timer_running) timer_start();
        break;
    case 'Q':
        /* Quit. */
        printf("\n");
        exit(EXIT_SUCCESS);
    }
}

/* Updates the clock. */
static void handle_timer(void)
{
    unsigned long ticks;

    /* See how many ticks happened. */
    if (read(pit_fd, &ticks, sizeof(ticks)) < 0) {
        perror("read");
        exit(EXIT_FAILURE);
    }

    /* Process the ticks. */
    for (; ticks != 0; --ticks) {
        if (++tenths  <  10) {continue;} else {tenths  = 0;}
        if (++seconds <  60) {continue;} else {seconds = 0;}
        if (++minutes <  60) {continue;} else {minutes = 0;}
        if (++hours   < 100) {continue;} else {hours   = 0;}
    }

    display_time();
}

/* Initialize and start the timer. */
static void timer_init(void)
{
    if ((pit_fd = open(devpit_fname, O_RDONLY)) < 0) {
        perror("open");
        fprintf(stderr, "Error opening %s.\n", devpit_fname);
        exit(EXIT_FAILURE);
    }

    if (ioctl(pit_fd, PIT_SET_DIVISOR, &divisor) != 0) goto fail;
    if (ioctl(pit_fd, PIT_ENABLE_TICKS)          != 0) goto fail;
    return;

fail:
    perror("ioctl");
    exit(EXIT_FAILURE);
}

/* Timer shutdown. */
static void timer_cleanup(void)
{
    if (pit_fd != -1 && close(pit_fd) != 0) {
        perror("close");
        fprintf(stderr, "Error closing %s.\n", devpit_fname);
    }
}

/* Pause the timer. */
static void timer_stop(void)
{
    if (ioctl(pit_fd, PIT_IGNORE_TICKS) != 0) {
        perror("ioctl");
        exit(EXIT_FAILURE);
    }
}

/* Start or resume the timer. */
static void timer_start(void)
{
    if (ioctl(pit_fd, PIT_ENABLE_TICKS) != 0) {
        perror("ioctl");
        exit(EXIT_FAILURE);
    }
}

/* Called before program quits. */
static void cleanup(void)
{
    timer_cleanup();
    tcsetattr(0, TCSANOW, &old_mode);
    if (devpit_fname != NULL) free(devpit_fname);
}

/* vim: set ts=4 sts=4 sw=4 tw=80 et: */
/* EOF */

