#include "NetPlay.h"
#include "NetWindow.h"

// called from ---GUI--- thread
NetPlayClient::~NetPlayClient()
{
	if (is_connected)
	{
		m_do_loop = false;
		m_thread->WaitForDeath();
		delete m_thread;
	}	
}

// called from ---GUI--- thread
NetPlayClient::NetPlayClient(const std::string& address, const u16 port, const std::string& name, NetPlayDiag* const npd)
{
	m_dialog = npd;
	is_connected = false;

	// why is false successful? documentation says true is
	if (0 == m_socket.Connect(port, address))
	{
		// send connect message
		sf::Packet spac;
		spac << NETPLAY_VERSION;
		spac << NETPLAY_DOLPHIN_VER;
		spac << name;
		m_socket.Send(spac);

		sf::Packet rpac;
		// TODO: make this not hang
		m_socket.Receive(rpac);
		MessageId error;
		rpac >> error;

		// got error message
		if (error)
		{
			switch (error)
			{
			case CON_ERR_SERVER_FULL :
				PanicAlert("The server is full!");
				break;
			case CON_ERR_VERSION_MISMATCH :
				PanicAlert("The server and client's NetPlay versions are incompatible!");
				break;
			case CON_ERR_GAME_RUNNING :
				PanicAlert("The server responded: the game is currently running!");
				break;
			default :
				PanicAlert("The server sent an unknown error message!");
				break;
			}
			m_socket.Close();
		}
		else
		{
			rpac >> m_pid;

			Player player;
			player.name = name;
			player.pid = m_pid;
			player.revision = NETPLAY_DOLPHIN_VER;

			// add self to player list
			m_players[m_pid] = player;
			m_local_player = &m_players[m_pid];

			UpdateGUI();

			//PanicAlert("Connection successful: assigned player id: %d", m_pid);
			is_connected = true;

			m_selector.Add(m_socket);
			m_thread = new Common::Thread(NetPlayThreadFunc, this);
		}
	}
	else
		PanicAlert("Failed to Connect!");

}

// called from ---NETPLAY--- thread
unsigned int NetPlayClient::OnData(sf::Packet& packet)
{
	MessageId mid;
	packet >> mid;

	switch (mid)
	{
	case NP_MSG_PLAYER_JOIN :
		{
			Player player;
			packet >> player.pid;
			packet >> player.name;
			packet >> player.revision;

			m_crit.players.Enter();	// lock players
			m_players[player.pid] = player;
			m_crit.players.Leave();

			UpdateGUI();
		}
		break;

	case NP_MSG_PLAYER_LEAVE :
		{
			PlayerId pid;
			packet >> pid;

			m_crit.players.Enter();	// lock players
			m_players.erase(m_players.find(pid));
			m_crit.players.Leave();

			UpdateGUI();
		}
		break;

	case NP_MSG_CHAT_MESSAGE :
		{
			PlayerId pid;
			packet >> pid;
			std::string msg;
			packet >> msg;

			// don't need lock to read in this thread
			const Player& player = m_players[pid];

			// add to gui
			std::ostringstream ss;
			ss << player.name << '[' << (char)(pid+'0') << "]: " << msg;

			AppendChatGUI(ss.str());
		}
		break;

	case NP_MSG_PAD_MAPPING :
		{
			PlayerId pid;
			packet >> pid;

			m_crit.players.Enter();	// lock players
			Player& player = m_players[pid];

			for (unsigned int i=0; i<4; ++i)
				packet >> player.pad_map[i];
			m_crit.players.Leave();

			UpdateGUI();
		}
		break;

	case NP_MSG_PAD_DATA :
		{
			PadMapping map = 0;
			NetPad np;
			packet >> map >> np.nHi >> np.nLo;

			// trusting server for good map value (>=0 && <4)
			// add to pad buffer
			m_crit.buffer.Enter();	// lock buffer
			m_pad_buffer[(unsigned)map].push(np);
			m_crit.buffer.Leave();
		}
		break;

	case NP_MSG_PAD_BUFFER :
		{
			u32 size = 0;
			packet >> size;

			m_crit.buffer.Enter();	// lock buffer
			m_target_buffer_size = size;
			m_crit.buffer.Leave();
		}
		break;

	case NP_MSG_CHANGE_GAME :
		{
			// lock here?
			m_crit.game.Enter();	// lock game state
			packet >> m_selected_game;
			m_crit.game.Leave();

			// update gui
			wxCommandEvent evt(wxEVT_THREAD, NP_GUI_EVT_CHANGE_GAME);
			// TODO: using a wxString in AddPendingEvent from another thread is unsafe i guess?
			evt.SetString(wxString(m_selected_game.c_str(), *wxConvCurrent));
			m_dialog->GetEventHandler()->AddPendingEvent(evt);
		}
		break;

	case NP_MSG_START_GAME :
		{
			packet >> m_on_game;

			wxCommandEvent evt(wxEVT_THREAD, NP_GUI_EVT_START_GAME);
			m_dialog->GetEventHandler()->AddPendingEvent(evt);
		}
		break;

	case NP_MSG_STOP_GAME :
		{
			wxCommandEvent evt(wxEVT_THREAD, NP_GUI_EVT_STOP_GAME);
			m_dialog->GetEventHandler()->AddPendingEvent(evt);
		}
		break;

	case NP_MSG_DISABLE_GAME :
		{
			PanicAlert("Other client disconnected while game is running!! NetPlay is disabled. You must manually stop the game.");
			CritLocker game_lock(m_crit.game);	// lock game state
			m_is_running = false;
			NetPlay_Disable();
		}
		break;

	case NP_MSG_PING :
		{
			u32 ping_key = 0;
			packet >> ping_key;

			sf::Packet spac;
			spac << (MessageId)NP_MSG_PONG;
			spac << ping_key;

			CritLocker send_lock(m_crit.send);
			m_socket.Send(spac);
		}
		break;

	default :
		PanicAlert("Unknown message received with id : %d", mid);
		break;

	}

	return 0;
}

// called from ---NETPLAY--- thread
void NetPlayClient::Entry()
{
	while (m_do_loop)
	{
		if (m_selector.Wait(0.01f))
		{
			sf::Packet rpac;
			switch (m_socket.Receive(rpac))
			{
			case sf::Socket::Done :
				OnData(rpac);
				break;

			//case sf::Socket::Disconnected :
			default :
				m_is_running = false;
				NetPlay_Disable();
				AppendChatGUI("< LOST CONNECION TO SERVER >");
				PanicAlert("Lost connection to server!");
				m_do_loop = false;
				break;
			}
		}
	}

	m_socket.Close();

	return;
}

// called from ---GUI--- thread
void NetPlayClient::GetPlayerList(std::string &list)
{
	CritLocker player_lock(m_crit.players);		// lock players

	std::ostringstream ss;

	std::map<PlayerId, Player>::const_iterator
		i = m_players.begin(),
		e = m_players.end();
	for ( ; i!=e; ++i)
		ss << i->second.ToString() << '\n';

	list = ss.str();
}


// called from ---GUI--- thread
void NetPlayClient::SendChatMessage(const std::string& msg)
{
	sf::Packet spac;
	spac << (MessageId)NP_MSG_CHAT_MESSAGE;
	spac << msg;

	m_crit.send.Enter();	// lock send
	m_socket.Send(spac);
	m_crit.send.Leave();
}

// called from ---CPU--- thread
void NetPlayClient::SendPadState(const PadMapping local_nb, const NetPad& np)
{
	// send to server
	sf::Packet spac;
	spac << (MessageId)NP_MSG_PAD_DATA;
	spac << local_nb;	// local pad num
	spac << np.nHi << np.nLo;

	m_crit.send.Enter();	// lock send
	m_socket.Send(spac);
	m_crit.send.Leave();
}

// called from ---GUI--- thread
bool NetPlayClient::StartGame(const std::string &path)
{
	m_crit.buffer.Enter();	// lock buffer

	if (false == NetPlay::StartGame(path))
		return false;

	// TODO: i dont like this here
	ClearBuffers();
	m_crit.buffer.Leave();

	// tell server i started the game
	sf::Packet spac;
	spac << (MessageId)NP_MSG_START_GAME;
	spac << m_on_game;

	m_crit.send.Enter();	// lock send
	m_socket.Send(spac);
	m_crit.send.Leave();

	return true;
}

// called from ---GUI--- thread
bool NetPlayClient::ChangeGame(const std::string &game)
{
	// warning removal
	game.size();

	return true;
}
