text: /*
  * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
  * its licensors.
  *
  * For complete copyright and license terms please see the LICENSE at the root of this
  * distribution (the "License"). All use of this software is governed by the License,
  * or, if provided, by the license below or the license accompanying this file. Do not
  * remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  *
  */
  #ifndef AZ_UNITY_BUILD
  
  #include <GridMate/Session/Session.h>
  #include <AzCore/std/string/conversions.h>
  #include <AzCore/std/parallel/lock.h>
  
  #include <GridMate/Carrier/Utils.h>
  
  #include <GridMate/VoiceChat/VoiceChatServiceBus.h>
  
  #ifdef AZ_PLATFORM_WINDOWS
  #   include <AzCore/PlatformIncl.h>
  #endif
  
  namespace GridMate
  {
      const EndianType kSessionEndian = EndianType::BigEndian;
  
      namespace Internal
      {
          /**
           * Called and executed from the carrier thread. Make sure everything is thread safe when you interact with this class.
           */
          class GridSessionHandshake
              : public Handshake
          {
          public:
              GM_CLASS_ALLOCATOR(GridSessionHandshake);
  
              struct NewConnection
              {
                  NewConnection()
                      : m_id(InvalidConnectionID)
                      , m_isInvited(false)
                      , m_peerModeRequested(Mode_Undefined)
                      , m_userData(kSessionEndian, 0)
                  {}
  
                  NewConnection(NewConnection&& nc) // VS2013 compiler doesn't generate default move ctors
                      : m_id(nc.m_id)
                      , m_isInvited(nc.m_isInvited)
                      , m_peerModeRequested(nc.m_peerModeRequested)
                      , m_userData(AZStd::forward<WriteBufferDynamic>(nc.m_userData))
                  {}
  
                  NewConnection& operator=(NewConnection&& nc)
                  {
                      m_id = nc.m_id;
                      m_isInvited = nc.m_isInvited;
                      m_peerModeRequested = nc.m_peerModeRequested;
                      m_userData = AZStd::move(nc.m_userData);
                      return *this;
                  }
  
                  ConnectionID        m_id;
                  bool                m_isInvited;
                  RemotePeerMode      m_peerModeRequested;
                  WriteBufferDynamic  m_userData;
              };
  
              typedef vector<NewConnection> NewConnectionsType;
              typedef vector<AZ::u8> UserDataBufferType;
              typedef unordered_set<string> AddressSetType;
  
              GridSessionHandshake(unsigned int handshakeTimeoutMS, const VersionType& version);
              virtual ~GridSessionHandshake() {}
  
              //////////////////////////////////////////////////////////////////////////
              // Handshake
  
              /// Called from the system to write initial handshake data.
              virtual void            OnInitiate(ConnectionID id, WriteBuffer& wb);
              /**
              * Called when a system receives a handshake initiation from another system.
              * You can write a reply in the WriteBuffer.
              * return true if you accept this connection and false if you reject it.
              */
              virtual HandshakeErrorCode OnReceiveRequest(ConnectionID id, ReadBuffer& rb, WriteBuffer& wb);
              /**
              * If we already have a valid connection and we receive another connection request, the system will
              * call this function to verify the state of the connection.
              */
              virtual bool            OnConfirmRequest(ConnectionID id, ReadBuffer& rb);
              /**
              * Called when we receive Ack from the other system on our initial data \ref OnInitiate.
              * return true to accept the ack or false to reject the handshake.
              */
              virtual bool            OnReceiveAck(ConnectionID id, ReadBuffer& rb)   { (void)id; (void)rb; return true; }  // we don't do any further filtering
              /**
              * Called when we receive Ack from the other system while we were connected. This callback is called
              * so we can just confirm that our connection is valid!
              */
              virtual bool            OnConfirmAck(ConnectionID id, ReadBuffer& rb)   { (void)id; (void)rb; return true; } // we don't do any further filtering
              /// Return true if you want to reject early reject a connection.
              virtual bool            OnNewConnection(const string& address);
              /// Called when we close a connection.
              virtual void            OnDisconnect(ConnectionID id);
              /// Return timeout in milliseconds of the handshake procedure.
              virtual unsigned int    GetHandshakeTimeOutMS() const                   { return m_handshakeTimeOutMS; }
              //////////////////////////////////////////////////////////////////////////
  
              void                    BanAddress(string address);
              void                    SetHost(bool isHost);
              void                    SetInvited(bool isInvited);
              void                    SetHostMigration(bool isMigrating);
              void                    SetUserData(const void* data, size_t dataSize);
              void                    SetSessionId(string sessionId);
              bool                    IsNewConnections()                              { return !m_newConnections.empty(); }
              NewConnectionsType&     AcquireNewConnections();
              void                    ReleaseNewConnections();
  
              void                    SetPeerMode(RemotePeerMode mode)                { m_peerMode = mode; }
              RemotePeerMode          GetPeerMode() const                             { return m_peerMode; }
  
          protected:
              AZStd::mutex            m_dataLock;
              NewConnectionsType      m_newConnections;
              AddressSetType          m_banList;
              UserDataBufferType      m_userData;
              string                  m_sessionId;
              RemotePeerMode          m_peerMode;
              VersionType             m_version;
  
              unsigned int            m_handshakeTimeOutMS;
              bool                    m_isHost;
              bool                    m_isInvited;
              bool                    m_isMigratingHost;
          };
      }
  }
  
  
  using namespace GridMate;
  using namespace GridMate::Internal;
  using namespace AZ;
  
  //////////////////////////////////////////////////////////////////////////
  //////////////////////////////////////////////////////////////////////////
  // GridSessionParam
  //////////////////////////////////////////////////////////////////////////
  //////////////////////////////////////////////////////////////////////////
  void GridSessionParam::SetValue(AZ::s32 value)
  {
      m_type = VT_INT32;
      AZStd::to_string(m_value, value);
  }
  void GridSessionParam::SetValue(AZ::s64 value)
  {
      m_type = VT_INT64;
      AZStd::to_string(m_value, value);
  }
  void GridSessionParam::SetValue(float value)
  {
      m_type = VT_FLOAT;
      AZStd::to_string(m_value, value);
  }
  void GridSessionParam::SetValue(double value)
  {
      m_type = VT_DOUBLE;
      AZStd::to_string(m_value, value);
  }
  void GridSessionParam::SetValue(AZ::s32* values, size_t numElements)
  {
      m_type = VT_INT32_ARRAY;
      if (numElements > 0)
      {
          AZ_Assert(values != nullptr, "Invalid values pointer!");
          string temp;
          for (size_t i = 0; i < numElements; ++i)
          {
              AZStd::to_string(temp, values[i]);
              m_value += temp;
              if (i != numElements - 1)
              {
                  m_value += ",";
              }
          }
      }
  }
  void GridSessionParam::SetValue(AZ::s64* values, size_t numElements)
  {
      m_type = VT_INT64_ARRAY;
      if (numElements > 0)
      {
          AZ_Assert(values != nullptr, "Invalid values pointer!");
          string temp;
          for (size_t i = 0; i < numElements; ++i)
          {
              AZStd::to_string(temp, values[i]);
              m_value += temp;
              if (i != numElements - 1)
              {
                  m_value += ",";
              }
          }
      }
  }
  void GridSessionParam::SetValue(float* values, size_t numElements)
  {
      m_type = VT_FLOAT_ARRAY;
      if (numElements > 0)
      {
          AZ_Assert(values != nullptr, "Invalid values pointer!");
          string temp;
          for (size_t i = 0; i < numElements; ++i)
          {
              AZStd::to_string(temp, values[i]);
              m_value += temp;
              if (i != numElements - 1)
              {
                  m_value += ",";
              }
          }
      }
  }
  void GridSessionParam::SetValue(double* values, size_t numElements)
  {
      m_type = VT_DOUBLE_ARRAY;
      if (numElements > 0)
      {
          AZ_Assert(values != nullptr, "Invalid values pointer!");
          string temp;
          for (size_t i = 0; i < numElements; ++i)
          {
              AZStd::to_string(temp, values[i]);
              m_value += temp;
              if (i != numElements - 1)
              {
                  m_value += ",";
              }
          }
      }
  }
  
  //////////////////////////////////////////////////////////////////////////
  //////////////////////////////////////////////////////////////////////////
  // GridSession
  //////////////////////////////////////////////////////////////////////////
  //////////////////////////////////////////////////////////////////////////
  
  //=========================================================================
  // GridSession
  //=========================================================================
  GridSession::GridSession(SessionService* service)
      : m_carrier(nullptr)
      , m_replicaMgr(nullptr)
      , m_handshake(nullptr)
      , m_isShutdown(true)
      , m_myMember(nullptr)
      , m_state(nullptr)
      , m_hostMigrationInProcess(false)
      , m_disconnectKickedPlayersDelay(500)
  {
      AZ_Assert(service, "Invalid service");
      m_service = service;
      m_gridMate = service->GetGridMate();
  }
  
  //=========================================================================
  // ~GridSession
  //=========================================================================
  GridSession::~GridSession()
  {
      while (!m_eventQueue.empty())
      {
          EventCommand& cmd = m_eventQueue.front();
          if (cmd.m_userDataSize)
          {
              azfree(cmd.event.userData, GridMateAllocatorMP);
          }
          m_eventQueue.pop();
      }
      AZ_Assert(m_isShutdown, "Shutdown has not been called!");
  
      // we own the local member so we manually delete it, member might not exist if initialization failed or did not happen
      delete m_myMember;
      m_myMember = nullptr;
  }
  
  //=========================================================================
  // Initialize
  //=========================================================================
  bool GridSession::Initialize(const CarrierDesc& carrierDesc)
  {
      m_carrierDesc = carrierDesc;
      m_isShutdown = false;
      m_service->AddSession(this);
      return true;
  }
  
  //=========================================================================
  // Shutdown
  //=========================================================================
  void
  GridSession::Shutdown()
  {
      if (!m_isShutdown)
      {
          if (m_carrier)
          {
              m_carrier->Disconnect(AllConnections);
          }
  
          m_service->RemoveSession(this);
  
          ReplicaMgrCallbackBus::Handler::BusDisconnect(m_gridMate);
  
          // delete replica manager in case we do a hard delete (otherwise SE_DELETE should already have done that)
          if (m_replicaMgr && m_replicaMgr->IsInitialized())
          {
              m_replicaMgr->Shutdown();
          }
  
          if (m_carrier)
          {
              m_carrier->Shutdown();
          }
  
          EBUS_DBG_EVENT(Debug::SessionDrillerBus, OnSessionDelete, this);
          EBUS_EVENT_ID(m_gridMate, SessionEventBus, OnSessionDelete, this);
  
          m_state = nullptr;
  
          delete m_replicaMgr;
          m_replicaMgr = nullptr;
  
          // remove the carrier we can't send more data.
          CarrierEventBus::Handler::BusDisconnect();
          delete m_carrier;
          m_carrier = nullptr;
  
          delete m_handshake;
          m_handshake = nullptr;
  
          m_isShutdown = true;
      }
  }
  
  //=========================================================================
  // SetHandshakeUserData
  //=========================================================================
  void GridSession::SetHandshakeUserData(const void* data, size_t size)
  {
      AZ_Assert(m_handshake, "We should have created a valid handshake first!");
      m_handshake->SetUserData(data, size);
  }
  
  //=========================================================================
  // Update
  //=========================================================================
  void
  GridSession::Update()
  {
      AZ_Assert(!m_isShutdown, "Update() called on session that was not initialized.");
  
      // check if we need to delete the session and escape
      if (m_sm.GetCurrentState() == SS_START_UP)
      {
          Shutdown();
          delete this;
          return;
      }
  
      // We always pass the tick event, no need to queue it.
      HSM::Event e;
      e.id = SE_UPDATE;
      m_sm.Dispatch(e); // send update event to the state machine
  
      // Process queued events that depend on overlapped operations.
      ProcessEvents();
  
      if (m_carrier)
      {
          m_carrier->Update();
  
          // TODO: Hook this change when we do a variable change, make sure we don't call too often.
          m_carrier->DebugEnableDisconnectDetection(m_state->m_isDisconnectDetection.Get());
      }
  
      //////////////////////////////////////////////////////////////////////////
      // Add new connections
      if (IsHost())
      {
          GridSessionHandshake::NewConnectionsType& nc = m_handshake->AcquireNewConnections();
          while (!nc.empty())
          {
              GridSessionHandshake::NewConnection newConnection = AZStd::move(nc.back());
              nc.pop_back();
  
              ConnectionID id = newConnection.m_id;
              bool isInvited = newConnection.m_isInvited;
  
              if (IsConnectionIdInMemberList(id))  // if we just receive the message again while we already have the member confirm it.
              {
                  continue;
              }
  
              // a client is trying to join. Join him if possible.
              ReadBuffer rb(kSessionEndian, newConnection.m_userData.Get(), newConnection.m_userData.Size());
              GridMember* member = CreateRemoteMember(m_carrier->ConnectionToAddress(id), rb, newConnection.m_peerModeRequested, id);
  
              if (member)
              {
                  // Set invited flag, before we call AddToMember so we can reserve the proper slot type.
                  member->m_isInvited.Set(isInvited);
                  // Set the peer mode in the member
                  member->m_peerMode.Set(newConnection.m_peerModeRequested);
                  // If we failed adding a member means that we could not find free slot, so just drop
                  // that member.
                  if (!AddMember(member))
                  {
                      m_carrier->Disconnect(id);
                      delete member;
                  }
              }
          }
          m_handshake->ReleaseNewConnections();
      }
  
      //////////////////////////////////////////////////////////////////////////
      // Try to bind state replicas
      for (auto memberStateIter = m_unboundMemberStates.begin(); memberStateIter != m_unboundMemberStates.end(); )
      {
          Internal::GridMemberStateReplica* memberState = *memberStateIter;
  
          // See if the member has arrived.
          GridMember* member = GetMemberById(memberState->m_memberId.Get());
          if (IsHost())
          {
              AZ_Assert(member, "Failed to match member to client state on the host!");
          }
  
          if (member)
          {
              AZ_Assert(member->m_clientState == nullptr /*|| member->m_clientState==this*/, "This member already have a pointer to a different replica state!");
              memberState->m_member = member;
              member->m_clientState = memberState;
              memberStateIter = m_unboundMemberStates.erase(memberStateIter);
  
              // Both member and client state are valid! send member joined message
              EBUS_DBG_EVENT(Debug::SessionDrillerBus, OnMemberJoined, this, member);
              EBUS_EVENT_ID(GetGridMate(), SessionEventBus, OnMemberJoined, this, member);
          }
          else
          {
              ++memberStateIter;
          }
      }
  
      //////////////////////////////////////////////////////////////////////////
      // Check connectivity
      if (IsHost() && GetTopology() == ST_PEER_TO_PEER)
      {
          TimeStamp now = AZStd::chrono::system_clock::now();
  
          // Check this
          if (AZStd::chrono::milliseconds(now - m_lastConnectivityUpdate).count() >= 1000)
          {
              m_lastConnectivityUpdate = now;
  
              // Loop through our members to find out how many p2p members are there.
              // Each peer will count themselves in, but since we are skipping the host,
              // the numbers even out.
              AZStd::size_t numPeerConnections = 0;
              for (AZStd::size_t i = 1 /*we the host are always member 0*/; i < m_members.size(); ++i)
              {
                  GridMember* member = m_members[i];
                  if (member->m_peerMode.Get() == Mode_Peer)
                  {
                      numPeerConnections++;
                  }
              }
  
              // in peer to peer mode every peer (member) should have the same number of connections
              for (AZStd::size_t i = 1 /*we the host are always member 0*/; i < m_members.size(); ++i)
              {
                  GridMember* member = m_members[i];
                  if (member->m_peerMode.Get() == Mode_Peer && (!member->m_clientState || member->m_clientState->m_numConnections.Get() != numPeerConnections))
                  {
                      // this member is out of sync.
                      NotFullyConnectedMember ncm(member);
                      NotConnectedArrayType::iterator iter = AZStd::find(m_membersNotFullyConnected.begin(), m_membersNotFullyConnected.end(), ncm);
                      if (iter == m_membersNotFullyConnected.end())
                      {
                          ncm.time = now;
                          m_membersNotFullyConnected.push_back(ncm);
                      }
                  }
              }
  
              for (AZStd::size_t i = 0; i < m_membersNotFullyConnected.size(); )
              {
                  GridMember* member = m_membersNotFullyConnected[i].member;
                  AZStd::chrono::milliseconds timeElapsed = now - m_membersNotFullyConnected[i].time;
                  if (member->m_clientState && member->m_clientState->m_numConnections.Get() == numPeerConnections)
                  {
                      // Member is back in sync with the host
                      m_membersNotFullyConnected.erase(m_membersNotFullyConnected.begin() + i);
                      continue;
                  }
                  else if (timeElapsed.count() > m_state->m_peerToPeerTimeout.Get())
                  {
                      // Member was out of sync for too long. Kick him out.
                      AZ_TracePrintf("GridMate", "Member %s (id=%s) is disconnected due peer to peer connectivity!\n", member->GetName().c_str(), member->GetId().ToString().c_str());
                      m_carrier->Disconnect(member->GetConnectionId());
  
                      m_membersNotFullyConnected.clear();  // clear the array (allow time to machines to go back in sync)
                      break;
                  }
                  ++i;
              }
          }
      }
  
      if (IsHost())
      {
          //////////////////////////////////////////////////////////////////////////
          TimeStamp now = AZStd::chrono::system_clock::now();
          for (auto it = m_futureKickedPlayers.begin();
                      it != m_futureKickedPlayers.end(); )
          {
              if (AZStd::chrono::milliseconds(now - it->first) >= m_disconnectKickedPlayersDelay)
              {
                  auto member = GetMemberById(it->second);
                  if (member)
                  {
                      m_carrier->Disconnect(member->GetConnectionId());
                  }
                  it = m_futureKickedPlayers.erase(it);
              }
              else
              {
                  it++;
              }
          }
          //////////////////////////////////////////////////////////////////////////
      }
  }
  
  //=========================================================================
  // GetTopology
  //=========================================================================
  SessionTopology
  GridSession::GetTopology() const
  {
      AZ_Assert(m_state, "Invalid session state replica. Session is not initialized.");
      return m_state->m_topology.Get();
  }
  
  //=========================================================================
  // GetNumUsedPublicSlots
  //=========================================================================
  AZ::u8
  GridSession::GetNumUsedPublicSlots() const
  {
      AZ_Assert(m_state, "Invalid session state replica. Session is not initialized.");
      return m_state->m_numUsedPublicSlots.Get();
  }
  //=========================================================================
  // GetNumUsedPrivateSlots
  //=========================================================================
  AZ::u8
  GridSession::GetNumUsedPrivateSlots() const
  {
      AZ_Assert(m_state, "Invalid session state replica. Session is not initialized.");
      return m_state->m_numUsedPrivateSlots.Get();
  }
  //=========================================================================
  // GetNumFreePublicSlots
  //=========================================================================
  AZ::u8
  GridSession::GetNumFreePublicSlots() const
  {
      AZ_Assert(m_state, "Invalid session state replica. Session is not initialized.");
      return m_state->m_numFreePublicSlots.Get();
  }
  //=========================================================================
  // GetNumFreePrivateSlots
  //=========================================================================
  AZ::u8
  GridSession::GetNumFreePrivateSlots() const
  {
      AZ_Assert(m_state, "Invalid session state replica. Session is not initialized.");
      return m_state->m_numFreePrivateSlots.Get();
  }
  //=========================================================================
  // GetFlags
  //=========================================================================
  AZ::u8
  GridSession::GetFlags() const
  {
      AZ_Assert(m_state, "Invalid session state replica. Session is not initialized.");
      return m_state->m_flags.Get();
  }
  //=========================================================================
  // GetPeerToPeerTimeout
  //=========================================================================
  AZ::u32
  GridSession::GetPeerToPeerTimeout() const
  {
      AZ_Assert(m_state, "Invalid session state replica. Session is not initialized.");
      return m_state->m_peerToPeerTimeout.Get();
  }
  //=========================================================================
  // GetHostMigrationTimeout
  //=========================================================================
  AZ::u32
  GridSession::GetHostMigrationTimeout() const
  {
      AZ_Assert(m_state, "Invalid session state replica. Session is not initialized.");
      return m_state->m_hostMigrationTimeout.Get();
  }
  //=========================================================================
  // GetHostMigrationTimeout
  //=========================================================================
  AZ::u32
  GridSession::GetHostMigrationVotingTime() const
  {
      AZ_Assert(m_state, "Invalid session state replica. Session is not initialized.");
      return m_state->m_hostMigrationVotingTime.Get();
  }
  //=========================================================================
  // GetNumParams
  //=========================================================================
  unsigned int
  GridSession::GetNumParams() const
  {
      AZ_Assert(m_state, "Invalid session state replica. Session is not initialized.");
      return static_cast<unsigned int>(m_state->m_params.Get().size());
  }
  //=========================================================================
  // GetParam
  //=========================================================================
  const GridSessionParam&
  GridSession::GetParam(unsigned int index) const
  {
      AZ_Assert(m_state, "Invalid session state replica. Session is not initialized.");
      return m_state->m_params.Get()[index];
  }
  
  //=========================================================================
  // AddParam
  //=========================================================================
  bool
  GridSession::SetParam(const GridSessionParam& param)
  {
      AZ_Assert(m_state, "Invalid session state replica. Session is not initialized.");
  
      if (IsHost())
      {
          if (m_state->m_params.Get().size() < SessionParams::k_maxNumParams)
          {
              m_state->m_params.Modify([=](Internal::GridSessionReplica::ParamContainer& params)
                  {
                      bool paramFound = false;
                      for (GridSessionParam& existingParam : params)
                      {
                          if (existingParam.m_id == param.m_id)
                          {
                              existingParam.m_type = param.m_type;
                              existingParam.m_value = param.m_value;
                              paramFound = true;
                              break;
                          }
                      }
                      if (!paramFound)
                      {
                          params.push_back();
                          params.back().m_id = param.m_id;
                          params.back().m_type = param.m_type;
                          params.back().m_value = param.m_value;
                      }
                      return true;
                  }
                  );
              OnSessionParamChanged(param);
              return true;
          }
      }
      return false;
  }
  
  //=========================================================================
  // RemoveParam
  //=========================================================================
  bool
  GridSession::RemoveParam(const string& paramId)
  {
      AZ_Assert(m_state, "Invalid session state replica. Session is not initialized.");
  
      bool isRemoved = false;
      if (IsHost())
      {
          isRemoved = m_state->m_params.Modify([=](Internal::GridSessionReplica::ParamContainer& params)
              {
                  for (auto iter = params.begin(); iter != params.end(); ++iter)
                  {
                      if (iter->m_id == paramId)
                      {
                          params.erase(iter);
                          return true;
                      }
                  }
                  return false;   // param not found
              }
              );
          if (isRemoved)
          {
              OnSessionParamRemoved(paramId);
          }
      }
      return isRemoved;
  }
  
  //=========================================================================
  // RemoveParam
  //=========================================================================
  bool
  GridSession::RemoveParam(unsigned int index)
  {
      AZ_Assert(m_state, "Invalid session state replica. Session is not initialized.");
  
      if (IsHost())
      {
          if (m_state->m_params.Get().size() > index)
          {
              const GridSessionReplica::ParamContainer& curParams = m_state->m_params.Get();
              const GridSessionParam& foundParam = curParams.at(index);
              string paramId = foundParam.m_id;
              m_state->m_params.Modify([=](Internal::GridSessionReplica::ParamContainer& params)
                  {
                      params.erase(&params.at(index));
                      return true;
                  }
                  );
              OnSessionParamRemoved(paramId);
              return true;
          }
      }
      return false;
  }
  
  //=========================================================================
  // GetMember
  //=========================================================================
  GridMember*
  GridSession::GetMemberById(const MemberID& id) const
  {
      for (unsigned int i = 0; i < m_members.size(); ++i)
      {
          if (m_members[i]->GetId() == id)
          {
              return m_members[i];
          }
      }
      return nullptr;
  }
  
  //=========================================================================
  // GetMember
  //=========================================================================
  GridMember*
  GridSession::GetMemberById(const MemberIDCompact& id) const
  {
      for (unsigned int i = 0; i < m_members.size(); ++i)
      {
          if (m_members[i]->GetIdCompact() == id)
          {
              return m_members[i];
          }
      }
      return nullptr;
  }
  
  //=========================================================================
  // GetHost
  //=========================================================================
  GridMember*
  GridSession::GetHost() const
  {
      for (unsigned int i = 0; i < m_members.size(); ++i)
      {
          if (m_members[i]->IsHost())
          {
              return m_members[i];
          }
      }
      return nullptr;
  }
  
  //=========================================================================
  // KickMember
  //=========================================================================
  GridSession::Result
  GridSession::KickMember(GridMember* member, AZ::u8 reason)
  {
      if (!IsHost() || member == nullptr)
      {
          return GS_ERROR;
      }
  
      AZ_TracePrintf("GridMate", "Member %s (id=%s) was kicked!\n", member->GetName().c_str(), member->GetId().ToString().c_str());
      member->KickRpc(reason);
  
      m_futureKickedPlayers.push_back(AZStd::make_pair(AZStd::chrono::system_clock::now(), member->GetIdCompact()));
  
      return GS_OK;
  }
  
  //=========================================================================
  // BanMember
  //=========================================================================
  GridSession::Result
  GridSession::BanMember(GridMember* member, AZ::u8 reason)
  {
      if (!IsHost() || member == nullptr)
      {
          return GS_ERROR;
      }
  
      m_handshake->BanAddress(member->GetId().ToAddress());
      AZ_TracePrintf("GridMate", "Member %s (id=%s) was banned!\n", member->GetName().c_str(), member->GetId().ToString().c_str());
      return KickMember(member, reason);
  }
  
  //=========================================================================
  // Leave
  //=========================================================================
  void
  GridSession::Leave(bool isMigrateHost)
  {
      (void)isMigrateHost;
      // We should support Leave at any moment/state.
      if (!m_sm.IsInState(SS_DELETE))
      {
          RequestEvent(SE_DELETE);
      }
  }
  
  //=========================================================================
  // GetTime
  //=========================================================================
  unsigned int
  GridSession::GetTime() const
  {
      if (m_carrier)
      {
          return m_carrier->GetTime();
      }
  
      return 0;
  }
  
  //=========================================================================
  // ReserveSlot
  //=========================================================================
  int
  GridSession::ReserveSlot(bool isInvited)
  {
      AZ_Assert(m_state, "Invalid session state replica. Session is not initialized.");
  
      if (!IsHost())
      {
          return 1;              // if we are client we don't manage slots. we always agree
      }
      unsigned char numFreePrivateSlots = m_state->m_numFreePrivateSlots.Get();
      if (isInvited && numFreePrivateSlots > 0)
      {
          AZ::u8 numUsedPrivateSlots = m_state->m_numUsedPrivateSlots.Get();
          m_state->m_numUsedPrivateSlots.Set(++numUsedPrivateSlots);
          m_state->m_numFreePrivateSlots.Set(--numFreePrivateSlots);
          return 2;
      }
  
      unsigned char numFreePublicSlots = m_state->m_numFreePublicSlots.Get();
      if (numFreePublicSlots > 0)
      {
          AZ::u8 numUsedPublicSlots = m_state->m_numUsedPublicSlots.Get();
          m_state->m_numUsedPublicSlots.Set(++numUsedPublicSlots);
          m_state->m_numFreePublicSlots.Set(--numFreePublicSlots);
          return 1;
      }
  
      return 0;
  }
  
  //=========================================================================
  // FreeSlot
  //=========================================================================
  void
  GridSession::FreeSlot(int slotType)
  {
      AZ_Assert(m_state, "Invalid session state replica. Session is not initialized.");
  
      if (!IsHost())
      {
          return;             // if we are client we don't manage slots.
      }
      //  AZ_Warning("GridMate",m_state,"GridSession::FreeSlot failed because of missing session state!");
      if (m_state == nullptr)
      {
          return;
      }
  
      if (slotType == 2)
      {
          AZ_Assert(m_state->m_numUsedPrivateSlots.Get() > 0, "Invalid free private slot operation!");
          AZ::u8 numUsedPrivateSlots = m_state->m_numUsedPrivateSlots.Get();
          AZ::u8 numFreePrivateSlots = m_state->m_numFreePrivateSlots.Get();
          m_state->m_numUsedPrivateSlots.Set(--numUsedPrivateSlots);
          m_state->m_numFreePrivateSlots.Set(++numFreePrivateSlots);
      }
      if (slotType == 1)
      {
          AZ_Assert(m_state->m_numUsedPublicSlots.Get() > 0, "Invalid free public slot operation!");
          AZ::u8 numUsedPublicSlots = m_state->m_numUsedPublicSlots.Get();
          AZ::u8 numFreePublicSlots = m_state->m_numFreePublicSlots.Get();
          m_state->m_numUsedPublicSlots.Set(--numUsedPublicSlots);
          m_state->m_numFreePublicSlots.Set(++numFreePublicSlots);
      }
  }
  
  //=========================================================================
  // AddMember
  //=========================================================================
  bool
  GridSession::AddMember(GridMember* member)
  {
      AZ_Assert(IsAddressInMemberList(member->GetId().ToAddress()) == false, "This member is already in the member list!");
  
      member->m_slotType = ReserveSlot(member->IsInvited());
      if (member->m_slotType == 0)
      {
          AZ_TracePrintf("GridMate", "Failed to reserve slot for %s(%s) [%d,%d]\n", member->GetId().ToString().c_str(), member->GetId().ToAddress().c_str(), m_state->m_numFreePrivateSlots.Get(), m_state->m_numFreePublicSlots.Get());
          AZ_TracePrintf("GridMate", "Current Members:\n");
          for (unsigned int i = 0; i < m_members.size(); ++i)
          {
              GridMember* currentMember = m_members[i];
              (void)currentMember;
              AZ_TracePrintf("GridMate", "  Member: %s(%s)\n", currentMember->GetId().ToString().c_str(), currentMember->GetId().ToAddress().c_str());
          }
          return false;
      }
  
      m_members.push_back(member);
  
      if (IsHost())
      {
          ReplicaPtr replica = member->GetReplica();
          if (!replica)
          {
              char debugName[256];
              azsnprintf(debugName, AZ_ARRAY_SIZE(debugName), "MemberId(%s)", member->GetId().ToString().c_str());
  
              replica = Replica::CreateReplica(debugName);
              replica->AttachReplicaChunk(member);
          }
  
          m_replicaMgr->AddMaster(replica);
          member->m_isHost.Set(member->IsLocal());
      }
  
      // Add member to voice chat if there is appropriate service
      EBUS_EVENT_ID(m_gridMate, VoiceChatServiceBus, RegisterMember, member);
  
      return true;
  }
  
  //=========================================================================
  // IsAddressInMemberList
  //=========================================================================
  bool
  GridSession::IsAddressInMemberList(const string& address)
  {
      for (AZStd::size_t i = 0; i < m_members.size(); ++i)
      {
          if (m_members[i]->GetId().ToAddress() == address)
          {
              return true;
          }
      }
  
      return false;
  }
  
  //=========================================================================
  // IsConnectionIdInMemberList
  //=========================================================================
  bool
  GridSession::IsConnectionIdInMemberList(const ConnectionID& connId)
  {
      for (AZStd::size_t i = 0; i < m_members.size(); ++i)
      {
          if (m_members[i]->GetConnectionId() == connId)
          {
              return true;
          }
      }
  
      return false;
  }
  
  //=========================================================================
  // RemoveMember
  //=========================================================================
  bool
  GridSession::RemoveMember(const MemberID& id)
  {
      AZ_Warning("GridMate", !m_sm.IsInState(SS_HOST_MIGRATE_ELECTION), "It should be impossible to remove a member while there is no active host!");
      for (unsigned int i = 0; i < m_members.size(); ++i)
      {
          GridMember* member = m_members[i];
          if (member->GetId() == id)
          {
              // Removing member from voice chat
              EBUS_EVENT_ID(m_gridMate, VoiceChatServiceBus, UnregisterMember, member);
  
              // The only ones that can get a remove member call
              // without having lost that connection are the non-hosts.
              // If we are the host, then either we already lost connection
              // or we will force disconnection if we are kicking the member.
              if (!IsHost())
              {
                  m_carrier->Disconnect(member->GetConnectionId());
              }
  
              FreeSlot(member->m_slotType);
  
              m_members.erase(m_members.begin() + i);
  
              NotConnectedArrayType::iterator iter = AZStd::find(m_membersNotFullyConnected.begin(), m_membersNotFullyConnected.end(), NotFullyConnectedMember(member));
              if (iter != m_membersNotFullyConnected.end())
              {
                  m_membersNotFullyConnected.erase(iter);
              }
  
              return true;
          }
      }
  
      return false;
  }
  
  //=========================================================================
  // OnIncomingConnection
  //=========================================================================
  void
  GridSession::OnIncomingConnection(Carrier* carrier, ConnectionID id)
  {
      (void)carrier;
      (void)id;
  }
  
  //=========================================================================
  // OnFailedToConnect
  //=========================================================================
  void
  GridSession::OnFailedToConnect(Carrier* carrier, ConnectionID id, CarrierDisconnectReason reason)
  {
      if (carrier != m_carrier)
      {
          return; // not for us
      }
      (void)reason;
      AZ_TracePrintf("GridMate", "FailedToConnect %s => (%s,%s)\n", m_myMember ? m_myMember->GetId().ToAddress().c_str() : "Local", m_carrier->ConnectionToAddress(id).c_str(), ReasonToString(reason).c_str());
  
      // Remove the member on the host or set ConnectionId to invalid for a peer.
      GridMember* removedMember = nullptr;
      for (unsigned int i = 0; i < m_members.size(); ++i)
      {
          GridMember* member = m_members[i];
          if (member->m_connectionId == id)
          {
              member->m_connectionId = InvalidConnectionID;
              removedMember = member;
              break;
          }
      }
      if (IsHost())
      {
          if (removedMember) // if we have a failed handshake, we will not have a member to remove.
          {
              removedMember->GetReplica()->Destroy();
          }
      }
      else
      {
          // if we are client make sure we did not failed to connected to the host. If so we failed to join.
          if (m_sm.IsInState(SS_JOIN) && m_carrier->ConnectionToAddress(id) == m_hostAddress)
          {
              AZ_TracePrintf("GridMate", "Failed to join session %s\n", m_sessionId.c_str());
              RequestEvent(SE_DELETE);
          }
      }
  }
  
  //=========================================================================
  // OnConnectionEstablished
  //=========================================================================
  void
  GridSession::OnConnectionEstablished(Carrier* carrier, ConnectionID id)
  {
      if (carrier != m_carrier)
      {
          return; // not for us
      }
      m_connections.insert(id);
  
      RemotePeerMode peerMode = Mode_Peer;
      if (IsHost())
      {
          AZ_Assert(GetTopology() != ST_INVALID, "Invalid sesson topology! Did session replica arrive yet?");
  
          // Check which peer mode does the client prefer
          GridSessionHandshake::NewConnectionsType& nc = m_handshake->AcquireNewConnections();
          GridSessionHandshake::NewConnectionsType::iterator it = nc.begin();
          while (it != nc.end())
          {
              if (it->m_id == id)
              {
                  break;
              }
              ++it;
          }
          AZ_Assert(it != nc.end(), "New connection is not in the handshake new connections list!");
          if (it->m_peerModeRequested != Mode_Undefined)
          {
              peerMode = it->m_peerModeRequested;
          }
  
          // If we are not peer to peer, then we can only support client mode.
          if (GetTopology() == ST_CLIENT_SERVER_DISTRIBUTED || GetTopology() == ST_CLIENT_SERVER)
          {
              peerMode = Mode_Client;
          }
  
          // write the final peer mode back into the connection record, we will use it when
          // we create the remote member.
          it->m_peerModeRequested = peerMode;
  
          m_handshake->ReleaseNewConnections();
      }
  
      AZ_Assert(m_myMember, "We should always have a valid local member!");
      AZ_Assert(m_myMember->m_clientState, "My member should always have a client state active!");
      m_myMember->m_clientState->m_numConnections.Set(static_cast<unsigned char>(m_connections.size()));
  
      AZ_TracePrintf("GridMate", "Connection %s => %s (%s) (Connections=%d)!\n", m_myMember->GetId().ToAddress().c_str(), m_carrier->ConnectionToAddress(id).c_str(), peerMode == Mode_Client ? "Client" : "Peer", m_connections.size());
  
      m_replicaMgr->AddPeer(id, peerMode);
  }
  
  //=========================================================================
  // OnDisconnect
  //=========================================================================
  void
  GridSession::OnDisconnect(Carrier* carrier, ConnectionID id, CarrierDisconnectReason reason)
  {
      if (carrier != m_carrier)
      {
          return; // not for us
      }
      (void)reason;
      AZ_TracePrintf("GridMate", "Disconnect %s => (%s,%s)\n", m_myMember ? m_myMember->GetId().ToAddress().c_str() : "Local", m_carrier->ConnectionToAddress(id).c_str(), ReasonToString(reason).c_str());
  
      // Remove the member on the host or set ConnectionId to invalid for a peer.
      GridMember* removedMember = nullptr;
      MemberIDCompact removedMemberId = 0;
      for (unsigned int i = 0; i < m_members.size(); ++i)
      {
          GridMember* member = m_members[i];
          if (member->m_connectionId == id)
          {
              member->m_connectionId = InvalidConnectionID;
              removedMember = member;
              removedMemberId = member->GetId().Compact();
              break;
          }
      }
      if (IsHost())
      {
          if (removedMember)
          {
              removedMember->GetReplica()->Destroy();
          }
      }
      else
      {
          // if we are client make sure we did not failed to connected to the host. If so we failed to join.
          if (m_sm.IsInState(SS_JOIN) && m_carrier->ConnectionToAddress(id) == m_hostAddress)
          {
              AZ_TracePrintf("GridMate", "Failed to join session %s\n", m_sessionId.c_str());
              RequestEvent(SE_DELETE);
          }
      }
  
      m_replicaMgr->RemovePeer(id);
      m_connections.erase(id);
  
      if (m_myMember)
      {
          AZ_Assert(m_myMember->m_clientState, "My member should always have a client state active!");
          m_myMember->m_clientState->m_numConnections.Set(static_cast<unsigned char>(m_connections.size()));
      }
  
      if (removedMemberId != 0)
      {
          RequestEventParam(SE_CONNECTION_LOST, removedMemberId);
      }
  }
  
  //=========================================================================
  // OnDriverError
  //=========================================================================
  void
  GridSession::OnDriverError(Carrier* carrier, ConnectionID id, const DriverError& error)
  {
      if (carrier != m_carrier)
      {
          return; // not for us
      }
      string errorMsg = string::format("Carrier driver error ConnectionID: %d ErrorCode: 0x%08x", id, error.m_errorCode);
      EBUS_DBG_EVENT(Debug::SessionDrillerBus, OnSessionError, this, errorMsg);
      EBUS_EVENT_ID(m_gridMate, SessionEventBus, OnSessionError, this, errorMsg);
  
      if (id != InvalidConnectionID)
      {
          // we had connection related error, carrier will close it.
          m_replicaMgr->RemovePeer(id);
          m_connections.erase(id);
      }
      else
      {
          // we had a global error, so this was it. close the session.
          Leave(false);
      }
  }
  
  //=========================================================================
  // OnSecurityError
  //=========================================================================
  void
  GridSession::OnSecurityError(Carrier* carrier, ConnectionID id, const SecurityError& error)
  {
      if (carrier != m_carrier)
      {
          return; // not for us
      }
      string errorMsg = string::format("Carrier security error ConnectionID: %d ErrorCode: 0x%08x", id, error.m_errorCode);
      EBUS_DBG_EVENT(Debug::SessionDrillerBus, OnSessionError, this, errorMsg);
  }
  
  //=========================================================================
  // OnNewHost
  //=========================================================================
  void
  GridSession::OnNewHost(bool isHost, ReplicaManager* manager)
  {
      (void)isHost;
      /// Called from the replica manager when HostMigration has been completed.
      if (manager == m_replicaMgr)
      {
          RequestEvent(SE_HM_REPLICAS_MIGRATED);
      }
  }
  
  //=========================================================================
  // ElectNewHost
  //=========================================================================
  void
  GridSession::ElectNewHost()
  {
      AZ_Assert(m_myMember, "We should always have local member!");
      //  AZ_Assert(m_sm.IsInState(SS_HOST_MIGRATE_ELECTION),"We should be in host migrate election state to call this function!");
      GridMember* newHost = nullptr; // Allow the user to choose
  
      EBUS_DBG_EVENT(Debug::SessionDrillerBus, OnMigrationElectHost, this, newHost);
      EBUS_EVENT_ID(m_gridMate, SessionEventBus, OnMigrationElectHost, this, newHost);
  
      if (newHost == nullptr || newHost->GetConnectionId() == InvalidConnectionID || newHost->IsHost())
      {
          // Elect a new host automatically...
          for (MemberArrayType::iterator iMember = m_members.begin(); iMember != m_members.end(); ++iMember)
          {
              GridMember* member = (*iMember);
              if (!member->IsHost() && (member->GetConnectionId() != InvalidConnectionID || member == m_myMember)) // find the oldest non host member we are connected to.
              {
                  newHost = member;
                  break;
              }
          }
      }
  
      if (newHost == nullptr) // if we can't select a new host we should quit the host migration
      {
          AZ_TracePrintf("GridMate", "Host migration: can't select a new host!");
          Leave(false);
          return;
      }
  
      CastNewHostVote(newHost);
  }
  
  //=========================================================================
  // CastNewHostVote
  //=========================================================================
  void
  GridSession::CastNewHostVote(GridMember* newHost)
  {
      AZ_Assert(m_myMember && m_myMember->m_clientState, "We should always have local member and state!");
      if (newHost)
      {
          m_myMember->m_clientState->m_newHostVote.Set(newHost->GetId().Compact());
          AZ_TracePrintf("GridMate", "Host migration: %s(%s) voted %s(%s) for new host!\n", m_myMember->GetName().c_str(), m_myMember->GetId().ToAddress().c_str(), newHost->GetName().c_str(), newHost->GetId().ToAddress().c_str());
      }
  }
  
  //=========================================================================
  // EventToQueue
  //=========================================================================
  void
  GridSession::EventToQueue(const HSM::Event& event, unsigned int userDataSize, bool isProcessRequired, EventCommand::DataCloner cloner, EventCommand::DataDeleter deleter)
  {
      EventCommand cmd;
      cmd.event = event;
      cmd.m_userDataSize = userDataSize;
      cmd.m_isProcessRequired = isProcessRequired;
      cmd.m_dataDeleter = deleter;
  
      // copy the event data if needed
      if (userDataSize)
      {
          AZ_Assert(event.userData != nullptr, "If you provide user data size, you must have a valid data pointer!");
          if (cloner)
          {
              cmd.event.userData = cloner(event.userData, userDataSize);
          }
          else
          {
              cmd.event.userData = azmalloc(userDataSize, 1, GridMateAllocatorMP);
              memcpy(cmd.event.userData, event.userData, userDataSize);
          }
      }
  
      m_eventQueue.push(cmd);
  }
  
  //=========================================================================
  // ProcessEventOnQueue
  //=========================================================================
  void
  GridSession::ProcessEventOnQueue()
  {
      AZ_Assert(!m_eventQueue.empty(), "You need to have an event to process it!");
      EventCommand cmd = m_eventQueue.front();
      m_eventQueue.pop();
      bool isProcessed = m_sm.Dispatch(cmd.event);
      (void)isProcessed;
      AZ_Assert(!cmd.m_isProcessRequired || isProcessed, "We require that event %d is processed by the state machine (in state %d), but it failed. Check the state machine logic!", cmd.event.id, m_sm.GetCurrentState());
  
      if (cmd.m_userDataSize)
      {
          if (cmd.m_dataDeleter)
          {
              cmd.m_dataDeleter(cmd.event.userData, cmd.m_userDataSize);
          }
          else
          {
              azfree(cmd.event.userData, GridMateAllocatorMP);
          }
      }
  }
  
  //=========================================================================
  // RequestEvent
  //=========================================================================
  void
  GridSession::RequestEvent(int id, const void* userData, unsigned int userDataSize, bool isProcessRequired, EventCommand::DataCloner cloner, EventCommand::DataDeleter deleter)
  {
      HSM::Event event;
      event.id = id;
      event.userData = const_cast<void*>(userData);
      if (!m_eventQueue.empty() || m_sm.IsDispatching())
      {
          EventToQueue(event, userDataSize, isProcessRequired, cloner, deleter);
      }
      else
      {
          // execute on spot
          bool isProcessed = m_sm.Dispatch(event);
          (void)isProcessed;
          AZ_Assert(!isProcessRequired || isProcessed, "We require that event %d is processed by the state machine (in state %d), but it failed. Check the state machine logic!", id, m_sm.GetCurrentState());
      }
  }
  
  //=========================================================================
  // ProcessEvents
  //=========================================================================
  void
  GridSession::ProcessEvents()
  {
      while (!m_eventQueue.empty())
      {
          ProcessEventOnQueue();
      }
  }
  
  //=========================================================================
  // DebugEnableDisconnectDetection
  //=========================================================================
  void
  GridSession::DebugEnableDisconnectDetection(bool isEnable)
  {
      AZ_Assert(m_state, "Invalid session state replica. Session is not initialized.");
  
      if (IsHost())
      {
          m_state->m_isDisconnectDetection.Set(isEnable);
      }
  }
  
  //=========================================================================
  // DebugIsEnableDisconnectDetection
  //=========================================================================
  bool
  GridSession::DebugIsEnableDisconnectDetection() const
  {
      AZ_Assert(m_state, "Invalid session state replica. Session is not initialized.");
      return m_state->m_isDisconnectDetection.Get();
  }
  
  //////////////////////////////////////////////////////////////////////////
  // State machine
  //////////////////////////////////////////////////////////////////////////
  
  //////////////////////////////////////////////////////////////////////////
  // Session state machine
  //=========================================================================
  // SetUpStateMachine
  //=========================================================================
  void
  GridSession::SetUpStateMachine()
  {
      // Set up the state machine
      m_sm.SetStateHandler(AZ_HSM_STATE_NAME(SS_TOP), HSM::StateHandler(this, &GridSession::OnStateNoSession), HSM::InvalidStateId, SS_NO_SESSION);
      m_sm.SetStateHandler(AZ_HSM_STATE_NAME(SS_NO_SESSION), HSM::StateHandler(this, &GridSession::OnStateNoSession), SS_TOP, SS_START_UP);
      m_sm.SetStateHandler(AZ_HSM_STATE_NAME(SS_START_UP), HSM::StateHandler(this, &GridSession::OnStateStartup), SS_NO_SESSION);
      m_sm.SetStateHandler(AZ_HSM_STATE_NAME(SS_CREATE), HSM::StateHandler(this, &GridSession::OnStateCreate), SS_NO_SESSION);
      m_sm.SetStateHandler(AZ_HSM_STATE_NAME(SS_IN_SESSION), HSM::StateHandler(this, &GridSession::OnStateInSession), SS_TOP, SS_IDLE);
      m_sm.SetStateHandler(AZ_HSM_STATE_NAME(SS_JOIN), HSM::StateHandler(this, &GridSession::OnStateJoin), SS_IN_SESSION);
      m_sm.SetStateHandler(AZ_HSM_STATE_NAME(SS_IDLE), HSM::StateHandler(this, &GridSession::OnStateIdle), SS_IN_SESSION);
      m_sm.SetStateHandler(AZ_HSM_STATE_NAME(SS_START_GAME), HSM::StateHandler(this, &GridSession::OnStateStart), SS_IN_SESSION);
      m_sm.SetStateHandler(AZ_HSM_STATE_NAME(SS_IN_GAME), HSM::StateHandler(this, &GridSession::OnStateInGame), SS_IN_SESSION);
      m_sm.SetStateHandler(AZ_HSM_STATE_NAME(SS_END_GAME), HSM::StateHandler(this, &GridSession::OnStateEnd), SS_IN_SESSION);
      m_sm.SetStateHandler(AZ_HSM_STATE_NAME(SS_DELETE), HSM::StateHandler(this, &GridSession::OnStateDelete), SS_IN_SESSION);
      m_sm.SetStateHandler(AZ_HSM_STATE_NAME(SS_HOST_MIGRATE_ELECTION), HSM::StateHandler(this, &GridSession::OnStateHostMigrateElection), SS_IN_SESSION);
      m_sm.SetStateHandler(AZ_HSM_STATE_NAME(SS_HOST_MIGRATE_SESSION), HSM::StateHandler(this, &GridSession::OnStateHostMigrateSession), SS_IN_SESSION);
  
      m_sm.Start();
  }
  
  bool
  GridSession::OnStateNoSession(HSM& sm, const HSM::Event& e)
  {
      switch (e.id)
      {
      case SE_DELETE:
      {
          if (!sm.IsInState(SS_START_UP) && !sm.IsInState(SS_DELETE))
          {
              sm.Transition(SS_DELETE);
          }
      } return true;
      }
      return false;
  }
  
  bool
  GridSession::OnStateStartup(HSM& sm, const HSM::Event& e)
  {
      switch (e.id)
      {
      case SE_JOIN:
      case SE_HOST:
      {
          sm.Transition(SS_CREATE);
      } return true;
      }
      return false;
  }
  bool
  GridSession::OnStateJoin(HSM& sm, const HSM::Event& e)
  {
      switch (e.id)
      {
      case SE_JOINED:
      {
          EBUS_DBG_EVENT(Debug::SessionDrillerBus, OnSessionJoined, this);
          EBUS_EVENT_ID(m_gridMate, SessionEventBus, OnSessionJoined, this);
          sm.Transition(SS_IDLE);
      } return true;
      }
      return false;
  }
  
  bool
  GridSession::OnStateInSession(HSM& sm, const HSM::Event& e)
  {
      switch (e.id)
      {
      case SE_CONNECTION_LOST:
      {
          // Allow host migration only if we are in a suitable state (we have operational session)
          if (sm.IsInState(SS_IDLE) || sm.IsInState(SS_IN_GAME) || sm.IsInState(SS_START_GAME) || sm.IsInState(SS_END_GAME))
          {
              MemberIDCompact id = static_cast<MemberIDCompact>(reinterpret_cast<size_t>(e.userData));
              GridMember* member = GetMemberById(id);
              GridMember* host = GetHost();
              // Check if we lost connection to the host
              if (host == nullptr || host == member)
              {
                  if (m_state && (m_state->m_flags.Get() & (SessionParams::SF_HOST_MIGRATION | SessionParams::SF_HOST_MIGRATION_NO_EMPTY_SESSIONS)))
                  {
                      sm.Transition(SS_HOST_MIGRATE_ELECTION);
                  }
                  else
                  {
                      Leave(false);            // Leave because the host left
                  }
              }
          }
      } return true;
      case SE_HM_REPLICAS_MIGRATED:
      {
          AZ_Assert(false, "We should not receive replica migrated callback if we are NOT in host migration state!!!");
      } return false;
      }
      return false;
  }
  
  bool
  GridSession::OnStateCreate(HSM& sm, const HSM::Event& e)
  {
      switch (e.id)
      {
      case HSM::EnterEventId:
      {
          AZ_Assert(m_carrierDesc.m_handshake == nullptr, "You cannot override the default carrier handshake provider if you are using the default multiplayer service!");
          m_handshake = aznew GridSessionHandshake(m_carrierDesc.m_connectionTimeoutMS, m_carrierDesc.m_version);
          m_carrierDesc.m_handshake = m_handshake;
          m_carrier = DefaultCarrier::Create(m_carrierDesc, m_gridMate);
          CarrierEventBus::Handler::BusConnect(m_gridMate);
          m_replicaMgr = aznew ReplicaManager();
          AZ_Assert(m_replicaMgr, "Failed to create ReplicaManager!");
          ReplicaMgrCallbackBus::Handler::BusConnect(m_gridMate);
      } return true;
      case SE_CREATED:
      {
          AZ_Assert(m_state && !m_state->GetReplica(), "We must have a valid state unbound replica at this stage!");
  
          m_handshake->SetHost(IsHost());
          m_handshake->SetInvited(m_myMember->IsInvited());
          m_handshake->SetSessionId(m_sessionId);
  
          if (IsHost())
          {
              AZ_TracePrintf("GridMate", "Session %s created by %s\n", m_sessionId.c_str(), m_myMember->GetId().ToString().c_str());
  
              //we are the host start broadcasting the system clock
              m_carrier->StartClockSync(1000, true);
  
              // Init replica manager
              ReplicaMgrDesc rmDesc(m_myMember->GetId().Compact(), m_carrier, CC_REPLICA_DATA, ReplicaMgrDesc::Role_SyncHost);
              m_replicaMgr->Init(rmDesc);
              m_replicaMgr->RegisterUserContext(AZ::Crc32("GridSession"), this);
  
              // Bind session replica
              Replica* stateReplica = Replica::CreateReplica("SessionStateInfo");
              stateReplica->AttachReplicaChunk(m_state);
              m_replicaMgr->AddMaster(stateReplica);
  
              // Bind member replica
              bool isAdded = AddMember(m_myMember);
              AZ_Error("GridMate", isAdded, "Failed to add my replica, check the number of open slots!")
              if (!isAdded)
              {
                  sm.Transition(SS_DELETE);
                  return true;
              }
  
              sm.Transition(SS_IDLE);
              EBUS_DBG_EVENT(Debug::SessionDrillerBus, OnSessionCreated, this);
              EBUS_EVENT_ID(m_gridMate, SessionEventBus, OnSessionCreated, this);
              EBUS_EVENT_ID(m_gridMate, SessionEventBus, OnSessionHosted, this);
          }
          else
          {
              AZ_TracePrintf("GridMate", "Joining session %s created by %s\n", m_sessionId.c_str(), m_hostAddress.c_str());
  
              // Init replica manager
              ReplicaMgrDesc rmDesc(m_myMember->GetId().Compact(), m_carrier, CC_REPLICA_DATA);
              m_replicaMgr->Init(rmDesc);
              m_replicaMgr->RegisterUserContext(AZ::Crc32("GridSession"), this);
  
              if (m_hostAddress.empty())
              {
                  sm.Transition(SS_DELETE);
                  return true;
              }
  
              sm.Transition(SS_JOIN);
  
              m_handshake->SetPeerMode(m_myMember->m_peerMode.Get());
  
              m_carrier->Connect(m_hostAddress);
  
              EBUS_DBG_EVENT(Debug::SessionDrillerBus, OnSessionCreated, this);
              EBUS_EVENT_ID(m_gridMate, SessionEventBus, OnSessionCreated, this);
          }
  
      } return true;
      };
      return false;
  }
  bool
  GridSession::OnStateIdle(HSM& sm, const HSM::Event& e)
  {
      (void)sm;
      (void)e;
      return false;
  }
  bool
  GridSession::OnStateStart(HSM& sm, const HSM::Event& e)
  {
      (void)sm;
      switch (e.id)
      {
      case HSM::EnterEventId:
      {
          EBUS_DBG_EVENT(Debug::SessionDrillerBus, OnSessionStart, this);
          EBUS_EVENT_ID(m_gridMate, SessionEventBus, OnSessionStart, this);
      } return true;
      }
      return false;
  }
  bool
  GridSession::OnStateInGame(HSM& sm, const HSM::Event& e)
  {
      (void)sm;
      (void)e;
      return false;
  }
  bool
  GridSession::OnStateEnd(HSM& sm, const HSM::Event& e)
  {
      (void)sm;
      switch (e.id)
      {
      case HSM::EnterEventId:
      {
          EBUS_DBG_EVENT(Debug::SessionDrillerBus, OnSessionEnd, this);
          EBUS_EVENT_ID(m_gridMate, SessionEventBus, OnSessionEnd, this);
      } return true;
      }
      return false;
  }
  bool
  GridSession::OnStateDelete(HSM& sm, const HSM::Event& e)
  {
      switch (e.id)
      {
      case HSM::EnterEventId:
      {
          // close all connections and give it some time to make a clean exit (while the session is being deleted)
          m_carrier->Disconnect(AllConnections);
  
          // remove all members
          while (!m_members.empty())
          {
              RemoveMember(m_members.front()->GetId());
          }
      } return true;
      case SE_DELETED:
      {
          // we should wait a little more to make sure send messages are send... or we can wait will all connection are gone
          // m_carrier->GetNumConnections() == 0
          sm.Transition(SS_NO_SESSION);
      } return true;
      }
  
      return false;
  }
  bool
  GridSession::OnStateHostMigrateElection(AZ::HSM& sm, const AZ::HSM::Event& e)
  {
      switch (e.id)
      {
      case HSM::EnterEventId:
      {
          if (m_members.size() < 2)
          {
              Leave(false);
              return true;
          }
  
          if (!m_hostMigrationInProcess)
          {
              m_hostMigrationInProcess = true;
              m_handshake->SetHostMigration(true);
  
              EBUS_DBG_EVENT(Debug::SessionDrillerBus, OnMigrationStart, this);
              EBUS_EVENT_ID(m_gridMate, SessionEventBus, OnMigrationStart, this);
  
              m_hostMigrationTimeOut = m_state ? m_state->m_hostMigrationTimeout.Get() : 0;
              m_hostMigrationVotingTime = m_state ? m_state->m_hostMigrationVotingTime.Get() : 0;
              m_hostMigrationStart = AZStd::chrono::system_clock::now();
          }
          m_hostMigrationSessionMigrated = false;
          m_hostMigrationReplicaMigrated = false;
      } // we don't return just execute the host election code.
      case SE_CONNECTION_LOST:
      {
          // If we have not voted or the host we voted for disconnected.
          if (m_myMember->m_clientState->m_newHostVote.Get() == 0 || m_myMember->m_clientState->m_newHostVote.Get() == static_cast<MemberIDCompact>(reinterpret_cast<size_t>(e.userData)))
          {
              ElectNewHost();
          }
      } return true;
      case SE_UPDATE:
      {
          TimeStamp defaultTime;
          if (m_hostMigrationStart == defaultTime)
          {
              return true;                                           // we have already queued a Leave Command, and waiting for the queue to be executed!
          }
          TimeStamp now = AZStd::chrono::system_clock::now();
          unsigned int votingTime = static_cast<unsigned int>(AZStd::chrono::milliseconds(now - m_hostMigrationStart).count());
          if (votingTime >= m_hostMigrationTimeOut)
          {
              AZ_TracePrintf("GridMate", "Host migration: election did not finish within %d ms!\n", m_hostMigrationTimeOut);
              Leave(false);
              m_hostMigrationStart = defaultTime;
              return true;
          }
  
          // check if we got 50 or more% of the votes if so start the session migration
          MemberIDCompact myId = m_myMember->GetId().Compact();
          int myVotes = 0;
          int hasVoted = 0;
          int maxVoters = 0;
          for (MemberArrayType::iterator iMember = m_members.begin(); iMember != m_members.end(); ++iMember)
          {
              GridMember* member = *iMember;
              if (member->m_clientState)
              {
                  ++maxVoters;
                  MemberIDCompact electedId = member->m_clientState->m_newHostVote.Get();
                  if (electedId != 0)
                  {
                      ++hasVoted;
                      if (electedId == myId)
                      {
                          ++myVotes;
                      }
                  }
              }
          }
          AZ_Assert(m_state, "We should have a valid session state!");
          if (maxVoters == 1 && (m_state->m_flags.Get() & SessionParams::SF_HOST_MIGRATION_NO_EMPTY_SESSIONS))
          {
              // we are by ourself just quit the host migration.
              AZ_TracePrintf("GridMate", "Host migration: we lost all connection and SF_HOST_MIGRATION_NO_EMPTY_SESSIONS flag is set! Leaving the session!\n");
              Leave(false);
              m_hostMigrationStart = defaultTime;
              return true;
          }
  
          if (maxVoters == hasVoted || votingTime >= m_hostMigrationVotingTime)
          {
              // if all people voted or the voting time expired, check if we have the majority.
              float votes = static_cast<float>(myVotes);
              float majority = static_cast<float>(maxVoters)*0.5f;
              if (votes >= majority)  // if we are == we could wait 0.5s before we conclude the voting
              {
                  // we have majority vote, we are the new host
                  m_sessionId.clear(); // clear the current session ID. (means we are the host and we need to assign a new one)
                  sm.Transition(SS_HOST_MIGRATE_SESSION);
              }
          }
      } return true;
      case SE_HM_MIGRATE_CLIENT:
      {
          // we received a command to migrate to a new host, set the target session and migrate to it
          m_sessionId = *reinterpret_cast<const SessionID*>(e.userData);
          sm.Transition(SS_HOST_MIGRATE_SESSION);
      } return true;
      }
  
      return false;
  }
  bool
  GridSession::OnStateHostMigrateSession(AZ::HSM& sm, const AZ::HSM::Event& e)
  {
      (void)sm;
      switch (e.id)
      {
      case HSM::EnterEventId:
      {
          // Reset host migration vote value.
          AZ_Assert(m_myMember && m_myMember->m_clientState, "We should always have local member and state!");
      } return true;
      case SE_HM_SESSION_MIGRATED:
      {
          // Send command to all clients to migrate to the new session. Using my member replica.
          m_myMember->m_clientState->OnNewHostRpc(m_sessionId);
  
          AZ_TracePrintf("GridMate", "New host elected %s(%s)\n", m_myMember->GetName().c_str(), m_myMember->GetId().ToAddress().c_str());
  
          // Tell replica manager about the new host
          m_replicaMgr->Promote();
  
          m_hostMigrationSessionMigrated = true;
      } return true;
      case SE_HM_CLIENT_SESSION_MIGRATED:
      {
          AZ_TracePrintf("GridMate", "Client %s(%s) migrated session %s!\n", m_myMember->GetName().c_str(), m_myMember->GetId().ToAddress().c_str(), m_sessionId.c_str());
          m_hostMigrationSessionMigrated = true;
      } return true;
      case SE_HM_REPLICAS_MIGRATED:
      {
          // Update the room, make sure all members and added/removed where needed.
          if (IsHost())
          {
              // Reset and recompute all the slots
              m_state->m_numFreePrivateSlots.Set(m_state->m_numFreePrivateSlots.Get() + m_state->m_numUsedPrivateSlots.Get());
              m_state->m_numFreePublicSlots.Set(m_state->m_numFreePublicSlots.Get() + m_state->m_numUsedPublicSlots.Get());
              m_state->m_numUsedPrivateSlots.Set(0);
              m_state->m_numUsedPublicSlots.Set(0);
  
              // Re-add the members
              for (AZStd::size_t i = 0; i < m_members.size(); ++i)
              {
                  GridMember* member = m_members[i];
                  member->m_slotType = ReserveSlot(member->IsInvited());
                  AZ_Assert(member->m_slotType != 0, "Somehow during migration the config changed and we can't reserved slot for existing member!");
              }
  
              // Remove all unconnected members
              for (AZStd::size_t i = 0; i < m_members.size(); )
              {
                  GridMember* member = m_members[i];
                  if (member != m_myMember && member->GetConnectionId() == InvalidConnectionID)
                  {
                      member->GetReplica()->Destroy();
                  }
                  else
                  {
                      ++i;
                  }
              }
              // at this moment all orphaned/missing member should be destroyed and deleted!
  
              // All clocks should be synced on US now.
              m_carrier->StartClockSync();
          }
          m_hostMigrationReplicaMigrated = true;
      } return true;
      case SE_UPDATE:
      {
          TimeStamp defaultTime;
          if (m_hostMigrationStart == defaultTime)
          {
              return true;                                           // we have already queued a Leave Command, and waiting for the queue to be executed!
          }
          TimeStamp now = AZStd::chrono::system_clock::now();
          if (AZStd::chrono::milliseconds(now - m_hostMigrationStart).count() >= m_hostMigrationTimeOut)
          {
              AZ_TracePrintf("GridMate", "Host migration: session migration did not finish within %d ms!\n", m_hostMigrationTimeOut);
              Leave(false);
              m_hostMigrationStart = defaultTime;
              return true;
          }
          // Wait for both session and replicas to migrate before we complete the session
          if (m_hostMigrationSessionMigrated && m_hostMigrationReplicaMigrated)
          {
              m_hostMigrationSessionMigrated = false;
              m_hostMigrationReplicaMigrated = false;
  
              GridMember* host = GetHost();
  
              EBUS_DBG_EVENT(Debug::SessionDrillerBus, OnMigrationEnd, this, host);
              EBUS_EVENT_ID(m_gridMate, SessionEventBus, OnMigrationEnd, this, host);
  
              m_myMember->m_clientState->m_newHostVote.Set(0);
  
              if (m_members.size() == 1 && (m_state->m_flags.Get() & SessionParams::SF_HOST_MIGRATION_NO_EMPTY_SESSIONS))
              {
                  Leave(false);
                  AZ_TracePrintf("GridMate", "Host migration: we lost all connection and SF_HOST_MIGRATION_NO_EMPTY_SESSIONS flag is set! Leaving the session!\n");
              }
              else
              {
                  sm.Transition(SS_IDLE);
                  m_hostMigrationInProcess = false;
  
                  m_handshake->SetHostMigration(false);
                  m_handshake->SetHost(IsHost());
                  m_handshake->SetSessionId(m_sessionId);
              }
  
              m_hostMigrationStart = defaultTime;
          }
      } return true;
      case SE_CONNECTION_LOST:
      {
          // if the current host left
          if (m_myMember->m_clientState->m_newHostVote.Get() == static_cast<MemberIDCompact>(reinterpret_cast<size_t>(e.userData)))
          {
              AZ_TracePrintf("GridMate", "New host 0x%x disconnected while migrating the session! Going back to host election...\n", m_myMember->m_clientState->m_newHostVote.Get());
  
              m_myMember->m_clientState->m_newHostVote.Set(0); // reset so we revote
  
              // go back to election.
              sm.Transition(SS_HOST_MIGRATE_ELECTION);
          }
      } return true;
      }
  
      return false;
  }
  
  // End of state machine
  //////////////////////////////////////////////////////////////////////////
  
  //////////////////////////////////////////////////////////////////////////
  //////////////////////////////////////////////////////////////////////////
  // GridMember
  //////////////////////////////////////////////////////////////////////////
  //////////////////////////////////////////////////////////////////////////
  
  //=========================================================================
  // GridMember
  //=========================================================================
  GridMember::GridMember(const MemberIDCompact& memberIdCompact)
      : m_connectionId(InvalidConnectionID)
      , m_session(nullptr)
      , m_slotType(0)
      , m_memberIdCompact(memberIdCompact)
      , m_isHost("IsHost")
      , m_isInvited("IsInvited")
      , m_peerMode("PeerMode")
      , KickRpc("KickRpc")
  {
      SetPriority(k_replicaPriorityRealTime);
  }
  
  //=========================================================================
  // IsReplicaMigratable
  //=========================================================================
  bool GridMember::IsReplicaMigratable()
  {
      return true;
  }
  
  //=========================================================================
  // GetNatType
  //=========================================================================
  NatType
  GridMember::GetNatType() const
  {
      return m_clientState ? m_clientState->m_natType.Get() : NAT_UNKNOWN;
  }
  
  //=========================================================================
  // GetName
  //=========================================================================
  string
  GridMember::GetName() const
  {
      return m_clientState ? m_clientState->m_name.Get().c_str() : "Unknown";
  }
  
  //=========================================================================
  // IsLocal
  //=========================================================================
  bool
  GridMember::IsLocal() const
  {
      return m_session->GetMyMember() == this;
  }
  
  //=========================================================================
  // OnActivate
  //=========================================================================
  void
  GridMember::OnReplicaActivate(const ReplicaContext& rc)
  {
      if (IsLocal())
      {
          // if this member is me... add my state to the system.
          AZ_Assert(m_session->GetMyMember() == this, "The only local member should be myMember too!");
          rc.m_rm->AddMaster(m_clientState->GetReplica());
  
          // Both member and client state are valid! send member joined message
          EBUS_DBG_EVENT(Debug::SessionDrillerBus, OnMemberJoined, m_session, this);
          EBUS_EVENT_ID(m_session->GetGridMate(), SessionEventBus, OnMemberJoined, m_session, this);
      }
  }
  
  //=========================================================================
  // OnDeactivate
  //=========================================================================
  void
  GridMember::OnReplicaDeactivate(const ReplicaContext& rc)
  {
      (void)rc;
      m_session->RemoveMember(GetId());
  
      if (m_clientState)
      {
          // We are deleting the member so send leave message (we are always keeping member <-> state together).
          EBUS_DBG_EVENT(Debug::SessionDrillerBus, OnMemberLeaving, m_session, this);
          EBUS_EVENT_ID(m_session->GetGridMate(), SessionEventBus, OnMemberLeaving, m_session, this);
  
          m_clientState->m_member = nullptr;
      }
  
      if (IsLocal())
      {
          // if our member was removed the session, leave it.
          m_session->Leave(false);
      }
      else
      {
          // unbind the client state
          m_clientState = nullptr;
          m_clientStateReplica = nullptr;
      }
  }
  
  //=========================================================================
  // OnChangeOwnership
  //=========================================================================
  void
  GridMember::OnReplicaChangeOwnership(const ReplicaContext& rc)
  {
      (void)rc;
      AZ_Assert(m_session->IsMigratingHost(), "This function can be called only during host migration!");
      if (IsMaster())
      {
          // Host owns the members, if I became the owner means I am the HOST!
          if (m_session->m_myMember == this)
          {
              m_isHost.Set(true);
          }
          else
          {
              m_isHost.Set(false);
          }
      }
  }
  
  //=========================================================================
  // OnKick
  //=========================================================================
  bool
  GridMember::OnKick(AZ::u8 reason, const RpcContext& rc)
  {
      //  Null check m_session and m_session->GetHost(). 
      //  2 Kick messages in quick succession can cause this to crash otherwise.
      if (m_session && m_session->GetHost() && rc.m_sourcePeer == m_session->GetHost()->GetIdCompact())    //Only the host can kick
      {
          EBUS_DBG_EVENT(Debug::SessionDrillerBus, OnMemberKicked, m_session, this);
          EBUS_EVENT_ID(m_session->GetGridMate(), SessionEventBus, OnMemberKicked, m_session, this, reason);
  
          if (IsLocal())
          {
              m_session->Leave(false);
          }
  
          return true; // this is called only on the master
      }
      return false;
  }
  
  //=========================================================================
  // Mute
  //=========================================================================
  void
  GridMember::Mute(const MemberIDCompact& id)
  {
      if (IsLocal())
      {
          const GridMemberStateReplica::MuteListType& muteList = m_clientState->m_muteList.Get();
          GridMemberStateReplica::MuteListType::const_iterator it = AZStd::find(muteList.begin(), muteList.end(), id);
          if (it == muteList.end())
          {
              // TODO: remove the copy (should be a ref) once replica allows it (with something like GetToModify)
              GridMemberStateReplica::MuteListType muteListCopy = muteList;
              muteListCopy.push_back(id);
              m_clientState->m_muteList.Set(muteListCopy);
          }
      }
  }
  
  //=========================================================================
  // Unmute
  //=========================================================================
  void
  GridMember::Unmute(const MemberIDCompact& id)
  {
      if (IsLocal())
      {
          const GridMemberStateReplica::MuteListType& muteList = m_clientState->m_muteList.Get();
          GridMemberStateReplica::MuteListType::const_iterator it = AZStd::find(muteList.begin(), muteList.end(), id);
          if (it != muteList.end())
          {
              // TODO: remove the copy (should be a ref) once replica allows it (with something like GetToModify)
              GridMemberStateReplica::MuteListType muteListCopy = muteList;
              size_t distance = AZStd::distance(it, muteList.begin());
              muteListCopy.erase(muteListCopy.begin() + distance);
              m_clientState->m_muteList.Set(muteListCopy);
          }
      }
  }
  
  //=========================================================================
  // IsMuted
  //=========================================================================
  bool
  GridMember::IsMuted(const MemberIDCompact& id) const
  {
      if (m_clientState)
      {
          const GridMemberStateReplica::MuteListType& muteList = m_clientState->m_muteList.Get();
          GridMemberStateReplica::MuteListType::const_iterator it = AZStd::find(muteList.begin(), muteList.end(), id);
          return it != muteList.end();
      }
      return true;
  }
  
  //=========================================================================
  // IsTalking
  //=========================================================================
  bool
  GridMember::IsTalking() const
  {
      return AZStd::chrono::milliseconds(AZStd::chrono::system_clock::now() - m_voiceDataProcessed).count() < 250;
  }
  
  void GridMember::UpdateTalking()
  {
      m_voiceDataProcessed = AZStd::chrono::system_clock::now();
  }
  
  //=========================================================================
  // SetHost
  //=========================================================================
  void
  GridMember::SetHost(bool isHost)
  {
      m_isHost.Set(isHost);
  }
  
  //=========================================================================
  // SetInvited
  //=========================================================================
  void
  GridMember::SetInvited(bool isInvited)
  {
      m_isInvited.Set(isInvited);
  }
  
  //=========================================================================
  // SendBinary
  //=========================================================================
  bool
  GridMember::SendBinary(const void* data, unsigned int dataSize, Carrier::DataReliability reliability, Carrier::DataPriority priority)
  {
      if (m_connectionId != InvalidConnectionID && m_session->m_carrier)
      {
          if (dataSize > 0)
          {
              m_session->m_carrier->Send(reinterpret_cast<const char*>(data), dataSize, m_connectionId, reliability, priority, GridSession::CC_USER_DATA);
          }
          return true;
      }
      return false;
  }
  
  //=========================================================================
  // ReceiveBinary
  //=========================================================================
  Carrier::ReceiveResult
  GridMember::ReceiveBinary(char* data, unsigned int maxDataSize)
  {
      if (m_connectionId != InvalidConnectionID && m_session->m_carrier)
      {
          return m_session->m_carrier->Receive(data, maxDataSize, m_connectionId, GridSession::CC_USER_DATA);
      }
      else
      {
          Carrier::ReceiveResult result;
          result.m_state = Carrier::ReceiveResult::NO_MESSAGE_TO_RECEIVE;
          result.m_numBytes = 0;
          return result;
      }
  }
  
  //=========================================================================
  // GetPlatformId
  //=========================================================================
  AZ::PlatformID
  GridMember::GetPlatformId() const
  {
      if (m_clientState)
      {
          return m_clientState->m_platformId.Get();
      }
      else
      {
          return AZ::PLATFORM_MAX;
      }
  }
  
  //=========================================================================
  // GridMemberGetProcessId
  //=========================================================================
  AZ::u32
  GridMember::GetProcessId() const
  {
      if (m_clientState)
      {
          return m_clientState->m_processId.Get();
      }
      else
      {
          return 0;
      }
  }
  
  //=========================================================================
  // GetMachineName
  //=========================================================================
  string
  GridMember::GetMachineName() const
  {
      if (m_clientState)
      {
          return m_clientState->m_machineName.Get();
      }
      else
      {
          return string();
      }
  }
  
  //////////////////////////////////////////////////////////////////////////
  //////////////////////////////////////////////////////////////////////////
  // GridMemberStateReplica
  //////////////////////////////////////////////////////////////////////////
  //////////////////////////////////////////////////////////////////////////
  
  //=========================================================================
  // GridMemberStateReplica
  //=========================================================================
  GridMemberStateReplica::GridMemberStateReplica(GridMember* member)
      : m_member(member)
      , OnNewHostRpc("OnNewHostRpc")
      , m_numConnections("NumConnections")
      , m_natType("NatType")
      , m_name("Name")
      , m_memberId("MemberId")
      , m_newHostVote("NewHostVote")
      , m_muteList("MuteList")
      , m_platformId("PlatformId")
      , m_machineName("MachineName")
      , m_processId("ProcessId")
      , m_isInvited("IsInvited")
  {
      m_platformId.Set(AZ::g_currentPlatform);
      m_machineName.Set(Utils::GetMachineAddress((m_member) ? m_member->GetSession()->GetCarrierDesc().m_familyType : 0));
      SetPriority(k_replicaPriorityRealTime);
  #if defined(AZ_PLATFORM_WINDOWS)
      m_processId.Set(GetCurrentProcessId());
  #endif
  }
  
  //=========================================================================
  // GridMemberStateReplica::OnReplicaActivate
  //=========================================================================
  void
  GridMemberStateReplica::OnReplicaActivate(const ReplicaContext& rc)
  {
      (void)rc;
      if (m_member)
      {
          // This code should be executed only for local members/states
          AZ_Assert(m_member->IsLocal(), "The only GridMemberStateReplica that has a non NULL member pointer should be the local member/state!");
          AZ_Assert(m_member->GetReplica()->GetRepId() != 0 /*InvalidReplicaId*/, "We should always have the member replica activated!");
          m_memberId.Set(m_member->GetIdCompact());
          AZ_Assert(m_member->m_clientState == this, "This member should already point to us!");
      }
      else
      {
          GridSession* session = static_cast<GridSession*>(rc.m_rm->GetUserContext(AZ::Crc32("GridSession")));
          AZ_Assert(session, "We need to have a valid session!");
          session->m_unboundMemberStates.insert(this);
      }
  }
  
  //=========================================================================
  // GridMemberStateReplica::OnReplicaDeactivate
  //=========================================================================
  void
  GridMemberStateReplica::OnReplicaDeactivate(const ReplicaContext& rc)
  {
      (void)rc;
      // for master (this is our state) we always keep it. So don't do anything.
      if (IsMaster())
      {
          return;
      }
  
      if (m_member)
      {
          // client state is gone send leave message
          EBUS_DBG_EVENT(Debug::SessionDrillerBus, OnMemberLeaving, m_member->m_session, m_member);
          EBUS_EVENT_ID(m_member->GetSession()->GetGridMate(), SessionEventBus, OnMemberLeaving, m_member->m_session, m_member);
  
          m_member->m_clientState = nullptr;
          m_member->m_clientStateReplica = nullptr;
          m_member = nullptr;
      }
      else
      {
          GridSession* session = static_cast<GridSession*>(rc.m_rm->GetUserContext(AZ::Crc32("GridSession")));
          AZ_Assert(session, "We need to have a valid session!");
          session->m_unboundMemberStates.erase(this);
      }
  }
  
  //=========================================================================
  // OnNewHost
  //=========================================================================
  bool
  GridMemberStateReplica::OnNewHost(const SessionID& sessionId, const RpcContext&)
  {
      // only process on proxies (remote members)
      if (IsProxy() && m_member)
      {
          // store the new host
          GridMember* myMember = m_member->m_session->GetMyMember();
          AZ_Assert(myMember->m_clientState, "We must have a valid client state!");
  
          // make sure we are in host migrate state
          if (m_member->m_session->m_sm.GetCurrentState() == GridSession::SS_HOST_MIGRATE_ELECTION)
          {
              // store the new host
              myMember->m_clientState->m_newHostVote.Set(m_member->GetId().Compact());
              // migrate to the new host
              m_member->m_session->RequestEventData(GridSession::SE_HM_MIGRATE_CLIENT, sessionId);
          }
          else
          {
              // Evaluate the connection to the host, if it's in trouble and over a certain threshold join the new host.
              GridMember* hostMember = m_member->m_session->GetHost();
              Carrier* carrier = m_member->m_session->GetCarrier();
              bool isHostLost = false;
              if (hostMember)
              {
                  Carrier::Statistics hostStats;
                  carrier->QueryStatistics(hostMember->GetConnectionId(), nullptr, &hostStats);
                  float connectionEvaluationThreshold = m_member->m_session->GetCarrierDesc().m_connectionEvaluationThreshold;
                  if (hostStats.m_connectionFactor >= connectionEvaluationThreshold)
                  {
                      AZ_TracePrintf("GridMate", "Host migration: Host %s disconnected %.2f connection factor (max %.2f) for switching to %s!\n",
                          hostMember->GetId().ToAddress().c_str(), hostStats.m_connectionFactor, connectionEvaluationThreshold, m_member->GetId().ToAddress().c_str());
  
                      isHostLost = true;
                      // disconnect the host and make SURE we end up in host migration election state!
                      carrier->Disconnect(hostMember->GetConnectionId());
                  }
                  else
                  {
                      AZ_TracePrintf("GridMate", "Host migration: Host connection factor %.2f allowed %.2f\n", hostStats.m_connectionFactor, connectionEvaluationThreshold);
                  }
              }
              if (isHostLost)
              {
                  // store the new host
                  myMember->m_clientState->m_newHostVote.Set(m_member->GetId().Compact());
                  // we should be in host migration state by now
                  m_member->m_session->RequestEventData(GridSession::SE_HM_MIGRATE_CLIENT, sessionId);
              }
              else
              {
                  AZ_TracePrintf("GridMate", "Host migration: %s rejected new host %s!\n", m_member->m_session->GetMyMember()->GetId().ToAddress().c_str(), m_member->GetId().ToAddress().c_str());
                  // we have a good connection to our host, so we need to close the connection to the new host.
                  carrier->Disconnect(m_member->GetConnectionId());
              }
          }
      }
      return true;
  }
  
  //////////////////////////////////////////////////////////////////////////
  //////////////////////////////////////////////////////////////////////////
  // GridSession Manager
  //////////////////////////////////////////////////////////////////////////
  //////////////////////////////////////////////////////////////////////////
  
  //=========================================================================
  // SessionService
  //=========================================================================
  SessionService::SessionService(const SessionServiceDesc& desc)
      : m_gridMate(nullptr)
  {
      (void)desc;
  }
  
  //=========================================================================
  // ~SessionService
  //=========================================================================
  SessionService::~SessionService()
  {
      AZ_Assert(m_sessions.empty(), "We are still having active session(s)! Did you forget to call SessionService::Shutdown?");
      AZ_Assert(m_activeSearches.empty() && m_completedSearches.empty(), "We are still having searches! Did you forget to call SessionService::Shutdown?");
      AZ_Assert(m_gridMate == nullptr, "We are still registered with GridMate! Call IGridMate::StopMultiplayerService() first!");
  }
  
  void SessionService::OnGridMateUpdate(IGridMate* gridMate)
  {
      (void)gridMate;
      Update();
  }
  
  void SessionService::OnServiceRegistered(IGridMate* gridMate)
  {
      AZ_Assert(gridMate, "Invalid GridMate instance");
      AZ_Assert(!m_gridMate, "Already bound to GridMate instance");
  
      m_gridMate = gridMate;
  }
  
  void SessionService::OnServiceUnregistered(IGridMate* gridMate)
  {
      (void)gridMate;
  
      while (!m_activeSearches.empty())
      {
          m_activeSearches.front()->Release();
      }
  
      while (!m_completedSearches.empty())
      {
          m_completedSearches.front()->Release();
      }
  
      while (!m_sessions.empty())
      {
          GridSession* session = m_sessions.front();
          session->Shutdown();
          delete session;
      }
  
      m_gridMate = nullptr;
  }
  
  //=========================================================================
  // Update
  //=========================================================================
  void
  SessionService::Update()
  {
      //////////////////////////////////////////////////////////////////////////
      // Sessions
      // A session can shutdown itself and potentially other sessions in it's update this is why
      // we need to be very careful when we update them. List will NOT be rearranged nor new
      // sessions will be added.
      {
          AZStd::fixed_vector<GridSession*, 16> updatedSessions;
          for (int i = static_cast<int>(m_sessions.size()) - 1; i >= 0; )
          {
              GridSession* session = m_sessions[i];
              size_t preUpdateSize = m_sessions.size();
              session->Update();
              updatedSessions.push_back(session);
              if (preUpdateSize != m_sessions.size())
              {
                  // we removed some sessions, find the first session to update
                  i = static_cast<int>(m_sessions.size()) - 1;
                  while (i >= 0)
                  {
                      session = m_sessions[i];
                      if (AZStd::find(updatedSessions.begin(), updatedSessions.end(), session) == updatedSessions.end())
                      {
                          break;
                      }
                      --i;
                  }
              }
              else
              {
                  --i;
              }
          }
      }
      //////////////////////////////////////////////////////////////////////////
  
      //////////////////////////////////////////////////////////////////////////
      // Searches
      for (SearchArrayType::iterator it = m_activeSearches.begin(); it != m_activeSearches.end(); )
      {
          GridSearch* search = *it;
          search->Update();
          if (search->IsDone())
          {
              it = m_activeSearches.erase(it);
              m_completedSearches.push_back(search);
  
              EBUS_DBG_EVENT(Debug::SessionDrillerBus, OnGridSearchComplete, search);
              EBUS_EVENT_ID(m_gridMate, SessionEventBus, OnGridSearchComplete, search);
          }
          else
          {
              ++it;
          }
      }
  
      //////////////////////////////////////////////////////////////////////////
  }
  
  //=========================================================================
  // ReleaseGridSearch
  //=========================================================================
  void SessionService::AddGridSeach(GridSearch* search)
  {
      if (search != nullptr)
      {
          if( AZStd::find(m_activeSearches.begin(), m_activeSearches.end(), search) != m_activeSearches.end() )
          {
              AZ_Error("SessionService", false, "This search %p is already in the active searches list!", search);
          }
          else if( AZStd::find(m_completedSearches.begin(), m_completedSearches.end(), search) != m_completedSearches.end() )
          {
              AZ_Error("SessionService", false, "This search %p is already in the complete searches list!", search);
          }
          else
          {
              if (search->IsDone())
              {
                  m_completedSearches.push_back(search);
              }
              else
              {
                  m_activeSearches.push_back(search);
              }
  
              EBUS_EVENT_ID(m_gridMate, SessionEventBus, OnGridSearchStart, search);
          }
      }
  }
  
  //=========================================================================
  // ReleaseGridSearch
  //=========================================================================
  void SessionService::ReleaseGridSearch(GridSearch* search)
  {
      if (search != nullptr)
      {
          bool released = false;
  
          if (search->IsDone() && !m_completedSearches.empty())
          {
              SearchArrayType::iterator it = AZStd::find(m_completedSearches.begin(), m_completedSearches.end(), search);
  
              if (it != m_completedSearches.end() )
              {
                  m_completedSearches.erase(it);
                  released = true;
              }
              else
              {
                  AZ_Error("SessionService", false, "Completed search %p was NOT found in the complete list!", search);
              }
          }
          else if (!m_activeSearches.empty())
          {
              SearchArrayType::iterator it = AZStd::find(m_activeSearches.begin(), m_activeSearches.end(), search);
  
              if( it != m_activeSearches.end() )
              {
                  m_activeSearches.erase(it);
                  released = true;
              }
              else
              {
                  AZ_Error("SessionService", false, "Active search %p was NOT found in the active list!", search);
              }
          }
  
          if (released)
          {
              EBUS_EVENT_ID(m_gridMate, SessionEventBus, OnGridSearchRelease, search);
          }
  
          delete search;
      }
  }
  
  //=========================================================================
  // AddSession
  //=========================================================================
  void SessionService::AddSession(GridSession* session)
  {
      if(session != nullptr)
      {
          if( AZStd::find(m_sessions.begin(), m_sessions.end(), session) == m_sessions.end() )
          {
              m_sessions.push_back(session);
          }
          else
          {
              AZ_Error("SessionService", false, "Session %p has already been added!", session);
          }
      }
  }
  
  //=========================================================================
  // RemoveSession
  //=========================================================================
  void SessionService::RemoveSession(GridSession* session)
  {
      if (session != nullptr)
      {
          SessionArrayType::iterator it = AZStd::find(m_sessions.begin(), m_sessions.end(), session);
          if (it != m_sessions.end())
          {
              m_sessions.erase(it);
          }
      }
  }
  
  //////////////////////////////////////////////////////////////////////////
  //////////////////////////////////////////////////////////////////////////
  // GridSessionHandshake
  //////////////////////////////////////////////////////////////////////////
  //////////////////////////////////////////////////////////////////////////
  
  //=========================================================================
  // GridSessionHandshake
  //=========================================================================
  GridSessionHandshake::GridSessionHandshake(unsigned int handshakeTimeoutMS, const VersionType& version)
      : m_peerMode(Mode_Undefined)
      , m_version(version)
      , m_handshakeTimeOutMS(handshakeTimeoutMS)
      , m_isHost(false)
      , m_isInvited(false)
      , m_isMigratingHost(false)
  {
  }
  
  //=========================================================================
  // OnInitiate
  //=========================================================================
  void GridSessionHandshake::OnInitiate(ConnectionID id, WriteBuffer& wb)
  {
      (void)id;
      AZStd::lock_guard<AZStd::mutex> l(m_dataLock);
      AZ_Assert(!m_isHost, "A host should NOT initiate session connections, only clients (as they wish to join)!");
      AZ_Assert(!m_sessionId.empty(), "You must have a valid session ID, this is how we filter which connections are allowed and which not!");
      wb.Write(m_sessionId);
      wb.Write(m_isInvited);
      wb.Write(m_peerMode);
      wb.Write(m_version);
  
      if (!m_userData.empty())
      {   
          wb.WriteRaw(m_userData.data(), m_userData.size());
      }
  }
  
  //=========================================================================
  // OnReceiveRequest
  //=========================================================================
  HandshakeErrorCode GridSessionHandshake::OnReceiveRequest(ConnectionID id, ReadBuffer& rb, WriteBuffer& wb)
  {
      (void)id;
      AZStd::lock_guard<AZStd::mutex> l(m_dataLock);
      string sessionId;
      bool isInvited = false;
      RemotePeerMode peerMode;
      VersionType version;
  
      bool isRead = rb.Read(sessionId);
      isRead &= rb.Read(isInvited);
      isRead &= rb.Read(peerMode);
      isRead &= rb.Read(version);
  
      if (version != m_version)
      {
          return HandshakeErrorCode::VERSION_MISMATCH;
      }
  
      bool isSaneData = isRead && (peerMode == Mode_Undefined || peerMode == Mode_Client || peerMode == Mode_Peer);
      if (!isSaneData)
      {
          return HandshakeErrorCode::REJECTED;
      }
  
      if (sessionId != m_sessionId)
      {
          return HandshakeErrorCode::REJECTED;
      }
  
      // make sure we don't accept connections during final stages of host migration...
      if (m_isMigratingHost)
      {
          return HandshakeErrorCode::REJECTED;
      }
  
      if (m_isHost)
      {
          for (NewConnectionsType::iterator connIt = m_newConnections.begin(); connIt != m_newConnections.end(); ++connIt)
          {
              if (connIt->m_id == id)
              {
                  return HandshakeErrorCode::OK; // if we just receive the message again while we already have the member confirm it.
              }
          }
  
          m_newConnections.push_back();
          NewConnection& nc = m_newConnections.back();
          nc.m_id = id;
          nc.m_isInvited = isInvited;
          nc.m_peerModeRequested = peerMode;
          if (rb.Left() > 0)
          {
              nc.m_userData.WriteFromBuffer(rb, rb.Left());
          }
      }
      else
      {
          // We always need to accept connection if client, we should assume the other members knows more as long as the session ID matches.
          //if(GetTopology()==ST_PEER_TO_PEER)
          //{
          //  // check if we have the member
          //}
      }
  
      (void)wb; // no reply by default
      return HandshakeErrorCode::OK;
  }
  
  //=========================================================================
  // OnConfirmRequest
  //=========================================================================
  bool GridSessionHandshake::OnConfirmRequest(ConnectionID id, ReadBuffer& rb)
  {
      (void)id;
      AZStd::lock_guard<AZStd::mutex> l(m_dataLock);
  
      string sessionId;
      if (!rb.Read(sessionId))
      {
          return false;
      }
  
      if (sessionId != m_sessionId)
      {
          return false;
      }
  
      if (m_isMigratingHost)
      {
          return false;
      }
  
      return true;
  }
  
  //=========================================================================
  // OnNewConnection
  //=========================================================================
  bool GridSessionHandshake::OnNewConnection(const string& address)
  {
      AZStd::lock_guard<AZStd::mutex> l(m_dataLock);
  
      if (m_isMigratingHost)
      {
          return false;
      }
  
      if (m_banList.find(address) != m_banList.end())
      {
          return false;
      }
      return true;
  }
  
  //=========================================================================
  // OnDisconnect
  //=========================================================================
  void GridSessionHandshake::OnDisconnect(ConnectionID id)
  {
      AZStd::lock_guard<AZStd::mutex> l(m_dataLock);
      for (NewConnectionsType::iterator connIt = m_newConnections.begin(); connIt != m_newConnections.end(); ++connIt)
      {
          if (connIt->m_id == id)
          {
              m_newConnections.erase(connIt); // remove the connection from the list
              return;
          }
      }
  }
  
  //=========================================================================
  // BanAddress
  //=========================================================================
  void GridSessionHandshake::BanAddress(string address)
  {
      AZStd::lock_guard<AZStd::mutex> l(m_dataLock);
      m_banList.insert(address);
  }
  
  //=========================================================================
  // SetHost
  //=========================================================================
  void GridSessionHandshake::SetHost(bool isHost)
  {
      AZStd::lock_guard<AZStd::mutex> l(m_dataLock);
      m_isHost = isHost;
  }
  
  //=========================================================================
  // SetInvited
  //=========================================================================
  void GridSessionHandshake::SetInvited(bool isInvited)
  {
      AZStd::lock_guard<AZStd::mutex> l(m_dataLock);
      m_isInvited = isInvited;
  }
  
  //=========================================================================
  // SetHostMigration
  //=========================================================================
  void GridSessionHandshake::SetHostMigration(bool isMigrating)
  {
      AZStd::lock_guard<AZStd::mutex> l(m_dataLock);
      m_isMigratingHost = isMigrating;
  }
  
  //=========================================================================
  // SetUserData
  //=========================================================================
  void GridSessionHandshake::SetUserData(const void* data, size_t dataSize)
  {
      AZStd::lock_guard<AZStd::mutex> l(m_dataLock);
      const AZ::u8* bytes = reinterpret_cast<const AZ::u8*>(data);
      m_userData.assign(bytes, bytes + dataSize);
  }
  
  //=========================================================================
  // SetSessionId
  //=========================================================================
  void GridSessionHandshake::SetSessionId(string sessionId)
  {
      AZStd::lock_guard<AZStd::mutex> l(m_dataLock);
      m_sessionId = sessionId;
  }
  
  //=========================================================================
  // AcquireNewConnections
  //=========================================================================
  GridSessionHandshake::NewConnectionsType& GridSessionHandshake::AcquireNewConnections()
  {
      m_dataLock.lock();
      return m_newConnections;
  }
  
  //=========================================================================
  // ReleaseNewConnections
  //=========================================================================
  void GridSessionHandshake::ReleaseNewConnections()
  {
      m_dataLock.unlock();
  }
  
  #endif // #ifndef AZ_UNITY_BUILD
  