// Server - the network hub for enabling online games

#include <stdexcept>
#include <iostream>
#include <memory>
#include <chrono>
#include <thread>
#include <cstdint>
#include <cstring>
#include <vector>
#include <event2/listener.h>
#include <event2/bufferevent.h>
#include <event2/buffer.h>
#include <arpa/inet.h>
#include <evhttp.h>

#include <unordered_map>
#include <map>
#include <set>
#include <random>
#include <chrono>
#include <algorithm>

#include "core.h"
#include "sha1.h"

static std::random_device random_dev;
static std::mt19937 mtwister(random_dev());
static std::uniform_int_distribution<uint32_t> dist32(1, 4294967295);
typedef std::chrono::high_resolution_clock HighClock;
typedef std::chrono::high_resolution_clock::time_point TimePoint;

struct Player;

struct ConnState {
	// connection state flags
	uint32_t connf;
	uint32_t cpid; // connection's player id once it becomes available
	struct sockaddr address;
	TimePoint last_active; // this is used to watch for timeouts
};

// room contains information about the maze and players inside it
struct Room {
	uint32_t rid; // room id
	bool is_closing; // is this room closing
	std::vector<uint8_t> rmaze; // the actual maze data: each cell is a byte
	uint32_t rwidth; // how wide is this room in cells?
	std::map<uint32_t, Player *> players; // all the players in this room
};

// player's position iside room is counted from the bottom left cell's bottom
// left corner. each cell is 1000 units wide and high
struct Player {
	uint32_t pid; // player id
	uint32_t prid; // player's current room id
	uint16_t xp; // player's experience points (how many rooms completed)
	bool looped; // whether player has already looped in this room
	bool waiting; // waiting for human input to start playing
	bool marking; // when 'm' message received from player
	struct sockaddr address; // player's address and socket
	std::array<int32_t, 2> pos; // player's pos inside room, 1cell = 1000
	std::array<int32_t, 2> vel; // player's velocity, units per second
	std::array<int32_t, 2> initpos; // initial position to respawn once more
	ConnState *connst; // connection associated with this player or nullptr
	TimePoint last_active; // updated only when the player gets disconnected
};

struct ServerState {
	event_base *eb;
	std::map<uint64_t, ConnState> connst; // all active connections
	std::map<uint32_t, Room> rooms; // all active rooms on this server
	std::map<uint32_t, Player> players; // all known players
	std::vector<Room *> campaign; // rooms ordered by size
	uint32_t desired_dispatch_interval_us; // desirable microseconds between
		// dispatch events. when server is overloaded we must skip.
	// time keeping to skip dispatch events to that they don't pile up
	uint32_t nvisitors; // counter: how many players entered the game
	TimePoint prev_dispatch_start, prev_dispatch_end,
			dispatch_start, dispatch_end;
};

// make a room of given size and generate a maze, returns the created room
static Room *mkroom(ServerState &sest, uint32_t width) {
	uint32_t room_id = dist32(mtwister);
	auto res = sest.rooms.emplace(room_id, Room());
	if(!res.second) {
		std::cout << "Error creating room\n";
		return 0;
	}
	Room &room = res.first->second;
	uint32_t ncells = width * width;
	room.rid = room_id;
	room.rwidth = width;
	uint32_t nblocks = ((width / 6) + 1) * ((width / 6) + 1);
	room.rmaze.resize(ncells, 0);
	std::vector<uint8_t> &cell = room.rmaze; // convenience alias

	std::uniform_int_distribution<uint32_t> customdist(0, ncells - 1);
	uint32_t cur = customdist(mtwister);
	std::vector<uint32_t> stack;

	stack.push_back(cur); // add first cell to the stack
	std::vector<uint8_t> visitorder = { 1, 2, 4, 8 };

	while(stack.size()) {
		std::shuffle(visitorder.begin(), visitorder.end(), mtwister);
		bool visited = false; // set to true once visited a cell
		for(uint8_t i = 0; i < 4; ++i) {
			// try to visit selected cell
			switch(visitorder[i]) {
			case 1:
				// try to visit cell above
				if(cur + width >= ncells || cell[cur + width]) {
					// can't visit cell above
					break;
				}
				cell[cur] |= 1; // remove northern wall
				cur += width; // go north
				cell[cur] |= 4; // remove southern wall
				// in reality southern and western walls
				// are redundant, but can simplify some checks.
				// they are not needed for replicating the maze
				stack.push_back(cur);
				visited = true;
				break;
			case 2:
				// going east
				if(cur % width == width - 1 || cell[cur + 1]) {
					break;
				}
				cell[cur] |= 2;
				++cur;
				cell[cur] |= 8;
				stack.push_back(cur);
				visited = true;
				break;
			case 4:
				// going south
				if(cur - width > ncells || cell[cur - width]) {
					break;
				}
				cell[cur] |= 4;
				cur -= width;
				cell[cur] |= 1;
				stack.push_back(cur);
				visited = true;
				break;
			case 8:
				// going west
				if(!(cur % width) || cell[cur - 1]) {
					break;
				}
				cell[cur] |= 8;
				--cur;
				cell[cur] |= 2;
				stack.push_back(cur);
				visited = true;
				break;
			}
			if(visited) {
				break;
			}
		}
		if(!visited) {
			cur = stack.back();
			stack.pop_back();
		}
	}

	cell[cur] |= 16; // add a portal

	return &room;
}

// we must always call this function instead of bufferevent_free() directly,
// because we also need to unlink the associated connection state and
// mark corresponding player as disconnected
static void disconnect(bufferevent *bev, ServerState &sest) {
	ConnState &cs = sest.connst[uint64_t(bev)];
	cs.connf = Connf::FAILED;
	if(cs.cpid) {
		// connection lost, but the player can still reconnect
		Player &player = sest.players[cs.cpid];
		player.connst = nullptr;
		player.last_active = HighClock::now();
		// kick the player out of room
		Room &room = sest.rooms[player.prid];
		room.players.erase(player.pid);
		player.prid = 0;
	}
	// we can now delete connection state associated with this bufferevent
	sest.connst.erase(uint64_t(bev));
	bufferevent_free(bev);

	std::cout << "Bev left in sest.connst: ";
	std::map<uint64_t, ConnState>::iterator it;
	for(it = sest.connst.begin(); it != sest.connst.end(); ++it) {
		struct bufferevent *bev = (struct bufferevent *)(it->first);
		std::cout << bev <<  ", ";
	}
	std::cout << std::endl;

}

// check if string starts with given prefix
static bool starts_with(const char *str, const char *pre) {
	return strncmp(pre, str, strlen(pre)) == 0;
}

// send a websocket ping message
static void wsping(bufferevent *bev) {
	unsigned char buf[32], *cur;
	int nbytes;
	cur = buf;
	*(cur++) = 0x89;
	*(cur++) = 0x0;
	nbytes = bufferevent_write(bev, buf, cur-buf);
}

static void wspong(bufferevent *bev) {
	unsigned char buf[32], *cur;
	int nbytes;
	cur = buf;
	*(cur++) = 0x8a;
	*(cur++) = 0x0;
	nbytes = bufferevent_write(bev, buf, cur-buf);
}

static void wstext(bufferevent *bev, std::string &str) {
	int nbytes = str.size();
	std::string output;
	output.push_back(0x81);
	if(nbytes > 125) {
		output.push_back(126);
		output.push_back(nbytes >> 8);
		output.push_back(nbytes & 0xff);
	} else {
		output.push_back(uint8_t(nbytes));
	}
	output += str;
//	std::cout << "Nbytes = " << nbytes << " output.size() = " <<
//			output.size() << "\n" << output << "\n";
	nbytes = bufferevent_write(bev, output.c_str(), output.size());
}

// accepts connection initiated by client, returns true on success
bool wsconnect(bufferevent *bev, const char *inbuf, size_t inbuflen) {
	const char *cur = strstr(inbuf, "Sec-WebSocket-Key");
	if(!cur || (cur - inbuf + 47) > inbuflen) {
		return false;
	}

	char c_key[25]; // key sent by client
	strncpy(c_key, cur + 19, 24);
	c_key[24] = '\0';
	std::cout << "Client key = '" << c_key << "' " << std::endl;

	// generate Sec-WebSocket-Accept key
	std::string source = std::string(c_key) +
			"258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
	std::string hash;

	SHA1 checksum;
	checksum.update(source);
	hash = checksum.final_bin();

	std::cout << std::endl;

	std::string hash64 = base64_encode(hash);

	std::string reply = "HTTP/1.1 101 Switching Protocols\r\n"
			"Upgrade: websocket\r\n"
			"Connection: Upgrade\r\n"
			"Sec-WebSocket-Accept: " + hash64 + "\r\n\r\n";
	size_t nbytes = bufferevent_write(bev, reply.c_str(), reply.size());

	wsping(bev);

	return true;
}

static bool levelup(ServerState &sest, Player &player) {
	std::cout << "Levelup ??\n";
	player.looped = false;
	player.waiting = true;
	// kick the player out of current room
	if(player.prid) {
		Room &room = sest.rooms[player.prid];
		room.players.erase(player.pid);
		player.prid = 0;
		++player.xp;
	}
	if(player.xp < sest.campaign.size()) {
		// move the player to the next room
		player.prid = sest.campaign[player.xp]->rid;
		Room &room = sest.rooms[player.prid];
		room.players[player.pid] = &player;
		player.pos = { 0, 0 };
	} else {
		// player has finished the game
		// thanks for testing! bye!
		return false;
	}
	return true;
}


// unmask the message in place and returns offset to it or zero if error
static size_t unmask(char *data, int nbytes, int *len) {
	int offset = 0;
	char *cur = data;
	bool ismasked = data[1] & 0x80;
	// determine offset
	size_t datalen = uint8_t(data[1]) & 0x7f;
	if(!datalen) {
		std::cout << "  pong received" << std::endl;
		*len = datalen;
		offset = ismasked? 6: 2;
		return offset;
	}
	if(datalen == 126) {
		datalen = uint8_t(data[2]) << 8 | uint8_t(data[3]);
		offset = ismasked? 8: 4;
	} else if(datalen == 127) {
		if(nbytes < 10) {
			return 0; // this error means give it up
		}
		datalen = uint64_t(uint8_t(data[2])) << 56 |
			uint64_t(uint8_t(data[3])) << 48 |
			uint64_t(uint8_t(data[4])) << 40 |
			uint64_t(uint8_t(data[5])) << 32 |
			uint64_t(uint8_t(data[6])) << 24 |
			uint64_t(uint8_t(data[7])) << 16 |
			uint64_t(uint8_t(data[8])) << 8 |
			uint8_t(data[9]);
		offset = ismasked? 14: 10;
	} else {
		offset = ismasked? 6: 2;
	}

	if(ismasked) {
		// unmask message
		for(int i = 0; i < datalen; ++i) {
			data[i + offset] ^= data[(i % 4) + offset - 4];
		}
	}

	*len = datalen;

	return offset;
}

static void send_stats(struct bufferevent *bev, ServerState &sest) {
	std::string msgbody = "{\"nrooms\":" +
			std::to_string(sest.campaign.size()) +
			",\"nonline\":" +
			std::to_string(sest.connst.size() - 1) +
			",\"nvisitors\":" +
			std::to_string(sest.nvisitors) +
			"}";
	std::string reply = "HTTP/1.1 200 OK\r\n"
			"Content-Type: text/html\r\n"
			"Content-length: " +
			std::to_string(msgbody.size()) + "\r\n"
			"Connection: Closed\r\n\r\n" + msgbody;
	bufferevent_write(bev, reply.c_str(), reply.size());
	// we use Connf::DISCONNECTING, only when we are sending
	// something to the client and want to close connection
	// not before the message is sent
}

static void onsocketread(struct bufferevent *bev, void *ptr) {
	ServerState &sest = *((ServerState *)ptr);

	sest.connst[(uint64_t)bev].last_active = HighClock::now();

	evbuffer *input = bufferevent_get_input(bev);
	size_t evbuflen = evbuffer_get_length(input);

	std::vector<char> inbytes(evbuflen + 1);
	char *inbuf = inbytes.data(), *cur = inbuf;
	size_t nbytes = evbuffer_copyout(input, inbuf, evbuflen);
	inbytes[evbuflen] = '\0';
	evbuffer_drain(input, nbytes);

//	std::cout << "[[[[[ Incoming Request: \n" <<
//			inbuf << "\n";

	// fetch the connection state
	ConnState &cs = sest.connst[(uint64_t)bev];

	if(cs.connf == Connf::RESET) {
		// new connection, expecting WebSocket handshake
		if(strstr(inbuf, "Upgrade: websocket") &&
				strstr(inbuf, "Connection: Upgrade")) {

			// check if valid player id is provided
			const char *cur = strstr(inbuf, "pid=");
			if(!cur) {
				std::cout <<
				"Websocket connection request missing pid=\n";
				return;
			}

			uint32_t player_id = atoi(cur + 4);
			// check if player was already added
			auto res = sest.players.find(player_id);
			if(res == sest.players.end()) {
				// player not created - error
				std::cout << "Someone is messing with pid\n";
				disconnect(bev, sest);
				return;
			}

			// check if player with this pid has already connected
			Player &player = res->second;
			if(player.connst) {
				// player with this id is already connected
				std::cout <<
				"Player with this pid already connected\n";
				disconnect(bev, sest);
				return;
			}

			if(wsconnect(bev, inbuf, nbytes)) {
				cs.connf |= Connf::WSREQUESTED;
				cs.connf |= Connf::WSACCEPTED;
				// link the player to this connection
				player.connst = &cs;
				cs.cpid = player_id;
				player.address = cs.address;
				// we need to assign the player to a room
				levelup(sest, player);
				++sest.nvisitors; // adjust stats
			} else {
				disconnect(bev, sest);
			}
		} else {
			// search for magic get var
			char *header = strstr(inbuf, "canijoinpls=");
			uint32_t requested; // requested character colors
				// as 2 bytes of uint16_t: one byte per color
			if(header && (header - inbuf) + 13 < nbytes) {
				requested = atoi(header + 12);
			} else {
				header = strstr(inbuf, "stats");
				if(header) {
					// stats requested
					send_stats(bev, sest);
					cs.connf = Connf::DISCONNECTING;
					return;
				}
				std::cout << "Wrong greeting message\n" <<
					inbuf << std::endl;
				cs.connf = Connf::FAILED;
				disconnect(bev, sest);
				return;
			}
			// generate a random player id, send it to the user
			// and create a local player
			uint32_t player_id = (dist32(mtwister) << 16) |
							(requested & 0xffff);
			auto res = sest.players.emplace(
						player_id, Player());
			if(!res.second) {
				std::cout << "Error creating player\n";
				disconnect(bev, sest);
				return;
			}
			Player &player = res.first->second;
			// we just inserted new player, fill out the form
			std::cerr << "player added" << std::endl;
			player.pid = player_id;
			player.prid = 0;
			player.xp = 0;
			player.pos = { 0, 0 };
			player.connst = nullptr;

			std::string msgbody = "Hello, " +
					std::to_string(player_id);
			std::cout << " [Http request] "  << inbuf;
			std::string reply = "HTTP/1.1 200 OK\r\n"
				"Content-Type: text/html\r\n"
				"Content-length: " +
					std::to_string(msgbody.size()) + "\r\n"
				"Connection: Closed\r\n\r\n" + msgbody;
			bufferevent_write(bev, reply.c_str(), reply.size());
			// we use Connf::DISCONNECTING, only when we are sending
			// something to the client and want to close connection
			// not before the message is sent
			cs.connf = Connf::DISCONNECTING;
		}
	} else if(cs.connf & Connf::WSACCEPTED) {
		std::cout << "read " << nbytes << " bytes: " << std::hex;
		for(int i = 0; i < nbytes; ++i) {
			std::cout << uint16_t(uint8_t(inbytes[i])) << " ";
		}

//		std::cout << " inbytes[0] = " << uint16_t(uint8_t(inbytes[0]));
		std::cout << std::endl;

		int msglen; // length of a message without meta bytes
		size_t offset; // offset to message data
		int bytesleft = inbytes.size(); // bytes left relative to cur

		while(bytesleft > 2) {
			std::cout << "decoding: " << (cur - inbuf) << std::endl;

		// unmask the message in-place
			offset = unmask(cur, bytesleft, &msglen);
			if(!offset) {
				std::cout <<"Dropping weird message\n";
				return;
			}

			if(!msglen) {
				std::cout << "Empty message offset " << offset
					<< std::endl;
//				std::cout << "Ping received. Sending pong.\n";
//				wspong(bev);
//				bytesleft -= offset;
//				cur += offset;
//				continue;
			}

			cur += offset;
			bytesleft -= offset;
			// perform the action
			char c = *cur;
			if(cs.cpid) {
				Player &player = sest.players[cs.cpid];
				switch(c) {
				case 'u': player.vel[1] = 2500; break;
				case 'l': player.vel[0] = -2500; break;
				case 'r': player.vel[0] = 2500; break;
				case 'd': player.vel[1] = -2500; break;
				case 'U': player.vel[1] = player.vel[1] > 0? 0:
						player.vel[1]; break;
				case 'L': player.vel[0] = player.vel[0] < 0? 0:
						player.vel[0]; break;
				case 'R': player.vel[0] = player.vel[0] > 0? 0:
						player.vel[0]; break;
				case 'D': player.vel[1] = player.vel[1] < 0? 0:
						player.vel[1]; break;
				case 's': player.waiting = false; break;
				case 'm': player.marking = true; break;
				}
			}
			cur += msglen;
			bytesleft -= msglen;
		}
	}

//	std::cout << inbytes.data();
}

static void onsocketwrite(bufferevent *bev, void *ptr) {
	ServerState &sest = *((ServerState *)ptr);

	ConnState &cs = sest.connst[(uint64_t)bev];
	if(cs.connf & Connf::DISCONNECTING) {
		std::cout << "Disconnecting a socket...\n";
		disconnect(bev, sest);
	}
}

static void onsocketevent(struct bufferevent *bev, short events, void *ptr) {
	ServerState &sest = *((ServerState *)ptr);

	if(events & BEV_EVENT_ERROR) {
		std::cout << "Error from bufferevent\n";
		disconnect(bev, sest);
	}
	if(events & (BEV_EVENT_EOF | BEV_EVENT_ERROR)) {
		std::cout << "Suddenly disconnected: error 0x" << std::hex
				<< events << std::endl;
		disconnect(bev, sest);
	}
}

static void onconnect(struct evconnlistener *listener, evutil_socket_t fd,
		struct sockaddr *address, int socklen, void *ptr) {
	ServerState &sest = *((ServerState *)ptr);

	// incoming connection: set up a bufferevent for it
	struct event_base *base = evconnlistener_get_base(listener);
	struct bufferevent *bev = bufferevent_socket_new(
			base, fd, BEV_OPT_CLOSE_ON_FREE);

	// add this connection to the connst map
	sest.connst[(uint64_t)bev] = ConnState{ Connf::RESET, 0, *address,
			HighClock::now() };

	sockaddr addr = *address;
	sockaddr_in *sin = (sockaddr_in *)(&addr);
//	std::cout << sin->sin_port << " << PORT" << std::endl;

	bufferevent_setcb(bev, onsocketread, onsocketwrite, onsocketevent, ptr);

	bufferevent_enable(bev, EV_READ | EV_WRITE);
}

// this function goes through all connections and sends a message to each client
static void dispatch(evutil_socket_t fd, short what, void *ptr) {
	ServerState &sest = *((ServerState *)ptr);

	sest.prev_dispatch_start = sest.dispatch_start;
	sest.prev_dispatch_end = sest.dispatch_end;

	sest.dispatch_start = HighClock::now();

	double dt = std::chrono::duration_cast<std::chrono::milliseconds>(
		sest.dispatch_start - sest.prev_dispatch_start).count()
		/ 1000.0;

	std::map<uint64_t, ConnState>::iterator it;
	for(it = sest.connst.begin(); it != sest.connst.end(); ++it) {
		if(!it->first) break; // the bug is somewhere else?
		ConnState &cs = it->second;
		if(!cs.cpid) {
			// todo: add timeout checks and maybe close connection
			continue;
		}
		struct bufferevent *bev = (struct bufferevent *)(it->first);
		Player &player = sest.players[cs.cpid];
		if(player.waiting) {
			// this player is waiting for human input to continue
			// playing, so we don't send update messages meanwhile
			continue;
		}
		Room &room = sest.rooms[player.prid];


		int opx = player.pos[0] / 1000; // previous player's x cell coor
		int opy = player.pos[1] / 1000;
		int px; // player's x cell coordinate
		int py; // player's y cell coordinate

		if(!player.pos[0] && !player.pos[1]) {
			// player just entered the room, we need to place
			// him/her randomly.
			for(int k = 0; k < 10; ++k) {
				std::uniform_int_distribution<uint32_t>
				customdist(1, room.rwidth * 1000 - 1);
				player.pos[0] = player.initpos[0] =
						customdist(mtwister);
				player.pos[1] = player.initpos[1] =
					customdist(mtwister);
				// check if not too close to portal
				px = player.pos[0] / 1000;
				py = player.pos[0] / 1000;
				uint8_t cell = room.rmaze[
						px + py * room.rwidth];
				if(!(cell & 16)) break; // not inside portal
			}
		} else {
			// update player position
			float x = float(player.pos[0]) + float(player.vel[0]) *
				dt;
			float y = float(player.pos[1]) + float(player.vel[1]) *
				dt;
			// do the checks
			if(x > 0 && y > 0 && x < room.rwidth * 1000 &&
					y < room.rwidth * 1000) {
				// check if x component crosses a wall
				px = int32_t(x) / 1000;
				py = int32_t(y) / 1000;
			uint8_t cell = room.rmaze[px + py * room.rwidth];
			uint8_t ocell = room.rmaze[opx + opy * room.rwidth];

				if( ((px < opx && cell & 2) ||
					(px > opx && cell & 8) ||
				(px == opx)) && (px == opx || py == opy) ) {
					player.pos[0] = int32_t(x);
				} else {
//					player.vel[0] = 0;
				}

				if( ((py < opy && cell & 1) ||
					(py > opy && cell & 4) ||
				(py == opy)) && (px == opx || py == opy) ) {
					player.pos[1] = int32_t(y);
				} else {
//					player.vel[1] = 0;
				}
			}
		}

		// serialize surrounding cells from bottom left to right, up
		std::string roomdata;
		px = player.pos[0] / 1000;
		py = player.pos[1] / 1000;

		if(room.rmaze[px + py * room.rwidth] & 16) {
			// portal reached
			if(player.looped) {
				// notify client about entering waiting state
				std::string str = "w";
				wstext(bev, str);
				if(!levelup(sest, player)) {
					player.xp = 0;
					// player finished all the levels
					// kthxbye
					disconnect(bev, sest);
				}
				continue;
			} else {
				player.looped = true;
				player.pos = player.initpos;
				player.vel = {0, 0};
			}
		}


		for(int j = -3; j < 4; ++j) {
			for(int i = -3; i < 4; ++i) {
				if(px + i < 0 || px + i > room.rwidth - 1) {
					roomdata.push_back(0x20);
					continue;
				}
				if(py + j < 0 || py + j > room.rwidth - 1) {
					roomdata.push_back(0x20);
					continue;
				}
				roomdata.push_back(room.rmaze[
					px + i + ((py + j) * room.rwidth)]
					+ 0x20);
			}
		}

		// serialize player pos, vel, pose, scolor, pcolor, glow
		// pos is relative to the chunk of the map of course,
		// origin = left bottom corner
		roomdata += std::to_string(player.pos[0] % 1000 + 3000) + ',' +
			std::to_string(player.pos[1] % 1000 + 3000) + ',' +
			std::to_string(player.vel[0]) + ',' +
			std::to_string(player.vel[1]) + ',' +
			std::to_string((player.pid >> 8) & 0xff) + ',' +
			std::to_string((player.pid) & 0xff) + ',' +
			std::to_string(int(player.looped)) + ';';

		// list all the other players in this room
		std::map<uint32_t, Player *>::iterator pit;
		for(pit = room.players.begin(); pit != room.players.end();
									++pit) {
			Player &p = *pit->second;
			if(p.pid == player.pid ||
				abs(p.pos[0] - player.pos[0]) > 4000 ||
				abs(p.pos[1] - player.pos[1]) > 4000) {
				continue;
			}
			roomdata += std::to_string(-player.pos[0] +
				p.pos[0] + 3000 + player.pos[0] % 1000) + ','
			+ std::to_string(-player.pos[1] + p.pos[1] + 3000
				+ player.pos[1] % 1000) + ',' +
			std::to_string(p.vel[0]) + ',' +
			std::to_string(p.vel[1]) + ',' +
			std::to_string((p.pid >> 8) & 0xff) + ',' +
			std::to_string((p.pid) & 0xff) + ',' +
			std::to_string(int(p.looped) | (int(p.marking) << 1)) +
				';';
		}

		wstext(bev, roomdata);
	}

	// reset all marking states
	for(it = sest.connst.begin(); it != sest.connst.end(); ++it) {
		if(!it->first) break;
		sest.players[it->second.cpid].marking = false;
	}

	sest.dispatch_end = HighClock::now();

}

static void accept_error_cb(struct evconnlistener *listener, void *ptr) {
        struct event_base *base = evconnlistener_get_base(listener);
        int err = EVUTIL_SOCKET_ERROR();
        printf("Got an error %d (%s) on the listener. "
                "Shutting down.\n", err, evutil_socket_error_to_string(err));

        event_base_loopexit(base, NULL);
}

int main() {
	ServerState sest;
	sest.nvisitors = 0;
	sest.prev_dispatch_start = sest.prev_dispatch_end
		= sest.dispatch_start = sest.dispatch_end = HighClock::now();

	sest.campaign.push_back(mkroom(sest, 10));
	sest.campaign.push_back(mkroom(sest, 16));
	sest.campaign.push_back(mkroom(sest, 21));
	sest.campaign.push_back(mkroom(sest, 25));
	sest.campaign.push_back(mkroom(sest, 30));
	sest.campaign.push_back(mkroom(sest, 35));
	sest.campaign.push_back(mkroom(sest, 40));

	// server related configuration
	std::uint16_t const SrvPort = 50200;

#ifdef _WIN32
	WSADATA wsa_data;
	WSAStartup(0x0201, &wsa_data);
#endif

	evconnlistener *listener, *listener_v6;
        struct sockaddr_in sin;

	sest.eb = event_base_new();
	if(!sest.eb) {
		std::cout << "Could not create event_base. Exiting."
				<< std::endl;
		exit(-1);
	}

	// create pure timer event:
	event *timerev = event_new(sest.eb, -1, EV_TIMEOUT | EV_PERSIST,
			dispatch, &sest);
	// ideally, we want this many microseconds between ticks
	sest.desired_dispatch_interval_us = 100000;
	timeval dispatch_interval = { 0, sest.desired_dispatch_interval_us };
	evtimer_add(timerev, &dispatch_interval);

	// Clear the sockaddr before using it, in case there are extra
        // platform-specific fields that can mess us up.
        memset(&sin, 0, sizeof(sin));
        // This is an INET address
        sin.sin_family = AF_INET;
        // Listen on 0.0.0.0
        sin.sin_addr.s_addr = htonl(0);
        // Listen on the given port.
        sin.sin_port = htons(SrvPort);

        listener = evconnlistener_new_bind(sest.eb, onconnect, &sest,
		LEV_OPT_CLOSE_ON_FREE|LEV_OPT_REUSEABLE, -1,
		(struct sockaddr *)&sin, sizeof(sin));
	if(!listener) {
		std::cout << "Couldn't create listener. Exiting." << std::endl;
		return 1;
	}
	evconnlistener_set_error_cb(listener, accept_error_cb);

	struct sockaddr_in6 sin6;
	memset(&sin6, 0, sizeof(sin6));
	sin6.sin6_family = AF_INET6;
	sin6.sin6_port = htons(SrvPort);
	inet_pton(AF_INET6, "::", &sin6.sin6_addr);
	listener_v6 = evconnlistener_new_bind(sest.eb, onconnect, &sest,
		LEV_OPT_CLOSE_ON_FREE | LEV_OPT_REUSEABLE |
		LEV_OPT_BIND_IPV6ONLY,
		-1, (struct sockaddr*) &sin6, sizeof(sin6));

	if(listener_v6 == NULL) {
		std::cout << "Couldn't listen ipv6. Moving on..." << std::endl;
		return -1;
	}

	event_base_dispatch(sest.eb);

	// wrap up and exit
	event_base_free(sest.eb);

	return 0;
}

