/*
	AramakiOnline
	Copyright (C) 2008 superbacker

	This program is free software; you can redistribute it and/or modify it under the terms
	of the GNU General Public License as published by the Free Software Foundation;
	either version 3 of the License, or (at your option) any later version.

	This program is distributed in the hope that it will be useful, but WITHOUT ANY
	WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
	FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.

	You should have received a copy of the GNU General Public License along with this
	program. If not, see <http://www.gnu.org/licenses/>.
 */

#include "P2PNetwork.h"
#include "Utility.h"
#include "MainFrame.h"
#include <wx/sstream.h>
#include <wx/url.h>
#include "MyCharacter.h"

BEGIN_EVENT_TABLE(P2PNetwork,wxEvtHandler)
	EVT_SOCKET(P2PNetwork::ID_SOCKET_SERVER,P2PNetwork::onServerSocketEvent)
	EVT_SET(P2PNetwork::onSet)
END_EVENT_TABLE()

P2PNetwork::P2PNetwork(uint16_t listenPort,const wxString &bootstrapScriptUrl) : error(false) {
	wxIPV4address addr;
	wxString initNodeHost;
	uint16_t initNodePort;

	//Altが押されていた場合ポートチェックを回避(デバッグ用)
	if (!wxGetKeyState(WXK_ALT)) portCheck(listenPort);

	addr.Service(listenPort);

	//TODO a
	serverSocket=NULL;

	if (!getInitNode(bootstrapScriptUrl,initNodeHost,initNodePort)) return;

	//サーバーソケットを作成
	serverSocket=new wxSocketServer(addr);

	if ((!serverSocket->Ok())||serverSocket->Error()) {
		error=true;
		wxMessageBox(wxT("接続受付に失敗しました。"),MainFrame::getInstance().GetTitle(),wxICON_ERROR,&MainFrame::getInstance());
		return;
	}

	serverSocket->SetEventHandler(*this,ID_SOCKET_SERVER);
	serverSocket->SetNotify(wxSOCKET_CONNECTION_FLAG);

	if (!initNodeHost.IsEmpty()) {
		//初期ノードに接続
		wxIPV4address initNodeAddr;
		Node *initNode;
		wxSocketClient *client;

		initNodeAddr.Hostname(initNodeHost);
		initNodeAddr.Service(initNodePort);

		client=new wxSocketClient;
		if (!client->Connect(initNodeAddr,true)) {
			error=true;
			wxMessageBox(wxT("初期ノードへの接続に失敗しました。"),MainFrame::getInstance().GetTitle(),wxICON_ERROR,&MainFrame::getInstance());
			client->Destroy();
			return;
		}

		initNode=new Node(*client,nodes,nodeListLockMutex,*this);
		initNode->getNodeList(listenPort);
		initNode->sendJoin(listenPort);
	}

	registerNetwork(bootstrapScriptUrl,listenPort);

	//Connect(wxEVT_SOCKET,wxSocketEventHandler(P2PNetwork::onServerSocketEvent),NULL,this);
}

P2PNetwork::~P2PNetwork() {
	//切断.
	wxMutexLocker locker(nodeListLockMutex);

	while(nodes.size()) (*nodes.begin())->stop();
	serverSocket->Destroy();
}

void P2PNetwork::onServerSocketEvent(wxSocketEvent &event) {
	switch(event.GetSocketEvent()) {
	case wxSOCKET_CONNECTION: {
		//接続受付
		wxSocketBase *newClient;
		Node *newNode;
		wxMutexLocker locker(nodeListLockMutex);

		newClient=serverSocket->Accept(true);
		if (!newClient) break;

		newNode=new Node(*newClient,nodes,nodeListLockMutex,*this);
		newNode->Run();

		break;
	}
	default:
		break;
	}
}

void P2PNetwork::sendPosition(const Point3d &position) {
	wxMutexLocker locker(nodeListLockMutex);

	for (Nodes::const_iterator i=nodes.begin();i!=nodes.end();++i) (*i)->sendPosition(position);
}

void P2PNetwork::sendMessage(const wxString &message) {
	wxMutexLocker locker(nodeListLockMutex);

	for (Nodes::const_iterator i=nodes.begin();i!=nodes.end();++i) (*i)->sendMessage(message);
}

void P2PNetwork::sendName(const wxString &name) {
	wxMutexLocker locker(nodeListLockMutex);

	for (Nodes::const_iterator i=nodes.begin();i!=nodes.end();++i) (*i)->sendName(name);
}

void P2PNetwork::sendMap(const Map &map) {
	wxMutexLocker locker(nodeListLockMutex);

	for (Nodes::const_iterator i=nodes.begin();i!=nodes.end();++i) (*i)->sendMap(map);
}

void P2PNetwork::renderCharacter() {
	wxMutexLocker locker(nodeListLockMutex);

	for (Nodes::const_iterator i=nodes.begin();i!=nodes.end();++i) {
		if ((*i)->getMap()==MyCharacter::getInstance().getMap().getMapXmlFileName()) (*i)->renderCharacter();
	}
}

void P2PNetwork::onSet(class SetEvent &event) {
	event.set();
}

void P2PNetwork::portCheck(uint16_t port) {
	wxURL url(wxString::Format(wxT("http://backeros.if.land.to/port_check.php?port=%u"),port));

	if (url.IsOk()) {
		wxInputStream *inputStream;
		wxIPV4address addr;
		wxSocketServer *serverSocket;

		//接続受付
		addr.Service(port);
		serverSocket=new wxSocketServer(addr);

		if (!serverSocket->Ok()) {
			wxMessageBox(wxT("ポートチェックに失敗しました。"),MainFrame::getInstance().GetTitle(),wxICON_ERROR,&MainFrame::getInstance());
			error=true;
			serverSocket->Destroy();
			return;
		}

		//ポートチェッカーにアクセス
		inputStream=url.GetInputStream();

		//ポートチェッカーからの接続を待つ
		if (serverSocket->WaitForAccept(4)) {
			wxSocketBase *socket=serverSocket->Accept(true);
			if (socket) {
				socket->Destroy();
			} else {
				wxMessageBox(wxT("ポートが開放されていません"),MainFrame::getInstance().GetTitle(),wxICON_ERROR,&MainFrame::getInstance());
				error=true;
			}
		} else {
			wxMessageBox(wxT("ポートが開放されていません"),MainFrame::getInstance().GetTitle(),wxICON_ERROR,&MainFrame::getInstance());
			error=true;
		}

		delete inputStream;

		//接続受付を終了
		serverSocket->Destroy();
	} else {
		wxMessageBox(wxT("ポートチェックに失敗しました。"),MainFrame::getInstance().GetTitle(),wxICON_ERROR,&MainFrame::getInstance());
		error=true;
	}
}

bool P2PNetwork::getInitNode(const wxString &bootstrapScriptUrl,wxString &initNodeHost,uint16_t &initNodePort) {
	wxURL url(bootstrapScriptUrl+wxT("?mode=get"));
	wxInputStream *inputStream;
	wxString initNodeAddress;
	wxStringOutputStream outputStream(&initNodeAddress);
	long port;

	//初期ノードを取ってくる
	inputStream=url.GetInputStream();

	if (!url.IsOk()||!inputStream) {
		error=true;
		wxMessageBox(wxT("初期ノードの取得に失敗しました。"),MainFrame::getInstance().GetTitle(),wxICON_ERROR,&MainFrame::getInstance());
		return false;
	}

	inputStream->Read(outputStream);
	delete inputStream;

	if (initNodeAddress==wxT("error")) {
		//ブートストラップスクリプト内部でエラーが発生
		error=true;
		wxMessageBox(wxT("初期ノードの取得に失敗しました。"),MainFrame::getInstance().GetTitle(),wxICON_ERROR,&MainFrame::getInstance());
		return false;
	}

	//ホスト/ポートに分割
	wxStringTokenizer tokenizer(initNodeAddress,wxT(":"));
	initNodeHost=tokenizer.GetNextToken();
	tokenizer.GetNextToken().ToLong(&port);
	initNodePort=port;

	return true;
}

void P2PNetwork::registerNetwork(const wxString &bootstrapScriptUrl,uint16_t listenPort) {
	wxURL url(bootstrapScriptUrl+wxT("?mode=add&port=")+wxString::Format(wxT("%u"),listenPort));
	wxInputStream *inputStream;
	wxString result;
	wxStringOutputStream outputStream(&result);

	//登録
	inputStream=url.GetInputStream();

	if (!url.IsOk()||!inputStream) {
		error=true;
		wxMessageBox(wxT("ネットワークへの登録に失敗しました。"),MainFrame::getInstance().GetTitle(),wxICON_ERROR,&MainFrame::getInstance());
		return;
	}

	inputStream->Read(outputStream);
	delete inputStream;

	if (result==wxT("error")) {
		//ブートストラップスクリプト内部でエラーが発生
		error=true;
		wxMessageBox(wxT("ネットワークへの登録に失敗しました。"),MainFrame::getInstance().GetTitle(),wxICON_ERROR,&MainFrame::getInstance());
	}
}

void P2PNetwork::ready() {
	serverSocket->Notify(true);
	for (Nodes::iterator i=nodes.begin();i!=nodes.end();++i) (*i)->Run();
}
