#pragma once
#include <np.h>
#include <libnetctl.h>
#include <net.h>
#include <np_toolkit.h>
#include <queue>

#include <unordered_map>
// #include "SonyVoiceChat_Orbis.h"

#include "..\..\Common\Network\Sony\SQRNetworkManager.h"

class SQRNetworkPlayer;
class ISQRNetworkManagerListener;
class SonyVoiceChat_Orbis;
class SQRVoiceConnection;
class C4JThread;

// This is the lowest level manager for providing network functionality on Sony platforms. This manages various network activities including the players within a gaming session.
// The game shouldn't directly use this class, it is here to provide functionality required by PlatformNetworkManagerSony.

class SQRNetworkManager_Orbis : public SQRNetworkManager
{
	friend class SonyVoiceChat_Orbis;
	friend class SQRNetworkPlayer;

	static const eSQRNetworkManagerState m_INTtoEXTStateMappings[SNM_INT_STATE_COUNT];

public:
	SQRNetworkManager_Orbis(ISQRNetworkManagerListener *listener);

	// General 
	void											Tick();
	void											Initialise();
	void											Terminate();
	eSQRNetworkManagerState							GetState();
	bool											IsHost();
	bool											IsReadyToPlayOrIdle();
	bool											IsInSession();

	// Session management
	void											CreateAndJoinRoom(int hostIndex, int localPlayerMask, void *extData, int extDataSize, bool offline);
	void											UpdateExternalRoomData();
	bool											FriendRoomManagerIsBusy();
	bool											FriendRoomManagerSearch();
	bool											FriendRoomManagerSearch2();
	int												FriendRoomManagerGetCount();
	void											FriendRoomManagerGetRoomInfo(int idx, SessionSearchResult *searchResult);
	bool											JoinRoom(SessionSearchResult *searchResult, int localPlayerMask);
	bool											JoinRoom(SceNpMatching2RoomId roomId, SceNpMatching2ServerId serverId, int localPlayerMask, const SQRNetworkManager_Orbis::PresenceSyncInfo *presence);
	void											StartGame();
	void											LeaveRoom(bool bActuallyLeaveRoom);
	void											EndGame();
	bool											SessionHasSpace(int spaceRequired);
	bool											AddLocalPlayerByUserIndex(int idx);
	bool											RemoveLocalPlayerByUserIndex(int idx);
	void											SendInviteGUI();
	static void										RecvInviteGUI();
	void											TickInviteGUI();

	// Remote play
	void											UpdateRemotePlay();



// 	void											GetInviteDataAndProcess(SceNpBasicAttachmentDataId id);
//	static bool										UpdateInviteData(SQRNetworkManager_Orbis::PresenceSyncInfo *invite);
	void											GetExtDataForRoom( SceNpMatching2RoomId roomId, void *extData, void (* FriendSessionUpdatedFn)(bool success, void *pParam), void *pParam );

	// Player retrieval
	int												GetPlayerCount();
	int												GetOnlinePlayerCount();
	SQRNetworkPlayer								*GetPlayerByIndex(int idx);
	SQRNetworkPlayer								*GetPlayerBySmallId(int idx);
	SQRNetworkPlayer								*GetPlayerByXuid(PlayerUID xuid);
	SQRNetworkPlayer								*GetLocalPlayerByUserIndex(int idx);
	SQRNetworkPlayer								*GetHostPlayer();

	void											removePlayerFromVoiceChat(SQRNetworkPlayer* pPlayer);
	// Communication parameter storage
	static const SceNpCommunicationId*				GetSceNpCommsId();
	static const SceNpCommunicationSignature*		GetSceNpCommsSig();
	static const SceNpTitleId*						GetSceNpTitleId();
	static const SceNpTitleSecret*					GetSceNpTitleSecret();

	static void										GetInviteDataAndProcess(sce::Toolkit::NP::MessageAttachment* pInvite);
private:
	void											InitialiseAfterOnline();
	void											ErrorHandlingTick();
	void											UpdateOnlineStatus(int status) { m_onlineStatus = status; }
	int												GetOnlineStatus() { return m_onlineStatus; }

	ISQRNetworkManagerListener					    *m_listener;
	SQRNetworkPlayer								*GetPlayerIfReady(SQRNetworkPlayer *player);

	// Internal state
	void											SetState(eSQRNetworkManagerInternalState state);
	void											ResetToIdle();
	eSQRNetworkManagerInternalState					m_state;
	eSQRNetworkManagerState							m_stateExternal;
	bool											m_nextIdleReasonIsFull;
	bool											m_isHosting;
	SceNpMatching2RoomMemberId						m_localMemberId;
	SceNpMatching2RoomMemberId						m_hostMemberId; // if we're not the host
	int												m_localPlayerCount;
	int												m_localPlayerJoined;		// Client only, keep a count of how many local players we have confirmed as joined to the application
	SceNpMatching2RoomId							m_room;
	unsigned char									m_currentSmallId;
	int												m_soc;
	bool											m_offlineGame;
	bool											m_offlineSQR;
	int												m_resendExternalRoomDataCountdown;
	bool											m_matching2initialised;
	PresenceSyncInfo								m_inviteReceived[MAX_SIMULTANEOUS_INVITES];	
	int												m_inviteIndex;
	static PresenceSyncInfo							*m_gameBootInvite;
	static PresenceSyncInfo							m_gameBootInvite_data;
	bool											m_doBootInviteCheck;
	bool											m_isInSession;
// 	static SceNpBasicAttachmentDataId				s_lastInviteIdToRetry;
	int												m_onlineStatus;
	bool											m_bLinkDisconnected;


private:

	CRITICAL_SECTION								m_csRoomSyncData;
	RoomSyncData									m_roomSyncData;
	void											*m_joinExtData;
	int												m_joinExtDataSize;

	std::vector<SQRNetworkPlayer *>					m_vecTempPlayers;
	SQRNetworkPlayer								*m_aRoomSlotPlayers[MAX_ONLINE_PLAYER_COUNT];		// Maps from the players in m_roomSyncData, to SQRNetworkPlayers
	void											FindOrCreateNonNetworkPlayer(int slot, int playerType, SceNpMatching2RoomMemberId memberId, int localPlayerIdx, int smallId);

	void											MapRoomSlotPlayers(int roomSlotPlayerCount =-1);
	void											UpdateRoomSyncUIDsFromPlayers();
	void											UpdatePlayersFromRoomSyncUIDs();
	void											LocalDataSend(SQRNetworkPlayer *playerFrom, SQRNetworkPlayer *playerTo, const void *data, unsigned int dataSize);
	int												GetSessionIndex(SQRNetworkPlayer *player);

	bool											AddRemotePlayersAndSync( SceNpMatching2RoomMemberId memberId, int playerMask, bool *isFull = NULL );
	void											RemoveRemotePlayersAndSync( SceNpMatching2RoomMemberId memberId, int mask );
	void											RemoveNetworkPlayers( int mask );
	void											SetLocalPlayersAndSync();
	void											SyncRoomData();
	SceNpMatching2RequestId							m_setRoomDataRequestId;
	SceNpMatching2RequestId							m_setRoomIntDataRequestId;
	SceNpMatching2RequestId							m_roomExtDataRequestId;

	// Server context management
	bool											GetMatchingContext(eSQRNetworkManagerInternalState asyncState);
	bool											GetServerContext();
	bool											GetServerContext2();
	bool											GetServerContext(SceNpMatching2ServerId serverId);
	void											DeleteServerContext();
	bool											SelectRandomServer();
	void											ServerContextTick();
	int												m_totalServerCount;
	int												m_serverCount;
	SceNpMatching2ServerId							*m_aServerId;
	SceNpMatching2ServerId							m_serverId;
	bool											m_serverContextValid;
	SceNpMatching2RequestId							m_serverSearchRequestId;
	SceNpMatching2RequestId							m_serverContextRequestId;

	// Room creation management
	SceNpMatching2RequestId							m_getWorldRequestId;
	SceNpMatching2RequestId							m_createRoomRequestId;
	SceNpMatching2WorldId							m_worldId;
	void											RoomCreateTick();

	// Room joining management
	SceNpMatching2RoomId							m_roomToJoin;
	int												m_localPlayerJoinMask;
	SceNpMatching2RequestId							m_joinRoomRequestId;
	SceNpMatching2RequestId							m_kickRequestId;

	// Room leaving management
	SceNpMatching2RequestId							m_leaveRoomRequestId;

	// Adding extra network players management
	SceNpMatching2RequestId							m_setRoomMemberInternalDataRequestId;

	// Player state management
	void											NetworkPlayerConnectionComplete(SQRNetworkPlayer *player);
	void											NetworkPlayerSmallIdAllocated(SQRNetworkPlayer *player, unsigned char smallId);
	void											NetworkPlayerInitialDataReceived(SQRNetworkPlayer *player,void *data);
	void											NonNetworkPlayerComplete(SQRNetworkPlayer *player, unsigned char smallId);
	void											HandlePlayerJoined(SQRNetworkPlayer *player);
	CRITICAL_SECTION								m_csPlayerState;

	// State and thread for managing basic event type messages
	C4JThread										*m_basicEventThread;
	SceKernelEqueue									m_basicEventQueue;
	static int										BasicEventThreadProc( void *lpParameter);

	// State and storage for managing search for friends' games
	eSQRNetworkManagerFriendSearchState				m_friendSearchState;
	SceNpMatching2ContextId							m_matchingContext;
	bool											m_matchingContextValid;
	SceNpMatching2RequestId							m_friendSearchRequestId;
	unsigned int									m_friendCount;
	C4JThread										*m_getFriendCountThread;
	static int										GetFriendsThreadProc( void* lpParameter );
	void											FriendSearchTick();
	SceNpMatching2RequestId							m_roomDataExternalListRequestId;
	void											(* m_FriendSessionUpdatedFn)(bool success, void *pParam);
	void											*m_pParamFriendSessionUpdated;
	void											*m_pExtDataToUpdate;

	// Results from searching for rooms that friends are playing in - 5 matched arrays to store their NpIds, rooms, servers, whether a room was found, and whether the external data had been received for the room. Also a count of how many elements are used in this array.
	class FriendSearchResult
	{
	public:
		SceNpId											m_NpId;
		SceNpMatching2RoomId							m_RoomId;
		SceNpMatching2ServerId							m_ServerId;
		bool											m_RoomFound;
		void											*m_RoomExtDataReceived;
	};
	std::vector<FriendSearchResult>						m_aFriendSearchResults;

	// Rudp management and local players
	std::unordered_map<int,SQRNetworkPlayer	*>			m_RudpCtxToPlayerMap;	

	std::unordered_map<SceNetInAddr_t, SQRVoiceConnection*>				m_NetAddrToVoiceConnectionMap;	

	bool											CreateRudpConnections(SceNpMatching2RoomId roomId, SceNpMatching2RoomMemberId peerMemberId, int playerMask, SceNpMatching2RoomMemberId playersPeerMemberId);
	bool											CreateVoiceRudpConnections(SceNpMatching2RoomId roomId, SceNpMatching2RoomMemberId peerMemberId, int playerMask);
	bool											CreateSocket();
	SQRNetworkPlayer								*GetPlayerFromRudpCtx(int rudpCtx);
	SQRVoiceConnection*								GetVoiceConnectionFromRudpCtx(int rudpCtx);

	SQRNetworkPlayer								*GetPlayerFromRoomMemberAndLocalIdx(int roomMember, int localIdx);
	SceNpMatching2RequestId							m_roomMemberDataRequestId;

	// Callbacks (for matching)
	bool											RegisterCallbacks();
	static void										ContextCallback(SceNpMatching2ContextId  id, SceNpMatching2Event event, SceNpMatching2EventCause eventCause, int errorCode, void *arg);

	static void										DefaultRequestCallback(SceNpMatching2ContextId id, SceNpMatching2RequestId reqId, SceNpMatching2Event event, int errorCode, const void *data, void *arg);
	static void										RoomEventCallback(SceNpMatching2ContextId id, SceNpMatching2RoomId roomId, SceNpMatching2Event event, const void *data, void *arg);


	// MGH - changed this to queue up the signalling events from the callback and process them later on the server thread
	class SignallingEvent
	{
	public:
		SceNpMatching2ContextId ctxId;
		SceNpMatching2RoomId roomId;
		SceNpMatching2RoomMemberId peerMemberId;
		SceNpMatching2Event event;
		int error_code;
	};
	std::vector<SignallingEvent>	m_signallingEventList;
	CRITICAL_SECTION				m_signallingEventListCS;

	static void										SignallingCallback(SceNpMatching2ContextId ctxId, SceNpMatching2RoomId roomId, SceNpMatching2RoomMemberId peerMemberId, SceNpMatching2Event event, int error_code, void *arg);
	void											ProcessSignallingEvent(SceNpMatching2ContextId ctxId, SceNpMatching2RoomId roomId, SceNpMatching2RoomMemberId peerMemberId, SceNpMatching2Event event, int error_code);
	void											SignallingEventsTick();

	// Callback for NpBasic
	static int										BasicEventCallback(int event, int retCode, uint32_t reqId, void *arg);

	// Callback for NpManager
	static void										ManagerCallback(int event, int result, void *arg);

	// Callback for sys util
	static void										SysUtilCallback(uint64_t status, uint64_t param, void *userdata);

	// Callbacks for rudp
	static void										RudpContextCallback(int ctx_id, int event_id, int error_code, void *arg);
	static int										RudpEventCallback(int event_id, int soc, uint8_t const *data, size_t datalen, struct SceNetSockaddr  const *addr, SceNetSocklen_t addrlen, void *arg);

	// Callback for netctl
	static void										NetCtlCallback(int eventType, void *arg);

	// Methods to be called when the server context has been created
	void											ServerContextValid_CreateRoom();
	void											ServerContextValid_JoinRoom();

	// Mask utilities
	int												GetOldMask(SceNpMatching2RoomMemberId memberId);
	int												GetAddedMask(int newMask, int oldMask);
	int												GetRemovedMask(int newMask, int oldMask);

#ifndef _CONTENT_PACKAGE
	static bool										aForceError[SNM_FORCE_ERROR_COUNT];
#endif
	bool											ForceErrorPoint(eSQRForceError err);

public:
	static void										AttemptPSNSignIn(int (*SignInCompleteCallbackFn)(void *pParam, bool bContinue, int pad), void *pParam, bool callIfFailed = false, int iPad = -1);
	//static void										CallSignInCompleteCallback();
	static int										(*s_SignInCompleteCallbackFn)(void *pParam, bool bContinue, int pad);
	static bool										s_signInCompleteCallbackIfFailed;
	static bool										s_signInCompleteCallbackFAIL;

	static void										*s_SignInCompleteParam;
	static bool										s_SignInCompleteCallbackPending;
	static long long								s_errorDialogClosed;
	static long long								s_systemDialogClosed;
	static int										s_SignInCompleteCallbackPad;

	// Time to wait for system UI before we check result
	#define											SYSTEM_UI_WAIT_TIME			1000

	static int										SetRichPresence(const void *data);
	void											SetPresenceDataStartHostingGame();
	int												GetJoiningReadyPercentage();
	static void										SetPresenceFailedCallback();

	// 4J-PB - so we can stop the crash when Iggy's LoadMovie is called from the ContextCallback
	static bool										m_bCallPSNSignInCallback;

private:
	static void										UpdateRichPresenceCustomData(void *data, unsigned int dataBytes);
	static void										TickRichPresence();
	static void										SendLastPresenceInfo();

	void											OnlineCheck();
	void											tickErrorDialog();

	static sce::Toolkit::NP::PresenceDetails		s_lastPresenceInfo;

	static const int								MIN_PRESENCE_RESEND_TIME = 30 * 1000;		// Minimum presence send rate - doesn't seem possible to find out what this actually should be
	static __int64									s_lastPresenceTime;
	static __int64									s_resendPresenceTime;	

	static bool										s_presenceStatusDirty;
	static bool										s_presenceDataDirty;

	static PresenceSyncInfo							s_lastPresenceSyncInfo;
	static PresenceSyncInfo							c_presenceSyncInfoNULL;
	static bool										b_inviteRecvGUIRunning;

	// Debug
	static long long								s_roomStartTime;
	
	// Error dialog
	static bool										s_errorDialogRunning;

	// NP Notify
	void											TickNotify();
	void											NotifyRealtimePlusFeature(int iQuadrant);
	long long										m_lastNotifyTime;

	static void										RefreshChatAndContentRestrictionsReturned_HandleInvite(void *pParam);
	bool											m_bRefreshingRestrictionsForInvite;

public:
	static bool										s_bInviteDialogRunning;
};

