#include <iostream.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <string>
#include <time.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>

#include "defines.h"

//
// Function Delcarations
// 
int initSocket(int port);	// Initializes on port 'port', then returns the socket number it initialized.
int getMaxSocket();		// Gets the top socket, used with select(2)
void parseCommandLine(int, char **);	// Parses the command line arugments
void gameLoop();		// Calls the main game loop
void gameCleanup();		// After the mud has shut down, memory cleanup, etc. goes here.
void loadInterpret();		// Where the interpret class loading/linking goes (for actions).

//
// Command line functions
// 
void commandVersion(void);
void commandHelp(void);

//
// Global Variables
//
int listensocket;		// Socket to listen on
int portnumber;			// Port number
struct Character *c_first;	// First descriptor in the list
bool running = true;		// Is the mud still running?

//
// Externed Variables
//
List < Character > c_list;	// Character List
List < BaseInterpret * > i_list; // Interpret List

//
// Start here (main)
//  TODO: add command line options and server shutdown
//
int main(int argc, char **argv)
{
    parseCommandLine(argc, argv);

    listensocket = initSocket(portnumber);

    loadInterpret();

    gameLoop();

    gameCleanup();

    return 0;
}

//
// Initializes and returns listening socket
//
int initSocket(int port)
{
    struct sockaddr_in sin;
    int sock;

    if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
	cerr << strerror(errno) << " ";
	cerr << "Error opening Socket" << endl;
	exit(1);
    }

    memset(&sin, 0, sizeof sin);
    sin.sin_family = AF_INET;
    sin.sin_addr.s_addr = htonl(INADDR_ANY);
    sin.sin_port = htons(port);
/*
    int yes = 1;
    if (setsockopt
	(sock, SOL_SOCKET, SO_REUSEADDR, (void *) yes, sizeof(int)) < 0) {
	cerr << strerror(errno) << " ";
	cerr << "Error setting the socket options" << endl;
	close(sock);
	exit(1);
    }
*/
    if (bind(sock, (struct sockaddr *) &sin, sizeof(sin)) < 0) {
	cerr << strerror(errno) << " ";
	cerr << "Error binding socket" << endl;
	close(sock);
	exit(1);
    }

    if (listen(sock, 5) < 0) {
	cerr << strerror(errno) << " ";
	cerr << "Error opening the socket for listening" << endl;
	close(sock);
	exit(1);
    }

    return sock;
}


//
// Main game loop, "Keep it clean!"
//
void gameLoop()
{
    int client;			// client socket
    int max;			// max socket
    int err;			// error checking
    fd_set exceptionfds, readfds, writefds;	// file decriptor listeners
    struct sockaddr_in addr_client;	// client address
    struct timeval mudtimer;	// MUD Timer
    Character ch;		// Multi-use desecriptor class

    while (running) {

	FD_ZERO(&readfds);	// init readfds
	FD_ZERO(&writefds);	// init writefds
	FD_ZERO(&exceptionfds);	// init exceptionfds
	FD_SET(listensocket, &readfds);	// set readfds to listen for new clients

	//
	// Set up to listen on the descriptors
	//
	CLOOP_START {
	    if (ch.getStatus() == STATUS_QUIT) continue;
	    FD_SET(ch.getSocket(), &readfds);
	    FD_SET(ch.getSocket(), &writefds);
	    FD_SET(ch.getSocket(), &exceptionfds);
	CLOOP_END}
	max = getMaxSocket();	// Finds the largest socket number

	//
	// "If you just listen to the people, they will talk"
	//
	err = select(max + 1, &readfds, &writefds, &exceptionfds, NULL);
	if (err < 0) {
	    cerr << strerror(errno) << " - ";
	    cerr << "Error in polling descriptors.";
	    exit(1);
	}

	//
	// Mud Timer
	//
        mudtimer.tv_sec = 0;
        mudtimer.tv_usec = 1;
	select(0, NULL, NULL, NULL, &mudtimer);

	//
	// Check the socket for new descriptors
	//
	if (FD_ISSET(listensocket, &readfds)) {
	    int len_client = sizeof addr_client;
	    client =
		accept(listensocket, (struct sockaddr *) &addr_client,
		       (socklen_t *) & len_client);	// Get the new client
	    cout << "New connection on socket " << client << endl;
	    if (client == -1) {
		cerr << strerror(errno);
		cerr << "Error getting new connection" << endl;
		exit(1);
	    }
	    Character ch2(client);
	    c_list.listInsert(1, ch2);
	}

	//
	// Read from socket
	//
	CLOOP_START {
	    if (FD_ISSET(ch.getSocket(), &readfds)) {
		string buf;
		char buf2[4000];
		err = read(ch.getSocket(), buf2, sizeof(buf2));
		if (err <= 0) {
		    FD_CLR(ch.getSocket(), &readfds);
		    FD_CLR(ch.getSocket(), &writefds);

		    ch.closeD();
		    c_list.listDelete(count);
		    if (count <= c_list.listLength()) {
		        count--;
		    }
		    continue;
		} else {
		    buf2[err] = '\0';
	   	    buf = buf2;
		    ch.addReadBuf(buf);
		    c_list.listReplace(count, ch);
		}
	    }
	CLOOP_END}

	//
	// Kick out all the exceptions
	//
	CLOOP_START {
	    if (FD_ISSET(ch.getSocket(), &exceptionfds)) {
		FD_CLR(ch.getSocket(), &readfds);
		FD_CLR(ch.getSocket(), &writefds);
		ch.closeD();
		c_list.listDelete(count);
		if (count <= c_list.listLength()) {
		    count--;
		}
	    }
	CLOOP_END}

	//
	// Has the socket finished inputing a line?
	//
	CLOOP_START {
	    if (ch.bufEnd()) {
		ch.setReadyBuf(true);
		c_list.listReplace(count, ch);
	    }
	CLOOP_END}

	//
	// Handle the actions
	//
	handleActions();

	//
	// Write writebuf to every socket
	//
	CLOOP_START {
	    ch.writeBuf();
	CLOOP_END}

	//
	// If they selected quit, kick them out
	//
	CLOOP_START {
	    if (ch.getStatus() == STATUS_QUIT) {
		FD_CLR(ch.getSocket(), &readfds);
		FD_CLR(ch.getSocket(), &writefds);
		ch.closeD();
		c_list.listDelete(count);
		if (count <= c_list.listLength()) {
		    count--;
		}
	    }
	CLOOP_END}

    }

}

int getMaxSocket()
{
    int currmax = 0;
    Character ch;

    // Only sockets should be descriptors or listen socket
    CLOOP_START {
	if (ch.getSocket() > currmax)
	    currmax = ch.getSocket();
    CLOOP_END}

    if (listensocket > currmax)
	currmax = listensocket;

    if (currmax == 0) {
	cerr << "Error: no sockets open" << endl;
	exit(1);
    }

    return currmax;
}

//
// Load Interpret Classes here after you define them in interpret.h and interpret.cpp
//  Try to keep them in alphabetical order
//
void loadInterpret()
{
    // The pointer used in the macros
    BaseInterpret *iClassBasePtr;

    LOAD_INTERPRET(InterpretHelp);
    LOAD_INTERPRET(InterpretPassword);
    LOAD_INTERPRET(InterpretQuit);
    LOAD_INTERPRET(InterpretSave);
    LOAD_INTERPRET(InterpretSay);
    LOAD_INTERPRET(InterpretShutdown);
    LOAD_INTERPRET(InterpretWho);
}

//
// Can't think of much that would go here, but in case it needs it, it gets it's own function
//
void gameCleanup()
{
    Character ch;
    //
    // Cleanup lists
    //

    // Character List
    while (c_list.listLength() > 0) {
	c_list.listRetrieve(1, ch);
	ch.closeD();
	c_list.listDelete(1);
    }

    // Interpret List
    while (i_list.listLength() > 0) {
	i_list.listDelete(1);
    }
}

// Parse command line options
void parseCommandLine(int argc, char **argv)
{
    int argCount = 1;		// Temp variable for parsing arguments

    // Parse command line arguments
    while (argCount < argc) {
	if (argv[argCount][0] == '-') {
	    switch (argv[argCount][1]) {
	    case 'p':		// Set port number
		if ((argCount + 1 < argc) && (argv[argCount + 1][1] != '-')
		    && (atoi(argv[argCount + 1]) > 1024)
		    && (atoi(argv[argCount + 1]) < 65535)) {
		    portnumber = atoi(argv[argCount + 1]);
		} else {
		    commandHelp();
		    exit(1);
		}
		break;
	    case 'v':		// Show version information
		commandVersion();
		exit(0);
		break;
	    case 'h':		// Show help
	    case '?':		// Show help
	    default:		// Show help by default
		commandHelp();
		exit(0);
		break;
	    }
	}
	else { // Wrong parameter format
	    commandHelp();
	    exit(0);
	}
	argCount += 2;
    }

    if (argCount == 1) {
	portnumber = 4500;
    }

    // Print this to everyone regarless of argument when the program starts
    commandVersion();
 
    cout << "Starting COWMud on port " << portnumber << endl;
}

// Prints out usage instructions
void commandHelp(void)
{
    cout << "cowmud [OPTIONS]" << endl
	<< "Options:" << endl
	<< " -p NUM     Use port NUM, 1024 < NUM < 65535" << endl
	<< "               Default: 4500" << endl
	<< " -h         This help screen" << endl
	<< " -v         Display version information" << endl;
}

// Prints version information
void commandVersion(void)
{
    cout << "CowMUD is a simple MUD, using object oriented programming." <<
	endl;
    cout << "CowMUD version " << VERSION << endl;
}