#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <unistd.h>
#include "SceneGraphRoot.h"
#include "lindaapi.h"
#include "network.h"
#include "network_game.pb.h"

#define GET_SERIAL_ID 65535

int NetworkGame::last_player_id = 0;

Viewer *NetworkGame::sgroot;
linda_t NetworkGame::linda_addr = { "localhost", 10000 };
int NetworkGame::linda;
int NetworkGame::serial_id;
int NetworkGame::width;
int NetworkGame::start_x;
char *NetworkGame::xml_file_name;

const char *usr_help_str = "Usage: ./network -linda LINDA_SERVER_NAME\n";
void TMend(TaskManager *manager);

extern void task_initialize();
extern int init(TaskManager *manager, int argc, char *argv[]);
extern Application *
application() {
    return new NetworkGame();
}

/*
static void
null_move(SceneGraphPtr node, void *sgroot_, int screen_w, int screen_h)
{
}
*/

static void
null_collision(SceneGraphPtr node, void *sgroot_, int screen_w, int screen_h, SceneGraphPtr tree)
{
}

void
NetworkGame::update_last_player_id() {
	last_player_id++;
	if (last_player_id == serial_id)
		last_player_id++;
}

void
NetworkGame::set_position(SceneGraphPtr node, unsigned char *reply) {
	network_game::Position *pos = new network_game::Position();
	pos->ParseFromArray(reply + LINDA_HEADER_SIZE, psx_get_datalength(reply));
	node->xyz[0] = pos->x();
	node->xyz[1] = pos->y();
	node->angle[0] = pos->angle_x();
	node->angle[1] = pos->angle_y();
	delete pos;
}

static void
update_position_move(SceneGraphPtr node, void *sgroot_, int screen_w, int screen_h)
{
	// LindaServerから座標データを取得してオブジェクトに反映させる。
	if (!node->resend_flag || node->seq_rd != node->seq) {
		unsigned char *reply_rd = psx_reply(node->seq_rd);
		if (reply_rd != NULL) {
			NetworkGame::set_position(node, reply_rd);
			psx_free(reply_rd);
			return;
		}
	}
	unsigned char *reply = psx_reply(node->seq);
	if (reply != NULL) {
//		NetworkGame::set_position(node, reply);
		psx_free(reply);
		node->seq = psx_wait_rd(NetworkGame::linda, node->id * 10 + 1);
		node->resend_flag = true;
	} else if (node->resend_flag) {
		node->seq_rd = psx_rd(NetworkGame::linda, node->id * 10 + 1);
		node->resend_flag = false;
	}
}

SceneGraphPtr
create_sg(Viewer *viewer, SceneGraphPtr par, unsigned char *data, int len, int serial_id)
{
    SceneGraphPtr child = viewer->sgroot->createSceneGraph();
    viewer->sgroot->createFromXMLmemory(viewer->sgroot->tmanager, child, (char *)data, len);
    child->set_move_collision(update_position_move, null_collision);
    child->id = serial_id;
	child->seq = psx_wait_rd(NetworkGame::linda, serial_id * 10 + 1);
	child->seq_rd = psx_rd(NetworkGame::linda, serial_id * 10 + 1);
	child->resend_flag = false;
    par->addChild(child);
    return child;
}

static void
check_new_player_move(SceneGraphPtr node, void *sgroot_, int screen_w, int screen_h)
{
	unsigned char *reply_rd = psx_reply(node->seq_rd);
	if (reply_rd != NULL) {
		unsigned char *xml_data = reply_rd + LINDA_HEADER_SIZE;
		int xml_len = psx_get_datalength(reply_rd);
		create_sg(NetworkGame::sgroot, node, xml_data, xml_len, NetworkGame::last_player_id);
		psx_free(reply_rd);
		NetworkGame::update_last_player_id();
		int tuple_id = NetworkGame::last_player_id * 10;
		node->seq_rd = psx_rd(NetworkGame::linda, tuple_id);
	}
	// printf("rd id: %d\n", NetworkGame::last_player_id);
}

void *
file_map(const char *filename, int *size) {
    int fd;
    void *addr;
    struct stat sb;

    if ((fd = open(filename, O_RDONLY)) == -1) {
		fprintf(stderr, "Can't open %s\n", filename);
		perror(NULL);
    }
    if (fstat(fd, &sb) == -1) {
		fprintf(stderr, "Can't fstat %s\n", filename);
		perror(NULL);
    }
    *size = sb.st_size;
    addr = mmap(NULL, *size, PROT_READ, MAP_PRIVATE, fd, 0);
    if (addr == MAP_FAILED) {
		perror("mmap error\n");
		exit(EXIT_FAILURE);
    }
    close(fd);

    return addr;
}

void callback_free(unsigned char *tuple, void *arg) {
	psx_free(tuple);
}

void
NetworkGame::send_position(SceneGraphPtr node) {
	int pos_id = serial_id * 10 + 1;
	psx_callback_in(linda, pos_id, callback_free, NULL);
	network_game::Position *pos = new network_game::Position();
	pos->set_x(node->xyz[0]);
	pos->set_y(node->xyz[1]);
	pos->set_angle_x(node->angle[0]);
	pos->set_angle_y(node->angle[1]);
	int size = pos->ByteSize();
	unsigned char *msg = (unsigned char *) sgroot->manager->allocate(sizeof(char) * size);
	pos->SerializeToArray(msg, size); // 更新したデータを再度シリアライズ
	delete pos;
	psx_out(linda, pos_id, msg, size);
}

void
my_move(SceneGraphPtr node, void *sgroot_, int w, int h)
{
    SceneGraphRoot *sgroot = (SceneGraphRoot *)sgroot_;
    Pad *pad = sgroot->getController();
	int flag = 0;
    if (pad->right.isHold() || pad->left.isHold()) {
		if (pad->right.isHold()) {
			node->xyz[0] += 5.0f;
			sgroot->camera->xyz[0] = node->xyz[0];
//			node->angle[1] = 0.0f;
			flag = 1;
		} else if (pad->left.isHold()) {
			node->xyz[0] -= 5.0f;
			sgroot->camera->xyz[0] = node->xyz[0];
//			node->angle[1] = 180.0f;
			flag = 1;
		}
    }
    
    if (pad->down.isHold() || pad->up.isHold() ) {
		if (pad->down.isHold()) {
			node->xyz[1] += 5.0f;
			sgroot->camera->xyz[1] = node->xyz[1];
			flag = 1;
		} else if (pad->up.isHold()) {
			node->xyz[1] -= 5.0f;
			sgroot->camera->xyz[1] = node->xyz[1];
			flag = 1;
		}
    }

	/* ここで座標を送信 */
	if (flag || node->resend_flag) {
		NetworkGame::send_position(node);
	}
}

void
NetworkGame::create_my_sg(Viewer *viewer, SceneGraphPtr par, int screen_w, int screen_h)
{
    int size;
    void *addr = file_map(xml_file_name, &size);
    SceneGraphPtr sgp = viewer->createSceneGraph();
    viewer->createFromXMLmemory(sgp, (char *)addr, size);
    sgp->set_move_collision(my_move, null_collision);
	Camera *camera = viewer->sgroot->camera;
	camera->xyz[0] = 0.0;
	camera->xyz[1] = 0.0;
	
    par->addChild(sgp);
    sgp->c_xyz[0] = 0.0f;
    sgp->c_xyz[1] = 0.0f;
    sgp->c_xyz[2] = 0.0f;

    int xml_id = serial_id * 10;
    psx_out(linda, xml_id, (unsigned char *)addr, size);
    int pos_id = serial_id * 10 + 1;
    
    network_game::Position *pos = new network_game::Position();
    pos->set_x(0.0f);
    pos->set_y(0.0f);
    pos->set_angle_x(0.0f);
    pos->set_angle_y(0.0f);
    unsigned char *msg = (unsigned char *) viewer->manager->allocate(sizeof(unsigned char *) * size);
    pos->SerializeToArray(msg, size);
    psx_out(linda, pos_id, (unsigned char *)msg, pos->ByteSize());
    delete pos;
    sgp->seq = 0;
    sgp->resend_flag = 0;
}

MainLoopPtr
NetworkGame::init(Viewer *sgroot, int screen_w, int screen_h)
{
	this->sgroot = sgroot;
	width = screen_w;
	linda_connect(); // 接続に合わせて serial_id も取得
//	update_screen_scope();
	SceneGraphPtr parent = sgroot->createSceneGraph();
	parent->set_move_collision(check_new_player_move, null_collision);
	
	create_my_sg(sgroot, parent, screen_w, screen_h);
	update_last_player_id();
	
	int tuple_id = NetworkGame::last_player_id * 10;
	parent->seq_rd = psx_rd(linda, tuple_id);
	sgroot->setSceneData(parent);
	return sgroot;
}

void
NetworkGame::linda_connect() {
	init_linda(); // セレクタの初期化
	linda = open_linda_java(linda_addr.hostname, linda_addr.port);
	// serial_id の取得
	int seq = psx_in(linda, GET_SERIAL_ID);
	unsigned char *data = NULL;
	do {
		psx_sync_n();
		data = psx_reply(seq);
	} while (data == NULL);
//	data[LINDA_HEADER_SIZE + psx_get_datalength(data)] = '\0';
	serial_id = atoi((char *)data + LINDA_HEADER_SIZE);
	psx_free(data);
	printf("Get serial_id: %d\n", serial_id);
}

int
TMmain(TaskManager *manager, int argc, char *argv[])
{
    task_initialize();
    manager->set_TMend(TMend);

    for (int i = 0; i < argc; i++) {
        if (strcmp(argv[i],"-linda") == 0 && i + 1 <= argc) {
			NetworkGame::linda_addr.hostname = argv[i+1];
        } else if (strcmp(argv[i],"-port") == 0 && i + 1 <= argc) {
			NetworkGame::linda_addr.port = atoi(argv[i+1]);
        } else if (strcmp(argv[i],"-xml") == 0 && i + 1 <= argc) {
			NetworkGame::xml_file_name = argv[i+1];
		}
    }
    return init(manager, argc, argv);
}

void
TMend(TaskManager *manager)
{
    printf("NetworkGame end\n");
}

/* end */
