#include <stdlib.h>
#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 <arpa/inet.h>
#include <rpc/types.h>
#include <rpc/xdr.h>
#include "SceneGraphRoot.h"
#include "lindaapi.h"
#include "dynamic_create.h"

#define PORT 10000

#define SERIAL_REGIST_TUPLE_NO 1

#define RECV_DATA_SIZE sizeof(float) * 6

typedef struct client_ {
    int id;
    SceneGraphPtr sgp;
    struct client_ *next;
} client_t;

typedef struct {
    int tid;
    int sid;
    int read_id;
    SceneGraphPtr node;
    SceneGraphRoot *sgroot;
    TaskManager *manager;
    client_t *clist;
} callback_arg;


void
client_list_init(TaskManager* manager, client_t *clist)
{
    clist->id = -1;
    clist->next = clist;
}

void
client_list_update(TaskManager *manager, client_t *clist, int id, SceneGraphPtr sgp)
{
}

void
client_list_delete(TaskManager *manager, client_t *clist, int id)
{
    client_t *c, *prev;
    for (c = clist->next, prev = clist; c->next != clist; c = c->next) {
	if (c->id == id) {
	    prev->next = c->next;
	    return;
	}
	prev = clist;
    }
    if (c->id == id) {
		prev->next = c->next;
		return;
    }
}


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

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

static void
set_position(SceneGraphPtr node, unsigned char *reply) {
	char *data = (char *)(reply + LINDA_HEADER_SIZE);
	// XDRの準備
	XDR xdrs;
	xdrmem_create(&xdrs, data, RECV_DATA_SIZE, XDR_DECODE);
	for (int i = 0; i < 3; i++) {
		xdr_float(&xdrs, &node->xyz[i]);
	}
}

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

static void
earth_move(SceneGraphPtr node, void *sgroot_, int screen_w, int screen_h)
{
    // psx_sync_n();  in viewer::MainLoop
}

SceneGraphPtr
create_sg(TaskManager *manager, SceneGraphRoot *sgroot, SceneGraphPtr parent, unsigned char *data,
	  int len, int serial_id)
{
    SceneGraphPtr child = sgroot->createSceneGraph();
    parent->addChild(child);
    // 読み込んだオブジェクトは、すべて、child の child になる。
    sgroot->createFromXMLmemory(sgroot->tmanager, child, (char *)data, len);
    child->set_move_collision(moon_move, moon_collision);
    child->id = serial_id;
	child->seq = psx_wait_rd(sgroot->tid, serial_id * 10 + 1);
	child->seq_rd = psx_rd(sgroot->tid, serial_id * 10 + 1);
	child->resend_flag = false;
    return child;
}


static void
callback_get_xml(unsigned char *xml_tuple, void *arg) {
    int xml_len = psx_get_datalength(xml_tuple);
    callback_arg *carg = (callback_arg *)arg;
    unsigned char *xml_data = xml_tuple + LINDA_HEADER_SIZE;
    SceneGraphPtr sgp;
    // ここで create
    sgp = create_sg(carg->manager, carg->sgroot, carg->node, xml_data, xml_len, carg->sid);
    printf("%s size %d loaded\n", sgp->children->name, xml_len);
    client_list_update(carg->manager, carg->clist, carg->sid, sgp);
    free(arg);
    free(xml_tuple);
}

static void
callbacker(unsigned char *taple, void *arg) {
    int serial_id, xml_id;
	
    unsigned char *data;
    callback_arg *carg = (callback_arg *)arg;
	
    // 最初の4byteデータは使わない
    data = taple + LINDA_HEADER_SIZE;
    // clientのSerialIDを取得
    serial_id = ntohl(*(int *)data);
	printf("serial id = %d\n", serial_id);    // タプルを解放
    
    // xml fileを取得する もうすでにxml fileが送信済みである事を期待
    // つまり、送信者がserial_idを送る前にxml fileを送信していなくてはならない
    xml_id = serial_id * 10;
    callback_arg *copy_arg = (callback_arg *)carg->manager->allocate(sizeof(callback_arg));
    *copy_arg = *carg;
    copy_arg->sid = serial_id;
    psx_callback_in(carg->tid, xml_id, callback_get_xml, (void *)copy_arg);
    
    /* dataは'\0'で終わっている事を期待     (writerで保証する)  */
    free(taple);
   
    // arg は使い回すらしい。
    psx_callback_in(carg->tid, carg->read_id, callbacker, arg);
}

static const char *linda = "localhost";

static void
linda_init(TaskManager *manager, SceneGraphRoot *sgroot, client_t *clist, SceneGraphPtr node)
{
    init_linda();
    callback_arg *carg = (callback_arg *)manager->allocate(sizeof(callback_arg));

    carg->tid = open_linda_java(linda, PORT);
    carg->sgroot = sgroot;
    carg->sgroot->tid = carg->tid;
    carg->read_id = SERIAL_REGIST_TUPLE_NO;    
    carg->node = node;
    carg->manager = manager;
    carg->clist = clist;
    psx_callback_in(carg->tid, carg->read_id, callbacker, carg);
}

MainLoopPtr
dynamic_create::init(Viewer *sgroot, int screen_w, int screen_h)
{
    client_t *clist;
    clist = (client_t *)sgroot->manager->allocate(sizeof(client_t));
	
    client_list_init(sgroot->manager, clist);

    SceneGraphPtr parent;
    parent = sgroot->createSceneGraph();
    parent->set_move_collision(earth_move, earth_collision);
    linda_init(sgroot->manager, sgroot->sgroot, clist,  parent);
    
    // SceneGraphRoot に、使用する SceneGraph を設定する
    // このとき、ユーザーが記述した SceneGraph の root を渡す。
    sgroot->setSceneData(parent);
    return sgroot;
}

extern Application *
application() {
    return new dynamic_create();
}

const char *usr_help_str = "Usage: ./test_nogl [OPTION]\n";

extern int init(TaskManager *manager, int argc, char *argv[]);
extern void task_initialize();
static void TMend(TaskManager *manager);

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) {
            linda = argv[i+1];
        }
    }

    return init(manager, argc, argv);
}

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

/* end */
