#include "Node.h"
#include "MainFrame.h"
#include "P2PNetwork.h"

DEFINE_EVENT_TYPE(wxEVT_SET)

Node::Node(wxSocketBase &socket,Nodes &nodes,wxMutex &nodeListLockMutex,P2PNetwork &network) : wxThread(wxTHREAD_DETACHED),socket(socket),socketStream(socket),inputStream(socketStream),outputStream(socketStream),nodes(nodes),nodeListLockMutex(nodeListLockMutex),network(network),deleting(false) {
	socket.SetFlags(wxSOCKET_WAITALL|wxSOCKET_BLOCK);

	//ネットワークバイトオーダー
	inputStream.BigEndianOrdered(true);
	outputStream.BigEndianOrdered(true);

	nodes.push_back(this);

	Create();
}

Node::~Node() {
	if (!deleting) nodeListLockMutex.Lock();

	nodes.remove(this);
	socket.Destroy();

	if (!deleting) nodeListLockMutex.Unlock();
}

wxThread::ExitCode Node::Entry() {
	while(!TestDestroy()) {
		uint8_t type=inputStream.Read8();

		if (socket.Error()) break;

		switch(type) {
		case MESSAGE_TYPE_JOIN:
			receiveJoin();
			break;
		case MESSAGE_TYPE_SET_NAME:
			receiveSetName();
			break;
		case MESSAGE_TYPE_SET_POSITION:
			receiveSetPosition();
			break;
		case MESSAGE_TYPE_SET_MESSAGE:
			receiveSetMessage();
			break;
		case MESSAGE_TYPE_CHANGE_MAP:
			receiveChangeMap();
			break;
		case MESSAGE_TYPE_GET_NODE_LIST:
			receiveGetNodeList();
			break;
		}
	}

	return (ExitCode)0;
}

void Node::receiveJoin() {
	wxMutexLocker locker(nodeListLockMutex);
	uint16_t listenPort=inputStream.Read16();

	this->listenPort=listenPort;
}

void SetNameEvent::set() {
	character.setName(name);
}

void Node::receiveSetName() {
	//メインスレッド以外からwxBitmapを操作することはできないので、
	//イベントを投げてメインスレッドで処理する
	//(Characterの内部でwxBitmapを使用している)
	wxMutexLocker locker(nodeListLockMutex);
	SetNameEvent event(character,inputStream.ReadString());
	network.AddPendingEvent(event);
}

void Node::receiveSetPosition() {
	wxMutexLocker locker(nodeListLockMutex);
	Point3d position;
	uint64_t tmp;

	//ReadDouble/WriteDoubleが動かない
	tmp=inputStream.Read64();
	position.x=*(double *)&tmp;
	tmp=inputStream.Read64();
	position.y=*(double *)&tmp;
	tmp=inputStream.Read64();
	position.z=*(double *)&tmp;

	character.setPosition(position);
}

void SetMessageEvent::set() {
	character.setMessage(message);
}

void Node::receiveSetMessage() {
	//メインスレッド以外からwxBitmapを操作することはできないので、
	//イベントを投げてメインスレッドで処理する
	//(Characterの内部でwxBitmapを使用している)
	wxMutexLocker locker(nodeListLockMutex);
	SetMessageEvent event(character,inputStream.ReadString());
	network.AddPendingEvent(event);
}

void Node::receiveChangeMap() {
	wxMutexLocker locker(nodeListLockMutex);
	wxString map=inputStream.ReadString();

	this->map=map;

	//TODO 複数マップに対応
	MyCharacter &myCharacter=getWorld().getMyCharacter();
	sendName(myCharacter.getName());
	sendPosition(myCharacter.getPosition());
	sendMessage(myCharacter.getMessage());
}

void Node::receiveGetNodeList() {
	//ノードリストを返す
	//ノードリスト要求メッセージを送ってきたノードは除外
	//ノード数
	wxMutexLocker locker(nodeListLockMutex);

	outputStream.Write32(nodes.size()-1);

	for (Nodes::iterator i=nodes.begin();i!=nodes.end();++i) {
		if (*i==this) continue;

		wxIPV4address address;

		(*i)->socket.GetPeer(address);

		//IP
		outputStream.WriteString(address.Hostname());
		//ポート
		outputStream.Write16((*i)->listenPort);
	}
}

void Node::sendJoin(uint16_t listenPort) {
	outputStream.Write8(MESSAGE_TYPE_JOIN);
	outputStream.Write16(listenPort);
}

void Node::sendName(const wxString &name) {
	outputStream.Write8(MESSAGE_TYPE_SET_NAME);
	outputStream.WriteString(name);
}

void Node::sendPosition(const Point3d &position) {
	outputStream.Write8(MESSAGE_TYPE_SET_POSITION);
	//WriteDoubleが動かない
	outputStream.Write64(*(uint64_t *)&position.x);
	outputStream.Write64(*(uint64_t *)&position.y);
	outputStream.Write64(*(uint64_t *)&position.z);
}

void Node::sendMessage(const wxString &message) {
	outputStream.Write8(MESSAGE_TYPE_SET_MESSAGE);
	outputStream.WriteString(message);
}

void Node::sendMap(const Map &map) {
	outputStream.Write8(MESSAGE_TYPE_CHANGE_MAP);
	outputStream.WriteString(map.getMapXmlFileName());
}

void Node::getNodeList(uint16_t listenPort) {
	uint32_t nodeNum;

	outputStream.Write8(MESSAGE_TYPE_GET_NODE_LIST);

	//ノード数
	nodeNum=inputStream.Read32();

	for (uint32_t i=0;i<nodeNum;++i) {
		wxIPV4address address;
		wxSocketClient *client;
		Node *node;

		//IP
		address.Hostname(inputStream.ReadString());
		//ポート
		address.Service(inputStream.Read16());

		//ノードに接続
		client=new wxSocketClient;
		if (!client->Connect(address,true)) {
			wxLogWarning(wxT("警告: ノードへの接続に失敗しました"));
			client->Destroy();
			continue;
		}

		node=new Node(*client,nodes,nodeListLockMutex,network);
		node->sendJoin(listenPort);
	}
}

void Node::stop() {
	deleting=true;
	socket.Close();
	Delete();
}
