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

#define MAX_CONNECT 256

#define AUTH '0'
#define MUDLIST '1'
#define TELL '3'
#define CHAT '4'
#define WHO '5'
#define RWHO '6'
#define INFOMSG '7'
#define MSG '8'
#define KICK '9'
#define AVERSION ':'
#define FILEPAGE ';'

#define TO_MUD '0'
#define TO_ALL '1'

#define SEP '\007'

#define D "\007"

#define WRITELEN 4096

#define SERVER_VERSION "AberChat Server 5.1"
#define SERVER_NAME "BetaServer"

int listener_fd = -1;

class conn {
 public:
    struct sockaddr_in addr;
    int fd;

    FILE *f;
    
    const char *get_name() { return name; };
    const char *get_lib() { return lib; };
    const char *get_host() { return host; };
    const char *get_ip() { return ip; };
    const char *get_port() { return port; };
    
    void set_name(const char *n) { name = strdup(n); };
    void set_port(const char *n) { port = strdup(n); };
    void set_host(const char *n) { host = strdup(n); };
    void set_lib(const char *n) { lib = strdup(n); };
    void set_ip(const char *n) { ip = strdup(n); };
    
    conn() {
	name = 0;
	lib = 0;
	host = 0;
	port = 0;
	ip = 0;
	buffer = 0;
	buflen = 0;
	bufsize = 0;
	auth = 0;
	f = 0;
	fd = -1;
    }
    
    void assocfd() {
	f = fdopen(fd, "r");
    }
    
    ~conn() {
	if (name) free(name);
	if (lib) free(lib);
	if (host) free(host);
	if (port) free(port);
	if (ip) free(ip);
	if (buffer) free(buffer);
	if (f) fclose(f);
	if (fd != -1) close(fd);
    }
    
    void queue(char *what, int len) {
	if ((len+buflen)>bufsize) {
	    bufsize = (len+buflen)*2;
	    if (buffer)
		buffer = (char *)realloc(buffer, bufsize);
	    else
		buffer = (char *)malloc(bufsize);
	}
	memcpy(buffer+buflen, what, len);
	buflen+=len;
    }

    void finished() {
	char thing[] = {0};
	queue(thing, 1);
    }

    void dequeue(int fd) {
	write(fd, buffer, buflen);
	buflen = 0;
    }
    
    bool auth;
    
 private:
    
    char *name, *lib, *host, *port, *ip, *buffer;
    int buflen, bufsize;
};

conn *conns[MAX_CONNECT] = {0};

conn *alloc_conn() {
    int i;
    for (i=0;i<MAX_CONNECT;i++) {
	if (!conns[i]) {
	    conns[i] = new conn();
	    return conns[i];
	}
    }
    return 0;
}

void get_connection() {
    struct sockaddr_in addr;
    socklen_t what = sizeof addr;
    int fd = accept(listener_fd, (struct sockaddr*)&addr, &what);
    if (fd!=-1) {
	conn *c = alloc_conn();
	c->addr = addr;
	c->fd = fd;
	c->set_ip(inet_ntoa(c->addr.sin_addr));
	c->set_host(inet_ntoa(c->addr.sin_addr));
    }
}

char *get_arg(char *txt) {
  static char c = '0';
  char *ptr;
    
  if ((ptr = strchr(txt, SEP))) {
    *ptr = '\0';
    return(++ptr);
  }
  else 
    return(&c);
}


void cprintf(conn *what, const char *format, ...) 
    __attribute__ ((format(printf, 2, 3)));

void cprintf(conn *what, const char *format, ...)
{
    va_list pvar;
    static char buffer[WRITELEN];
    va_start(pvar, format);
    vsprintf(buffer, format, pvar);
    va_end(pvar);
    what->queue(buffer, strlen(buffer));
}

void authenticate(char *packet, conn **what) {
    char *name = packet, *lib, *port, *pass;
    lib = get_arg(name);
    port = get_arg(lib);
    pass = get_arg(port);
            
    
    bool found = false;
    
    FILE * f = fopen("muds", "r");
    if (!f) {
	fprintf(stderr, "cant open mudsfile.\n");
	exit(1);
    }
        
    do {
	char mudline[1024];
	char a_port[128], a_ip[128], a_pass[128], c_pass[128];
	fgets(mudline, 1024, f);
	if (feof(f)) break;
	sscanf(mudline, "%s %s %s", a_ip, a_port, a_pass);
	strcpy(c_pass, crypt(a_pass, "Ac"));
	if (feof(f)) break;
	if (!strcmp(a_ip, (*what)->get_ip()) &&
	    !strcmp(a_port, port) &&
	    !strcmp(c_pass, pass)) {
	    found = 1;
	    break;
	}
    } while(1);

    if (found || 1) {
     // fprintf(stderr, "Yes.\n");

	for (int i=0;i<MAX_CONNECT;i++) 
	  if (conns[i] && conns[i]->auth) {
	    cprintf(conns[i], "%c" D "%s" D "%s",
		    INFOMSG, name, "Connected!");
	    conns[i]->finished();
	  }
	
    
	(*what)->set_name(name);
	(*what)->set_lib(lib);
	(*what)->set_port(port);
	(*what)->auth = true;

	cprintf(*what, "%c" D "Y", AUTH);
	(*what)->finished();
	
    } else {
    //  fprintf(stderr, "No.\n");

	cprintf(*what, "%c" D "N", AUTH);
	(*what)->finished();
	delete *what;
	*what = 0;
    }
	
}

int cmp_conn(const void *a, const void *b) {
    if (a > b) return 1;
    if (b < a) return -1;
    return 0;
}

void mudlist(char *player, conn **what) {
    conn *muds[MAX_CONNECT];
    int len, i;
    for (i = 0, len = 0 ; i < MAX_CONNECT ; i++)
	if (conns[i] && conns[i]->auth)
	    muds[len++] = conns[i];
    
    qsort(muds, len, sizeof(conn *), cmp_conn);
    
    cprintf(*what, "%c" D "%s", MUDLIST, player);
    
    for (i = 0; i<len; i++) {
	cprintf(*what, D "%s" D "%s" D "%s" D "%s" D "%s",
		
		muds[i]->get_name(),
		muds[i]->get_host(),
		muds[i]->get_ip(),
		muds[i]->get_port(),
		muds[i]->get_lib());
    }

    (*what)->finished();
}

inline int min(int a, int b) {
    if (a < b) return a;
    return b;
}

bool eqmud(const char *mudindex, const char *mudname) {
    if (!strncasecmp(mudindex, "The ", 4)) mudindex+=4;
    if (!strncasecmp(mudname, "The ", 4)) mudname+=4;
    
    if (!strncasecmp(mudindex, mudname, min(strlen(mudindex), strlen(mudname))))
	return true;
    
    return false;
}

struct conn *find_mud(const char *text) {
    for (int i=0;i<MAX_CONNECT;i++)
	if (conns[i] && conns[i]->auth && 
	    eqmud(conns[i]->get_name(), text))
	    return conns[i];
    return NULL;
}

void version(char *buff, conn **what) {
    char *player, *cli_ver;
    player = buff;
    cli_ver = get_arg(player);
    cprintf(*what, "%c" D "%s" D "%s" D "%s", 
	    AVERSION, player, cli_ver, SERVER_VERSION);
    (*what)->finished();
}

bool has_line(const char *ln) {
    FILE *f = fopen("pager-files", "r");
    if (!f) return false;
    char buffer[4096];
    while (!feof(f)) {
	fgets(buffer, 4095, f);
	if (char *p=strchr(buffer, '\n')) *p=0;
	if (!strcmp(buffer, ln)) {
	    fclose(f);
	    return true;
	}
    }
    fclose(f);
    return false;
}

void filepage(char *to_plr, conn **what) {
    // I despair. Whoever designed this should be shot.
    char *filename = get_arg(to_plr);
    char *lt = get_arg(filename);
    if (!lt) return;
    if (!has_line(filename)) {
	cprintf(*what, "%c" D "%s" D "Learn to type, muppet.\n" D "%c",
		FILEPAGE, to_plr, 0);
	(*what)->finished();
	return;
    }
    FILE *f = fopen(filename, "r");
    if (!f) {
	cprintf(*what, "%c" D "%s" D "No such file.\n" D "%c",
		FILEPAGE, to_plr, 0);
	(*what)->finished();
	return;
    }
    int linenum = (atoi(lt)) * 22;
    char buffer[4096];
    cprintf(*what, "%c" D "%s" D, FILEPAGE, to_plr);
    while (!feof(f) && linenum--)
	fgets(buffer, 4095, f);
    if (!feof(f)) 
	for (linenum = 23; linenum > 0 ; linenum--) {
	    if (linenum == 5) {
		linenum = 5;
	    }
	    fgets(buffer, 4095, f);
	    if (feof(f)) break;
	    cprintf(*what, "%s", buffer);
	}
    cprintf(*what, D "%c", feof(f) ? '0' : '1');
    (*what)->finished();
    fclose(f);
}

void tomud(char *packet, conn **what) {
    char *data = get_arg(packet), type, *args;
    args = data + 2;
    type = *data;
    char *mudname = packet;
    struct conn *c = mudname ? find_mud(mudname) : 0;

   // fprintf(stderr, "got tomud packet type %i\n", type);
    
    if (type != AUTH) {
	if (!c) {
	    char *player = get_arg(data);
	    if (type == WHO) {
		player = get_arg(player);
	    }
	    if (char *p=strchr(player, SEP)) *p=0;
	    cprintf(*what, "%c" D "%s" D "%s" D "%s", 
		    MSG, player, SERVER_NAME, "No such mud connected.");
	    (*what)->finished();
	    return;
	}
    }
	
    switch (type) {
     case AUTH:
	authenticate(args, what);
	break;
     case MUDLIST:
	mudlist(args, what);
	break;
     case AVERSION:
	version(args, what);
	break;
     case FILEPAGE:
	filepage(args, what);
	break;

     default:
	if (c) {
	    cprintf(c, "%s", data);
	    c->finished();
	}
    }
}

void toall(char *data, conn **) {
    for (int i=0;i<MAX_CONNECT;i++) 
	if (conns[i] && conns[i]->auth) {
	    cprintf(conns[i], "%s", data);
	    conns[i]->finished();
	}
}

void printpacket(const char *a) {
    char f[4096];
    strcpy(f, a);
    char *p = f;
    while (*p) {
	if (*p==SEP)
	    *p = '|';
	p++;
    }
}

void do_read(conn **what) {
    char buffer[4096];
    int len;
    memset(buffer, 0, sizeof buffer);
    len = recv((*what)->fd, buffer, sizeof buffer, 0);
    if (len < 1) {
	for (int i=0;i<MAX_CONNECT;i++) 
	  if (conns[i] && conns[i]->auth && conns[i] != *what) {
	    cprintf(conns[i], "%c" D "%s" D "%s",
		    INFOMSG, (*what)->get_name(), "Disconnected!");
	    conns[i]->finished();
	  }

	delete *what;
	*what=0;

    } else {
	switch (buffer[0]) {
	 case TO_MUD:
	    tomud(buffer+2, what);
	    break;
	 case TO_ALL:
	    toall(buffer+2, what);
	    break;
	}
	
    }
}

int main() {
    
    struct sockaddr_in addr = { AF_INET, htons(6715), { 0, } };
    int yes = 1;
    
    listener_fd = socket(AF_INET, SOCK_STREAM, 0);
    setsockopt(listener_fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof yes);
    bind(listener_fd, (struct sockaddr *)&addr, sizeof addr);
    listen(listener_fd, 2);
    
    while (1) {

	fd_set rd, ex;
	int maxfd = listener_fd, i;
	FD_ZERO(&rd);
	FD_ZERO(&ex);
	FD_SET(listener_fd, &rd);
	FD_SET(listener_fd, &ex);
	for (i=0;i<MAX_CONNECT;i++) if (conns[i]) {
	    FD_SET(conns[i]->fd, &rd);
	    FD_SET(conns[i]->fd, &ex);
	    if (maxfd < conns[i]->fd) maxfd = conns[i]->fd;
	}

	for (i=0;i<MAX_CONNECT;i++)
	    if (conns[i])
		conns[i]->dequeue(conns[i]->fd);

	select(maxfd+1, &rd, 0, &ex, 0);
	if (FD_ISSET(listener_fd, &rd)) get_connection();
	
	for (i=0;i<MAX_CONNECT;i++) {
	    if (conns[i] && FD_ISSET(conns[i]->fd, &rd))
		do_read(&conns[i]);
	}

    }
}