#include <stdio.h>

#define TRUE (1)
#define FALSE (0)

struct gstr
{
  char *group, *last;
  int unread;
  struct gstr *next;
};

struct twoway
{
  FILE *sock_rd, *sock_wt;
};

/* ReadGroup is the main reading loop of the program -- it cycles
 * through as many articles as it can of the group named, until it
 * runs out, the user quits, or an error occurs.  Under any of those
 * circumstances, it exits with an appropriate message.
 */

void ReadGroup(sock_rw, tptr)
     struct gstr *tptr;
     struct twoway *sock_rw;
{
  int temp, fwd = TRUE, cnt, namewid = 80, artsz;
  char query, newart[16];
  static char *fname = NULL;

  void GetBody(), SelectGroup(), CheckError(), error();
  int GetNext(), GetSubject();

  printf("\nReading group %s\n", tptr->group);

/* Set last from the pointer, select group. */

  SelectGroup(tptr, strcpy(newart, tptr->last), sock_rw);

/* Start cycling through articles. */

  while(TRUE)
    {

/* Get the header of the current article, and print out Subject: and
 * From: lines, ask if user wants to read the article.
 */

      if (GetSubject(sock_rw, NULL) != 0)
	return;
      fwd = TRUE;
      printf("Read article #%s? (y/n/q)? ", tptr->last);
      query = getchar();

/* Get the body of the current article and print it out, flag the end,
 * and wait for the user to finish reading.
 */

      switch(query)
	{
	case 'n': case 'N': case '\177':
	  printf("\n\n");
	  break;
	case 'q':
	  printf("\n");
	  return;
	case 'Q':
	  printf("\n");
	  Shutdown();
	case '-':
	  printf("\n\n");
	  fwd = FALSE;
	  break;
	case 's': case 'S':
	  query = getchar();
	  if (query == '-')
	    {
	      fwd = FALSE;
	      temp = GetNext(sock_rw, fwd);
	      query = ' ';
	    }
	  cnt = 0;
	  while (query == ' ' &&
		 query != '\n' &&
		 query != '\r')
	    query = getchar();
	  while (query != '\n' && query != '\r')
	    {
	      if (fname == NULL)
		{
		  fname = (char *)malloc(sizeof(char) * namewid);
		  if (fname == NULL)
		    CheckError("Nmsgs: ReadGroup: malloc");
		}
	      *(fname + cnt++) = query;
	      if (cnt > namewid - 1)
		{
		  namewid <<= 1;
		  if (namewid <= cnt)
		    {
		      printf("Filename too long\n");
		      Shutdown();
		    }
		  fname = (char *)realloc((char *)fname, (sizeof(char) *
							  namewid));
		}
	      query = getchar();
	    }
	  if (cnt == 0)
	    {
	      if (fname == NULL)
		{
		  fname = (char *)malloc(sizeof(char) * namewid);
		  if (fname == NULL)
		    CheckError("Nmsgs: ReadGroup: malloc");
		}
	      if (namewid <= (strlen(tptr->group) + strlen(tptr->last) + 2))
		{
		  namewid = (strlen(tptr->group) + strlen(tptr->last) + 2);
		  fname = (char *)realloc(fname, sizeof(char) * namewid);
		  if (fname == NULL)
		    CheckError("Nmsgs: ReadGroup: realloc");
		}
	      sprintf(fname, "%s.%d", tptr->group, (atoi(tptr->last)
						    + (fwd == TRUE ? 1 : 0)
						    - 1));
	    }
	  else
	    *(fname + cnt) = '\0';
	  GetSubject(sock_rw, fname);
	  GetBody(sock_rw, fname);
	  printf("\nWritten to %s\n\n", fname);
	  fwd = TRUE;
	  break;
	case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9':
	  artsz = 0;
	  newart[artsz++] = query;
	  query = getchar();
	  while (query >= '0' && query <= '9')
	    {
	      newart[artsz++] = query;
	      if (artsz >= 16)
		error("Nmsgs: ReadGroup: Article number too long.");
	      query = getchar();
	    }
	  while (query != '\n' && query != '\r')
	    query = getchar();
	  SelectGroup(tptr, newart, sock_rw);
	  printf("\n");
	  continue;
	default:
	  printf("\n\n");
	  GetBody(sock_rw, NULL);
	  printf("*** End of message %s.  Hit any key to continue ***",
		 tptr->last);
	  getchar();
	  printf("\n\n");
	}

      temp = GetNext(sock_rw, fwd);
      if (temp == -1)
	{
	  sprintf(tptr->last, "%d", atoi(tptr->last) + 1);
	  return;
	}
      else if (temp != -2)
	sprintf(tptr->last, "%d", temp);
    }
}


void SelectGroup(tptr, art, sock_rw)
     struct gstr *tptr;
     char *art;
     struct twoway *sock_rw;
{
  FILE *sock_rd, *sock_wt;
  char *send, *dump;
  int status, fill, first, last, prev, rqst;
  void PutLine(), CheckError();
  char *GetLine();

  sock_rd = sock_rw->sock_rd;
  sock_wt = sock_rw->sock_wt;

  send = (char *)malloc(sizeof(char) * (strlen("group ") +
					strlen(tptr->group) + 3));
  if (send == NULL)
    CheckError("Nmsgs: SelectGroup: malloc");
  sprintf(send, "group %s\r\n", tptr->group);
  PutLine(send, sock_wt);
  dump = GetLine(sock_rd);
  sscanf(dump, "%d %d %d %d", &status, &fill, &first, &last);

  prev = atoi(tptr->last);
  rqst = atoi(art);
  sprintf(send, "stat %d\r\n", rqst);
  PutLine(send, sock_wt);
  dump = GetLine(sock_rd);
  sscanf(dump, "%d", &status);
  if (status == 223)
    {
      sprintf(tptr->last, "%d", rqst);
      return;
    }

  while (rqst != prev)
    {
      sprintf(send, "stat %d\r\n", rqst);
      PutLine(send, sock_wt);
      dump = GetLine(sock_rd);
      sscanf(dump, "%d", &status);
      if (status == 223)
	{
	  sprintf(tptr->last, "%d", rqst);
	  return;
	}
      if ((rqst % 50) == 0)
	{
	  printf("%d...", rqst);
	  fflush(stdout);
	}
      rqst += (rqst < prev) ? 1 : -1;
    }
  rqst = atoi(art);
  while (rqst <= last && rqst >= first)
    {
      sprintf(send, "stat %d\r\n", rqst);
      PutLine(send, sock_wt);
      dump = GetLine(sock_rd);
      sscanf(dump, "%d", &status);
      if (status == 223)
	{
	  sprintf(tptr->last, "%d", rqst);
	  return;
	}
      if ((rqst % 50) == 0)
	{
	  printf("%d...", rqst);
	  fflush(stdout);
	}
      rqst += (rqst < prev) ? -1 : 1;
    }
}


/* GetSubject gets the header lines of the current article from the
 * socket, and prints the From, Subject, and Newsgroups lines.
 */

int GetSubject(sock_rw, fname)
     struct twoway *sock_rw;
     char *fname;
{
  FILE *sock_wt, *sock_rd, *more;
  char *dump;
  int status;

  char *GetLine();
  void PutLine();

/* Set the sockets from the socket pointer. */

  sock_rd = sock_rw->sock_rd;
  sock_wt = sock_rw->sock_wt;

/* Write the head request to the server. */

  PutLine("head\r\n", sock_wt);
  dump = GetLine(sock_rd);

/* Check the status. */

  sscanf(dump, "%d", &status);

/* Go to next group on failure.  More graceful? */

  if (status != 221)
    {
      if (status == 420)
	  printf("No articles in this group.\n");
      else
	printf("Header requested not received\n");
      return(1);
    }

/* Open the output stream through more. */

  if (fname != NULL)
    {
      more = fopen(fname, "w");
      if (more == NULL)
	CheckError("Nmsgs: GetSubject: fopen");
    }
  else
    more = stdout;

/* Print From, Subject, and Newsgroups lines of the header block, and return.
 */

  while(TRUE)
    {
      dump = GetLine(sock_rd);
      if (*dump == '.' && *(dump+1) == '\0')
	break;
      if (strncmp("Subject", dump, 7) == 0 ||
	  strncmp("From", dump, 4) == 0 ||
	  strncmp("Newsgroups", dump, 10) == 0)
	fprintf(more, "%s\n", dump);
    }
  if (more != stdout)
    fclose(more);
  return(0);
}


/* GetBody gets the body of the current article from the socket and
 * pipes them through more, or to a requested file name.
 */

void GetBody(sock_rw, fname)
     struct twoway *sock_rw;
     char *fname;
{
  FILE *more, *sock_wt, *sock_rd;
  char *dump, *errout;
  int status;

  char *GetLine();
  void PutLine(), error(), CheckError();

/* Set the sockets from the socket pointer. */

  sock_rd = sock_rw->sock_rd;
  sock_wt = sock_rw->sock_wt;

/* Write the body request to the server */

  PutLine("body\r\n", sock_wt);
  dump = GetLine(sock_rd);

/* Check the status. */

  sscanf(dump, "%d", &status);

/* Exit on failure -- this probably should be more graceful. */

  if (status != 222)
    error("Article requested not recieved");

/* Open the output stream through more. */

  if (fname == NULL)
    more = popen("more", "w");
  else
    more = fopen(fname, "w");

  if (more == NULL)
    {
      errout = (char *)malloc(sizeof(char) *
			      (strlen("Open failed to ")
			       + strlen((fname == NULL) ? "more" : fname)));
      if (errout == NULL)
	CheckError("Nmsgs: GetBody: malloc");
      sprintf(errout, "Open failed to %s", (fname == NULL) ? "more" : fname);
      error(errout);
    }

/* Pump each line from the message body through more, and exit. */

  while(TRUE)
    {
      dump = GetLine(sock_rd);
      if (*dump == '.' && *(dump+1) == '\0')
	{
	  if (fname == NULL)
	    pclose(more);
	  else
	    fclose(more);
	  return;
	}
      fprintf(more, "%s\n", dump);
      fflush(more);
    }
}


/* GetNext gets the header of the next article in the selected
 * newsgroup and returns the number of the article.  If there is no
 * next article, or the request fails, it returns -1.
 */

int GetNext(sock_rw, fwd)
     struct twoway *sock_rw;
     int fwd;
{
  FILE *sock_wt, *sock_rd;
  int status, art;
  char *dump;

  void PutLine();
  char *GetLine();

  sock_rd = sock_rw->sock_rd;
  sock_wt = sock_rw->sock_wt;

/* Send the next article request to the server. */

  if (fwd == TRUE)
    PutLine("next\r\n", sock_wt);
  else
    PutLine("last\r\n", sock_wt);

/* Catch and sanity-check the header. */

  dump = GetLine(sock_rd);
  sscanf(dump, "%d %d", &status, &art);

/* Abnormal failure -- say so and exit. */

  if (status != 223 && status != 421 && status != 422)
    {
      printf("Next article request failed\n");
      return(-1);
    }

  if (status >= 400)
    {
      if (fwd = TRUE)
	return(-1);
      else
	{
	  printf("No previous article\n\n");
	  return(-2);
	}
    }

/* Return the article number. */

  return(art);
}
