/* Name:  Chuck Winters
 * Corp:  Lockheed Martin ATL
 * Date:  29 April, 2002
 * File:  basic_c.c
 * Desc:  Basic testing program client
*/

#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <basic_common.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>

#define PROG_NAME "basic_c"

int conn_sock = -1;


void print_help(FILE *f)
{
	fprintf(f, "Usage:  \n");
	fprintf(f, " %s [options] host\n", PROG_NAME);
	fprintf(f, "\nOptions:\n");
	fprintf(f, " -%c num  -  Send num messages instead of default %d messages\n", 
			       NUM_CHAR, DEFAULT_NUM_MSGS);
	fprintf(f, " -%c      -  Receive user level acknowledgements\n", ACK_CHAR);
	fprintf(f, " -%c      -  Set socket to SO_LINGER\n", LINGER_CHAR);
	fprintf(f, " -%c      -  Use Nagle Algorithm (default off)\n", DELAY_CHAR);
	fprintf(f, " -%c num  -  Send to port num instead of default port %d\n", 
			       REMOTE_PORT_CHAR, DEFAULT_REMOTE_PORT);
	fprintf(f, " -%c num  -  Bind to local port num instead of default port %d\n",
			       LOCAL_PORT_CHAR, DEFAULT_LOCAL_PORT);
	fprintf(f, " -%c ip   -  Bind to local address ip instead of default\n",
			       LOCAL_ADDR_CHAR);
	fprintf(f, " -%c      -  Use send(2) instead system call(cannot be used with -%c)\n", 
			       SEND_RECV_CHAR, SEND_RECV_MSG_CHAR);
	fprintf(f, " -%c      -  Use sendmsg(2) system call(cannot be used with -%c)\n",
			       SEND_RECV_MSG_CHAR, SEND_RECV_CHAR);
	fprintf(f, " -%c      -  Sleep %d seconds between sends\n", 
			       SLEEP_CHAR, SLEEP_TIME); 
	fprintf(f, " -%c      -  Print this message\n", HELP_CHAR);
	return ;
}

void cleanup(void)
{
	if(conn_sock > 0)
	{
		while(close(conn_sock) < 0)
			fprintf(stderr, "Attempting to close socket %d\n", conn_sock);
	}
}

int main(int argc, char **argv)
{
	struct sockaddr_in *local_addrs = NULL, *remote_addrs = NULL;
	char c_arg;
	char **local_ips = NULL;
	int valid_l_ips = 0, valid_r_ips = 0, num_r_ips = 0;
	int i, j;
	unsigned bool_args = 0;
	struct in_addr tmp_in;
	char *bad_opt = NULL;
	char *msg[] = MSG;
	int  msg_len = MSG_LEN;



	/** Control Variables with Defaults **/
		int num_msgs = DEFAULT_NUM_MSGS;
		int remote_port = DEFAULT_REMOTE_PORT;
		int local_port = DEFAULT_LOCAL_PORT;

	/** Register cleanup function **/
		atexit(&cleanup);

	/** Parse Command line arguments **/
		while( (c_arg = getopt(argc, argv, OPT_STRING)) != -1)
		{
			switch(c_arg)
			{
				case ACK_CHAR:
						bool_args |= OPT_ACK;
						break;
				case LINGER_CHAR:
						bool_args |= OPT_LINGER;
						break;
				case DELAY_CHAR:
						bool_args |= OPT_DELAY;
						break;
				case SEND_RECV_CHAR:
						if(bool_args&OPT_SEND_RECV_MSG)
						{
							fprintf(stderr, "Cannot use both -%c and -%c\n", 
									    SEND_RECV_CHAR, SEND_RECV_MSG_CHAR);
							print_help(stderr);
							exit(1);
						}
						bool_args |= OPT_SEND_RECV;
						break;
				case SEND_RECV_MSG_CHAR:
						if(bool_args&OPT_SEND_RECV)
						{
							fprintf(stderr, "Cannot use both -%c and -%c\n", 
									    SEND_RECV_MSG_CHAR, SEND_RECV_CHAR);
							print_help(stderr);
							exit(1);
						}
						bool_args |= OPT_SEND_RECV_MSG;
						break;
				case SLEEP_CHAR:
						bool_args |= OPT_SLEEP;
						break;
				case NUM_CHAR:
						num_msgs = strtol(optarg, &bad_opt, 0);
						if(*bad_opt != '\0')
						{
							bad_arg_opt(NUM_CHAR);
							exit(1);
						}
						break;
				case REMOTE_PORT_CHAR:
						remote_port = strtol(optarg, &bad_opt, 0);
						if(*bad_opt != '\0')
						{
							bad_arg_opt(REMOTE_PORT_CHAR);
							exit(1);
						}
						break;
				case LOCAL_PORT_CHAR:
						local_port = strtol(optarg, &bad_opt, 0);
						if(*bad_opt != '\0')
						{
							bad_arg_opt(LOCAL_PORT_CHAR);
							exit(1);
						}
						break;
				case LOCAL_ADDR_CHAR:
						if(inet_aton(optarg, &tmp_in) <= 0)
						{
							bad_arg_opt(LOCAL_ADDR_CHAR);
							exit(1);
						}
						if( !(local_ips = (char** ) realloc(local_ips, (valid_l_ips + 1) * sizeof(char*))) )
						{
							fprintf(stderr, "Out of memory\n");
							exit(1);
						}
						fprintf(stdout, "Adding %d ip %s\n", (valid_l_ips + 1), optarg);
						local_ips[valid_l_ips] = strdup(optarg);
						valid_l_ips++;
						break;
				default:
						print_help(stderr);
						exit(1);
						break;
			}
		}

	/** Now Finish Parsing Command line looking for remote ip's **/
		num_r_ips = argc - optind;

		if(num_r_ips <= 0)
		{
			fprintf(stderr, "You must enter an IP address for the host you wish to contact\n");
			print_help(stderr);
			exit(1);
		}

		fprintf(stdout, "Adding %d addresses\n", num_r_ips);
		for(i = 0, j = optind; i < num_r_ips, j < argc; i++, j++)
		{
			if(inet_aton(argv[j], &tmp_in) <= 0)
			{
				fprintf(stdout, "Bad remote address: %s\n", argv[j]);
				i--;
				continue;
			}
			remote_addrs = (struct sockaddr_in* ) realloc(remote_addrs, sizeof(struct sockaddr_in) * (valid_r_ips + 1));
			if(!remote_addrs)
			{
				fprintf(stderr, "Out of Memory\n");
				exit(1);
			}

			remote_addrs[i].sin_family = AF_INET;
			remote_addrs[i].sin_port = htons(remote_port);
			memcpy(&(remote_addrs[i].sin_addr), &tmp_in, sizeof(struct in_addr));
			print_sockaddr_in(stdout, &(remote_addrs[i]));
			valid_r_ips++;
#if ( PROTO_TYPE == IPPROTO_TCP )
			break;
#endif
		}

	/** Fill in the local IPs **/
		if( !(local_addrs = (struct sockaddr_in* ) malloc(sizeof(struct sockaddr_in) * valid_l_ips)))
		{
			fprintf(stderr, "Out Of Memory\n");
			exit(1);
		}

		for(i = 0; i < valid_l_ips; i++)
		{
			local_addrs[i].sin_family = AF_INET;
			local_addrs[i].sin_port = htons(local_port);
			inet_aton(local_ips[i], &(local_addrs[i].sin_addr));
#if ( PROTO_TYPE == IPPROTO_TCP )
			valid_l_ips = 1;
			break;
#endif
		}
			
	/** Create the Socket **/
		fprintf(stdout, "-- Creating Socket\n");
		if( (conn_sock = socket(PF_INET, SOCKET_TYPE, PROTO_TYPE)) < 0)
		{
			perror("Creating Socket");
			exit(2);
		}
		fprintf(stdout, "-- Created socket with fd of %d\n", conn_sock);

	/** Setup the options **/
		if(bool_args&OPT_LINGER)
		{
			struct linger lin = { on, 3 };

			printf("-- Setting SO_LINGER\n");
			if( setsockopt(conn_sock, SOL_SOCKET, SO_LINGER, &lin, sizeof(struct linger)) < 0 )
			{
				perror("Setting SO_LINGER");
				exit(3);
			}
		}
		if(!(bool_args&OPT_DELAY))
		{
			printf("-- Turning off Nagle Algorithm\n");
			if( setsockopt(conn_sock, PROTO_LEVEL, PROTO_DELAY, &off, sizeof(int)) < 0)
			{
				perror("Turning off Nagle Algorithm");
				exit(3);
			}
		}

#if 0
	/** Bind to local port and address(s) **/
		printf("-- Binding to Local Addresses\n");
		if( !(bind(conn_sock, (struct sockaddr* ) local_addrs, (sizeof(struct sockaddr_in) * valid_l_ips))) < 0)
		{
			perror("Binding to socket");
			exit(3);
		}
#endif

	/** Connect to the Server **/
		printf("-- Connecting to server\n");
		if( connect(conn_sock, (struct sockaddr* ) remote_addrs, (sizeof(struct sockaddr_in) * valid_r_ips)) < 0)
		{
			perror("Error Connecting to Server");
			exit(4);
		}

	/** Start sending data **/ 
		printf("-- Commencing Sending\n");
		for(i = 0; i < num_msgs; i++)
		{
			int ret_val;

			printf("--- %d) Sending %s\n", i, msg[i%MSG_NUM]);
			if(bool_args&OPT_SEND_RECV)
			{
				if( (ret_val = send(conn_sock, msg[i%MSG_NUM], msg_len, 0)) < msg_len )
				{
					if(ret_val < 0)
					{
						perror("Sending data");
						exit(3);
					}
					else
						printf("** Sent %d bytes of %d byte message\n", ret_val, msg_len);
				}
			}
			else if(bool_args&OPT_SEND_RECV_MSG)
			{
				fprintf(stderr, "(Send/Recv)Msg not implemented yet\n");
				exit(4);
			}
			else
			{
				if( (ret_val = write(conn_sock, msg[i%MSG_NUM], msg_len)) < msg_len)
				{
					if(ret_val < 0)
					{
						perror("Writing data");
						exit(4);
					}
					else
						printf("** Sent %d bytes of %d byte message\n", ret_val, msg_len);
				}
			}

			/** If we want to receive acks **/
				if(bool_args&OPT_ACK)
				{
					char ack_msg[ACK_MSG_LEN];

					printf("--- Receiving Acknolegement\n");
					if(bool_args&OPT_SEND_RECV)
					{
						if( (ret_val = recv(conn_sock, ack_msg, ACK_MSG_LEN, MSG_WAITALL)) < ACK_MSG_LEN)
						{
							perror("Receiving Acknowledgement");
							exit(5);
						}
						if(strncmp(ACK_MSG, ack_msg, ACK_MSG_LEN) != 0)
						{
							fprintf(stderr, "Expected Acknowledgement Not Received\n");
							exit(5);
						}
					}
					else if(bool_args&OPT_SEND_RECV_MSG)
					{
						fprintf(stderr, "** (Send/Recv)Msg not implemented yet\n");
						exit(6);
					}
					else
					{
						if( (ret_val = read(conn_sock, ack_msg, ACK_MSG_LEN)) < ACK_MSG_LEN)
						{
							perror("Receiving Acknoledgement");
							exit(5);
						}
						if(strncmp(ACK_MSG, ack_msg, ACK_MSG_LEN) != 0)
						{
							fprintf(stderr, "Expected Acknowledgement Not Received\n");
							exit(5);
						}
					}
				} /** OPT_ACK **/

			/** Wait Some amount of time **/
				if(bool_args&OPT_SLEEP)
				{
					printf("-- Waiting %d seconds before sending anymore data\n", SLEEP_TIME);
					sleep(SLEEP_TIME);
				}
				
		} /** for **/

		/** Later **/
			printf("-- Exiting\n");
			exit(0);
}
