//
// Gopher.cc
//
// (c) Copyright 1993, San Diego State University -- College of Sciences
//       (See the COPYRIGHT file for more Copyright information)
//
// Implementation of the Gopher class
//
#include "Gopher.h"
#include "String.h"
#include "Connection.h"
#include "GWindow.h"
#include "Response.h"
#include <unistd.h>
#include <stdio.h>
#include <xview/notify.h>
#include <fcntl.h>
#include <time.h>

#define	LINE_COUNT_INCREMENT	20
#define	MAX_LIST_LINES			1023


//***************************************************************************
// Gopher::Gopher(GWindow *gwin)
//
Gopher::Gopher(GWindow *gwin)
{
	fd = -1;
	gwindow = gwin;
	*filename = '\0';
	connection = (Connection *) 0;
}


//***************************************************************************
// Gopher::~Gopher()
//
Gopher::~Gopher()
{
	if (connection->isopen())
		(void) notify_set_input_func((Notify_client) this, NOTIFY_FUNC_NULL, connection->get_socket());
	fd = -1;
	if (*filename)
		unlink(filename);
	if (connection)
		delete connection;
}


//***************************************************************************
// Gopher::Gopher(char *server, int port, GWindow *gwin)
//
Gopher::Gopher(char *server, int port, GWindow *gwin)
{
	fd = -1;
	gwindow = gwin;
	*filename = '\0';
	if (open(server, port) == NOTOK)
		exit(1);
}


//***************************************************************************
// int Gopher::open(server, port)
// PURPOSE:
//    Build a connection with the gopher server.
//
int Gopher::open(char *server, int port)
{
	connection = new Connection;

	if (connection->open() == NOTOK)
		return NOTOK;
	if (connection->assign_port(port) == NOTOK)
		return NOTOK;
	if (connection->assign_server(server) == NOTOK)
		return NOTOK;
	if (connection->connect() == NOTOK)
		return NOTOK;

	return OK;
}


//***************************************************************************
// int Gopher::read(char type, char *cmd)
// PURPOSE:
//    Send cmd to the server and receive the information until the
//    connection is closed by the server
//
int Gopher::read(char type, char *cmd)
{
	gwindow->status("Retrieving information...");

	//
	// First send the command.  We need to terminate the command with
	// a return.  Since we do not know how big cmd is, we will just make
	// it into two writes.
	//
	if (cmd == NULL)
		cmd = "";
	connection->write(cmd, strlen(cmd));
	connection->write("\r\n", 2);

	//
	// Now we are ready to receive the information.  The type of information
	// depends on the command we sent to the server, so we will use it
	// to determine where this information will be stored.
	//
	switch (type)
	{
		case GOPHER_FILE:
			//
			// Plain ASCII file coming through...  Save it someplace
			//
			read_ascii();
			break;
		case GOPHER_DIRECTORY:
		case '\0':
			//
			// We are getting a directory listing.  Read it into our List
			//
			read_list();
			break;
		case GOPHER_CSO:
			break;
		case GOPHER_ERROR:
			break;
		case GOPHER_UU:
			break;
		case GOPHER_INDEX:
			break;
		case GOPHER_TELNET:
			break;
		case GOPHER_BINHEX:
		case GOPHER_DOS:
		case GOPHER_BIN:
			//
			// Some binary file coming in.  Read until connection closes
			//
			read_binary();
			break;
		case GOPHER_SOUND:
			//
			// Some binary file coming in.  Read until connection closes
			//
			read_binary();
			datatype = TYPE_SOUND;
			break;
		case GOPHER_IMAGE:
		case GOPHER_GIF:
			//
			// Some binary file coming in.  Read until connection closes
			//
			read_binary();
			datatype = TYPE_IMAGE;
			break;
		case GOPHER_REDUNDANT:
			break;
		default:
			printf("Hmm.  The I don't know how to read data of type '%c'\n", type);
			break;
	}
	return OK;
}


//***************************************************************************
// void Gopher::start_get()
// PURPOSE:
//    Set up the list of data so we can retrieve data in order
//
void Gopher::start_get()
{
	list.start_get();
}


//***************************************************************************
// char *Gopher::get_next()
// PURPOSE:
//    Get the next item from our list
//
char *Gopher::get_next()
{
	String	*str = (String *) list.get_next();

	if (str)
		return str->get();
	else
		return NULL;
}


//***************************************************************************
// void Gopher::read_list()
//
void Gopher::read_list()
{
	//
	// In order not to block on a read, we will use the xview read notify
	// to tell us when data is actually available.
	//
	datatype = TYPE_LIST;
	length = 0;
	(void) notify_set_input_func((Notify_client) this, (Notify_func) read_list_proc, connection->get_socket());
}

#define	INPUT_BUF_SIZE	50000

//***************************************************************************
// void Gopher::read_list_proc(Gopher *gopher, int ifd)
//
void Gopher::read_list_proc(Gopher *gopher, int ifd)
{
	char			buffer[INPUT_BUF_SIZE];
	static time_t	last = 0;
	char			*p;

	if (gopher->connection->read_line(buffer, INPUT_BUF_SIZE) == NULL || gopher->length > MAX_LIST_LINES)
	{
		//
		// End of file reached.
		//
		(void) notify_set_input_func((Notify_client) gopher, NOTIFY_FUNC_NULL, ifd);
		gopher->connection->close();
		gopher->gwindow->display();
		sprintf(buffer, "Total of %d item%s", gopher->length, gopher->length == 1 ? "" : "s");
		gopher->gwindow->status(buffer);

		if (gopher->length == 0)
		{
			//
			// Nothing was found.  Let the user know...
			//
			gopher->gwindow->nothing_found();
		}
	}
	else
	{
		if (*buffer == '.' && buffer[1] == '\0')
			return;
		p = buffer;
		while (*p == '-' || *p > 126 || *p < 32)
			p++;
		gopher->list.add(new String(p));

		//
		// Keep a running count and display the results every second
		//
		gopher->length++;
		if (gopher->length > MAX_LIST_LINES)
			gopher->gwindow->list_full();

		if (last < time(NULL))
		{
			char str[20];
			sprintf(str, "%d line%s...", gopher->length, gopher->length == 1 ? "" : "s");
			gopher->gwindow->status(str);
			last = time(NULL);
		}
	}
}


//***************************************************************************
// void Gopher::read_ascii()
//
void Gopher::read_ascii()
{
	datatype = TYPE_ASCII;
	strcpy(filename, "/tmp/gophXXXXXX");
	fd = mkstemp(filename);
	length = 0;
	(void) notify_set_input_func((Notify_client) this, (Notify_func) read_ascii_proc, connection->get_socket());
}


//***************************************************************************
// void Gopher::read_ascii_proc(Gopher *gopher, int ifd)
//
void Gopher::read_ascii_proc(Gopher *gopher, int ifd)
{
	char			buffer[INPUT_BUF_SIZE];
	static time_t	last = 0;

	if (gopher->connection->read_line(buffer, INPUT_BUF_SIZE) == NULL)
	{
		//
		// End of file reached.
		//
		::close(gopher->fd);
		(void) notify_set_input_func((Notify_client) gopher, NOTIFY_FUNC_NULL, ifd);
		gopher->connection->close();
		gopher->gwindow->display();
		char str[30];
		sprintf(str, "Total of %d lines", gopher->length);
		gopher->gwindow->status(str);
	}
	else
	{
		if (*buffer == '.' && buffer[1] == '\0')
			return;
		write(gopher->fd, buffer, strlen(buffer));
		write(gopher->fd, "\n", 1);
		gopher->length++;
		if (last < time(NULL))
		{
			char str[20];
			sprintf(str, "%d line%s...", gopher->length, gopher->length == 1 ? "" : "s");
			gopher->gwindow->status(str);
			last = time(NULL);
		}
	}
}


//***************************************************************************
// void Gopher::read_binary()
//
void Gopher::read_binary()
{
	datatype = TYPE_BINARY;
	strcpy(filename, "/tmp/gophXXXXXX");
	fd = mkstemp(filename);
	length = 0;
	gwindow->status("Waiting for first data to arrive...");
	(void) notify_set_input_func((Notify_client) this, (Notify_func) read_binary_proc, connection->get_socket());
}


//***************************************************************************
// void Gopher::read_binary_proc(Gopher *gopher, int ifd)
//
void Gopher::read_binary_proc(Gopher *gopher, int ifd)
{
	char			buffer[INPUT_BUF_SIZE];
	static time_t	last = 0;

	int n = gopher->connection->read_partial(buffer, INPUT_BUF_SIZE);
	if (n <= 0)
	{
		//
		// End of file reached.
		//
		::close(gopher->fd);
		(void) notify_set_input_func((Notify_client) gopher, NOTIFY_FUNC_NULL, ifd);
		gopher->connection->close();
		gopher->gwindow->display();
		sprintf(buffer, "Total of %d bytes", gopher->length);
		gopher->gwindow->status(buffer);
	}
	else
	{
		::write(gopher->fd, buffer, n);
		gopher->length += n;
		if (last < time(NULL))
		{
			char status[40];
			sprintf(status, "%d byte%s...", gopher->length, gopher->length == 1 ? "" : "s");
			gopher->gwindow->status(status);
			last = time(NULL);
		}
	}
}


