#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <sys/types.h>
#include <sys/param.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <sys/wait.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <errno.h>
#include <fcntl.h>

#define DEFAULT_SERVER_NAME "news"
#define DEFAULT_COMICSRC "/mit/outland/share/lib/default.comicsrc"

typedef struct entry {
    char *name;
    int high;
    struct entry *next;
} Entry;

static Entry *read_comicsrc(void);
static void read_comics(Entry *entries);
static void process_article(FILE *from);
static void connect_to_server(FILE **from, FILE **to);
void write_new_comicsrc(Entry *entries);
static void *emalloc(size_t size);
static void efgets(char *buf, size_t bufsize, FILE *fp);
static void die(char *fmt, ...);

int main(int argc, char **argv)
{
    Entry *entries;

    putenv("SP_NOFALLBACK=");
    freopen("/dev/null", "w", stdout);
    system("attach -h -n -q mime graphics");
    entries = read_comicsrc();
    read_comics(entries);
}

static Entry *read_comicsrc()
{
    FILE *fp;
    char *home, filename[200], buf[200], *p;
    Entry *entries = NULL, *entry, *reversed = NULL, *next;
    int len;

    /* Open the .comicsrc file. */
    home = getenv("HOME");
    if (!home)
	die("No HOME environment variable.");
    strcpy(filename, home);
    if (*home && home[strlen(home) - 1] != '/')
	strcat(filename, "/");
    strcat(filename, ".comicsrc");
    fp = fopen(filename, "r");
    if (!fp)
	fp = fopen(DEFAULT_COMICSRC, "r");
    if (!fp) {
	die("Can't open .comicsrc file.  Create one with a line for each\n"
	    "newsgroup you want to see comics in.");
    }

    /* Read in the lines, creating a list of entries. */
    while (fgets(buf, sizeof(buf), fp)) {
	/* Strip off any trailing newline and carriage return. */
	if (*buf && buf[strlen(buf) - 1] == '\n')
	    buf[strlen(buf) - 1] = 0;
	if (*buf && buf[strlen(buf) - 1] == '\r')
	    buf[strlen(buf) - 1] = 0;

	/* Make a new entry, copy in the group name and high water mark. */
	p = strchr(buf, ':');
	len = (p) ? p - buf : strlen(buf);
	entry = (Entry *) emalloc(sizeof(Entry));
	entry->name = (char *) emalloc(len + 1);
	strncpy(entry->name, buf, len);
	entry->name[len] = 0;
	entry->high = (p) ? atoi(p + 1) : 0;

	/* Chain the new entry into the list and return it. */
	entry->next = entries;
	entries = entry;
    }

    /* Reverse the list so that it's in the right order. */
    for (entry = entries; entry; entry = next) {
	next = entry->next;
	entry->next = reversed;
	reversed = entry;
    }
    return reversed;
}

static void read_comics(Entry *entries)
{
    Entry *entry;
    FILE *from, *to;
    char buf[200];
    int num, first, last, code, artno;

    /* Connect to the server, read the welcome line. */
    connect_to_server(&from, &to);
    efgets(buf, sizeof(buf), from);

    /* For each group, read the comics in it. */
    for (entry = entries; entry; entry = entry->next) {
	fprintf(to, "group %s\r\n", entry->name);
	fflush(to);
	efgets(buf, sizeof(buf), from);
	if (sscanf(buf, "211 %d %d %d", &num, &first, &last) < 3) {
	    fprintf(stderr, "Can't enter group %s, skipping.\n", entry->name);
	    continue;
	}
	fprintf(to, "stat %d\r\n", entry->high);
	fflush(to);
	efgets(buf, sizeof(buf), from);
	while (1) {
	    fprintf(to, "next\r\n");
	    fflush(to);
	    efgets(buf, sizeof(buf), from);
	    sscanf(buf, "%d %d", &code, &artno);
	    if (code != 223)
		break;
	    fprintf(to, "article\r\n");
	    fflush(to);
	    efgets(buf, sizeof(buf), from);
	    if (atoi(buf) != 220) {
		fprintf(stderr, "Failed to read an article, skipping.\n");
		continue;
	    }
	    process_article(from);
	    entry->high = artno;
	    write_new_comicsrc(entries);
	}
    }

    /* Close the NNTP connection. */
    fprintf(to, "quit\r\n");
    fflush(to);
    efgets(buf, sizeof(buf), from);
    fclose(from);
    fclose(to);
}

static void process_article(FILE *from)
{
    char buf[4096];
    FILE *metamail;
    int status;

    metamail = popen("PATH=\"${PATH}:/mit/mime/bin:/mit/graphics/arch/@sys/bin\"; export PATH; exec metamail -d -q -r", "w");
    if (!metamail)
	die("Can't run metamail to process article.");
    while (1) {
	efgets(buf, sizeof(buf), from);
	if (*buf && buf[1] && buf[strlen(buf) - 2] == '\r') {
	    buf[strlen(buf) - 2] = '\n';
	    buf[strlen(buf) - 1] = 0;
	}
	fputs(buf, metamail);
	if (*buf == '.' && (buf[1] == '\r' || buf[1] == '\n'))
	    break;
    }
    status = pclose(metamail);
    if (!WIFEXITED(status) || WEXITSTATUS(status) != 0)
	exit(1);
}

static void connect_to_server(FILE **from, FILE **to)
{
    char *server_name;
    int fd;
    struct hostent *hostent;
    struct servent *servent;
    struct sockaddr_in sin;

    /* Find the address of the news server. */
    server_name = getenv("NNTPSERVER");
    server_name = (server_name) ? server_name : DEFAULT_SERVER_NAME;
    hostent = gethostbyname(server_name);
    if (!hostent)
	die("Can't look up news server address.");
    servent = getservbyname("nntp", "tcp");
    if (!servent)
	die("Can't look up nntp service port.");
    sin.sin_family = AF_INET;
    sin.sin_port = servent->s_port;
    memcpy(&sin.sin_addr, hostent->h_addr, sizeof(struct in_addr));

    /* Get the socket to connect with. */
    fd = socket(AF_INET, SOCK_STREAM, 0);
    if (fd < 0)
	die("Can't get socket: %s", strerror(errno));

    /* Make the connection. */
    if (connect(fd, (struct sockaddr *) &sin, sizeof(sin)) < 0)
	die("Can't connect to the news server.");

    /* Make file handles from the file descriptor. */
    *from = fdopen(fd, "r");
    *to = fdopen(dup(fd), "w");
    if (!*from || !*to)
	die("Can't open file handles to news server.");
}

void write_new_comicsrc(Entry *entries)
{
    char newfilename[200], filename[200];
    FILE *fp;

    strcpy(filename, getenv("HOME"));
    if (*filename && filename[strlen(filename) - 1] != '/')
	strcat(filename, "/");
    strcat(filename, ".comicsrc");
    strcpy(newfilename, filename);
    strcat(newfilename, ".new");
    fp = fopen(newfilename, "w");
    if (!fp)
	die("Can't open new comicsrc file for writing.");
    for (; entries; entries = entries->next)
	fprintf(fp, "%s:%d\n", entries->name, entries->high);
    fclose(fp);
    if (unlink(filename) < 0 && errno != ENOENT) {
	unlink(newfilename);
	die("Can't unlink old comicsrc for update.");
    }
    if (rename(newfilename, filename) < 0)
	die("WARNING!  Unable to move updated comicsrc file in place.");
}

static void *emalloc(size_t size)
{
    void *ptr;

    ptr = malloc(size);
    if (ptr == NULL)
	die("malloc(%d) failed.\n", (int) size);
    return ptr;
}

static void efgets(char *buf, size_t bufsize, FILE *fp)
{
    if (!fgets(buf, bufsize, fp))
	die("Failure reading from news server.");
}

static void die(char *fmt, ...)
{
    char buf[1000];
    va_list args;

    va_start(args, fmt);
    vsprintf(buf, fmt, args);
    va_end(args);
    fputs(buf, stderr);
    putc('\n', stderr);
    exit(1);
}

