context: using System;
  using System.Collections.Generic;
  using System.Diagnostics;
  using System.Numerics;
  using ACE.Entity;
  using ACE.Entity.Enum;
  using ACE.Entity.Enum.Properties;
  using ACE.Server.Factories;
  using ACE.Server.Managers;
  using ACE.Server.Network.GameMessages.Messages;
  using ACE.Server.Network.GameEvent.Events;
  using ACE.Server.Network.Structure;
  using ACE.Server.Physics.Animation;
  using ACE.Server.WorldObjects;
  
  namespace ACE.Server.Entity.Chess
  {
      // ported from Anahera's code
      public class ChessMatch
      {
          public Game ChessBoard;
          public ChessState State;
          public ChessSide[] Sides;
          public ChessLogic Logic;
          public Queue<ChessDelayedAction> Actions;    // delayed action queue
  
          public ChessAiState AiState;
          public ChessAiMoveResult AiFuture;
  
          public ChessMoveResult MoveResult;
          public bool WaitingForMotion;
          public List<ObjectGuid> Motions;
  
          public DateTime? NextRangeCheck;
          public DateTime? StartAiTime;
  
          public List<ChessMove> Log;
  
          public ChessMatch(Game chessBoard)
          {
              ChessBoard = chessBoard;
              Sides = new ChessSide[Chess.NumColors];
  
              Logic = new ChessLogic();
  
              Actions = new Queue<ChessDelayedAction>();
              Motions = new List<ObjectGuid>();
  
              Log = new List<ChessMove>();
          }
  
          public ChessColor GetColor(ObjectGuid playerGuid)
          {
              foreach (var side in Sides)
              {
                  if (side == null)
                      continue;
  
                  if (side.PlayerGuid == playerGuid)
                      return side.Color;
              }
              return ChessColor.None;
          }
  
          public bool InMatch(ObjectGuid playerGuid)
          {
              return GetColor(playerGuid) != ChessColor.None;
          }
  
          public void Update()
          {
              switch (State)
              {
                  case ChessState.WaitingForPlayers:
                      {
                          if (StartAiTime != null && DateTime.UtcNow >= StartAiTime)
                          {
                              AddAi();
                          }
                          break;
                      }
  
                  case ChessState.InProgress:
                      {
                          switch (AiState)
                          {
                              case ChessAiState.WaitingToStart:
                                  StartAiMove();
                                  break;
                              case ChessAiState.WaitingForFinish:
                                  FinishAiMove();
                                  break;
                          }
                          break;
                      }
              }
  
              // don't handle any delayed actions while ai is working to prevent races
              if (AiState != ChessAiState.None)
                  return;
  
              // don't handle any delayed actions while weenie pieces are moving or attacking
              if (WaitingForMotion)
                  return;
  
              while (Actions.Count > 0)
              {
                  var action = Actions.Dequeue();
  
                  switch (action.Action)
                  {
                      case ChessDelayedActionType.Start:
                          Start();
                          break;
                      case ChessDelayedActionType.Move:
                          MoveDelayed(action);
                          break;
                      case ChessDelayedActionType.MovePass:
                          MovePassDelayed(action);
                          break;
                      case ChessDelayedActionType.Stalemate:
                          StalemateDelayed(action);
                          break;
                      case ChessDelayedActionType.Quit:
                          QuitDelayed(action.Color);
                          break;
                  }
              }
  
              if (NextRangeCheck != null)
              {
                  if (NextRangeCheck.Value <= DateTime.UtcNow)
                  {
                      foreach (var side in Sides)
                      {
                          if (side == null)
                              continue;
  
                          if (side.IsAi())
                              continue;
  
                          var player = side.GetPlayer();
                          if (player == null)
                          {
                              QuitDelayed(side.Color);
                              return;
                          }
  
                          // arbitrary distance, should there be some warning before reaching leash range?
                          var distanceToGame = player.Location.DistanceTo(ChessBoard.Location);
                          if (distanceToGame > 40.0f)
                          {
                              QuitDelayed(side.Color);
                              return;
                          }
                      }
                      NextRangeCheck = DateTime.UtcNow.AddSeconds(5);
                  }
              }
          }
  
          public void AddSide(Player player, ChessColor color)
          {
              // ai is represented with object guid 0
              var playerGuid = player != null ? player.Guid : new ObjectGuid(0);
  
              Sides[(int)color] = new ChessSide(playerGuid, color);
  
              if (player != null)
                  player.ChessMatch = this;
  
              // spawn weenie pieces in the world for side
              Logic.WalkPieces((piece) =>
              {
                  if (piece.Color == color)
                      AddWeeniePiece(piece);
              });
  
              if (Sides[(int)Chess.InverseColor(color)] == null)
              {
                  var ai_enabled = PropertyManager.GetDouble("chess_ai_start_time").Item;
                  if (ai_enabled > 0)
                  {
                      player.Session.Network.EnqueueSend(new GameMessageSystemChat($"If another player doesn't join within {ai_enabled} seconds, the game will automatically start with AI", ChatMessageType.Broadcast));
                      StartAiTime = DateTime.UtcNow.AddSeconds(ai_enabled);
                  }
              }
              else
                  Actions.Enqueue(new ChessDelayedAction(ChessDelayedActionType.Start));
          }
  
          public void AddAi()
          {
              if (State != ChessState.WaitingForPlayers)
                  return;
  
              var color = GetFreeColor();
              if (color == ChessColor.None)
                  return;
  
              AddSide(null, color);
          }
  
          public void Join(Player player)
          {
              var color = GetFreeColor();
              if (color != ChessColor.None)
              {
                  if (NextRangeCheck == null)
                      NextRangeCheck = DateTime.UtcNow.AddSeconds(5);
  
                  AddSide(player, color);
              }
  
              player.Session.Network.EnqueueSend(new GameEventJoinGameResponse(player.Session, ChessBoard.Guid, color));
          }
  
          public void MoveEnqueue(Player player, ChessPieceCoord from, ChessPieceCoord to)
          {
              if (State != ChessState.InProgress)
                  return;
  
              var color = GetColor(player.Guid);
  
              Actions.Enqueue(new ChessDelayedAction(ChessDelayedActionType.Move, color, from, to));
          }
  
          public void MovePassEnqueue(Player player)
          {
              if (State != ChessState.InProgress)
                  return;
  
              var color = GetColor(player.Guid);
  
              Actions.Enqueue(new ChessDelayedAction(ChessDelayedActionType.MovePass, color));
          }
  
          public void QuitEnqueue(Player player)
          {
              if (State != ChessState.WaitingForPlayers && State != ChessState.InProgress)
                  return;
  
              var color = GetColor(player.Guid);
  
              Actions.Enqueue(new ChessDelayedAction(ChessDelayedActionType.Quit, color));
          }
  
          public void StalemateEnqueue(Player player, bool stalemate)
          {
              if (State != ChessState.InProgress)
                  return;
  
              var color = GetColor(player.Guid);
  
              Actions.Enqueue(new ChessDelayedAction(ChessDelayedActionType.Stalemate, color, stalemate));
          }
  
          public void AsyncMoveAiSimple(ChessAiAsyncTurnKey key, ChessAiMoveResult result)
          {
              Debug.Assert(AiState == ChessAiState.WaitingForWorker);
              AiState = ChessAiState.InProgress;
  
              ChessPieceCoord from = null, to = null;
              var moveResult = Logic.AsyncCalculateAiSimpleMove(key, ref from, ref to);
              result.SetResult(moveResult, from, to);
  
              AiState = ChessAiState.WaitingForFinish;
          }
  
          public void AsyncMoveAiComplex(ChessAiAsyncTurnKey key, ChessAiMoveResult result)
          {
              Debug.Assert(AiState == ChessAiState.WaitingForWorker);
              AiState = ChessAiState.InProgress;
  
              ChessPieceCoord from = null, to = null;
              uint counter = 0;
              var moveResult = Logic.AsyncCalculateAiComplexMove(key, ref from, ref to, ref counter);
              result.SetResult(moveResult, from, to);
              result.ProfilingCounter = counter;
  
              AiState = ChessAiState.WaitingForFinish;
          }
  
          public void PieceReady(ObjectGuid pieceGuid)
          {
              if (MoveResult.HasFlag(ChessMoveResult.OKMovePromotion))
              {
                  var piece = Logic.GetPiece(pieceGuid);
                  Debug.Assert(piece != null);
                  UpgradeWeeniePiece(piece);
              }
  
              Motions.Remove(pieceGuid);
              if (Motions.Count == 0)
              {
                  WaitingForMotion = false;
                  FinishTurn();
              }
          }
  
          public ChessColor GetFreeColor()
          {
              for (var i = 0; i < Chess.NumColors; i++)
                  if (Sides[i] == null)
                      return (ChessColor)i;
  
              return ChessColor.None;
          }
  
          public void Start()
          {
              Debug.Assert(State == ChessState.WaitingForPlayers);
              State = ChessState.InProgress;
  
              foreach (var side in Sides)
              {
                  if (side.IsAi())
                      continue;
  
                  var player = side.GetPlayer();
                  Debug.Assert(player != null);
                  SendStartGame(player, Logic.Turn);
              }
          }
  
          public void Finish(int winner)
          {
              if (State != ChessState.WaitingForPlayers && State != ChessState.InProgress)
                  return;
  
              foreach (var side in Sides)
              {
                  if (side == null)
                      continue;
  
                  if (side.IsAi())
                      continue;
  
                  var player = PlayerManager.FindByGuid(side.PlayerGuid, out bool isOnline);
                  Player onlinePlayer = null;
                  if (isOnline)
                  {
                      onlinePlayer = PlayerManager.GetOnlinePlayer(side.PlayerGuid);
                      SendGameOver(onlinePlayer, winner);
                  }
  
                  if (winner != Chess.ChessWinnerEndGame)
                  {
                      var totalGames = (player.GetProperty(PropertyInt.ChessTotalGames) ?? 0) + 1;
                      player.SetProperty(PropertyInt.ChessTotalGames, totalGames);
  
                      if (isOnline)
                          onlinePlayer.Session.Network.EnqueueSend(new GameMessagePrivateUpdatePropertyInt(onlinePlayer, PropertyInt.ChessTotalGames, totalGames));
                  }
  
                  if (winner >= 0)
                  {
                      var winnerColor = (ChessColor)winner;
                      if (winnerColor == side.Color)
                      {
                          var won = (player.GetProperty(PropertyInt.ChessGamesWon) ?? 0) + 1;
                          player.SetProperty(PropertyInt.ChessGamesWon, won);
  
                          if (isOnline)
                              onlinePlayer.Session.Network.EnqueueSend(new GameMessagePrivateUpdatePropertyInt(onlinePlayer, PropertyInt.ChessGamesWon, won));
                      }
                      else
                      {
                          var lost = (player.GetProperty(PropertyInt.ChessGamesLost) ?? 0) + 1;
                          player.SetProperty(PropertyInt.ChessGamesLost, lost);
  
                          if (isOnline)
                              onlinePlayer.Session.Network.EnqueueSend(new GameMessagePrivateUpdatePropertyInt(onlinePlayer, PropertyInt.ChessGamesLost, lost));
                      }
                  }
  
                  if (isOnline)
                      onlinePlayer.ChessMatch = null;
              }
  
              if (winner >= 0)
              {
                  // adjust player ranks
                  var playerGuid = Sides[0].PlayerGuid;
                  var opponentGuid = Sides[1].PlayerGuid;
                  var winnerGuid = winner == 0 ? playerGuid : opponentGuid;
  
                  AdjustPlayerRanks(playerGuid, opponentGuid, winnerGuid);
              }
  
              Actions.Clear();
  
              Logic.WalkPieces((piece) =>
              {
                  RemoveWeeniePiece(piece);
              });
  
              State = ChessState.Finished;
              NextRangeCheck = null;
  
              ChessBoard.ChessMatch = null;
          }
  
          public void FinishTurn()
          {
              var side = Sides[(int)Chess.InverseColor(Logic.Turn)];
              var opponent = side.GetPlayer();
              var opponentGuid = side.PlayerGuid;
              if (side != null && !side.IsAi())
                  SendMoveResponse(opponent, MoveResult);
  
              side = Sides[(int)Logic.Turn];
              if (side != null)
              {
                  if (side.IsAi())
                      AiState = ChessAiState.WaitingToStart;
                  else
                  {
                      var move = Logic.GetLastMove();
                      var piece = Logic.GetPiece(move.To);
                      var data = new GameMoveData(ChessMoveType.FromTo, move.Color, move.From, move.To);
                      SendOpponentTurn(side.GetPlayer(), opponentGuid, piece, data);
                  }
              }
          }
  
          public void StartAiMove()
          {
              Debug.Assert(AiState == ChessAiState.WaitingToStart);
              AiState = ChessAiState.WaitingForWorker;
  
              // todo: execute ai work on a separate thread
              var key = new ChessAiAsyncTurnKey();
              AiFuture = new ChessAiMoveResult();
              AsyncMoveAiSimple(key, AiFuture);
          }
  
          public void FinishAiMove()
          {
              Debug.Assert(AiState == ChessAiState.WaitingForFinish);
              AiState = ChessAiState.None;
  
              var result = AiFuture;
              MoveResult = result.Result;
  
              if (MoveResult == ChessMoveResult.NoMoveResult)
              {
                  var color = Chess.InverseColor(Logic.Turn);
                  var side = Sides[(int)color];
                  var opSide = Sides[(int)Logic.Turn];
  
                  // checkmate
  
gt:                 if (Logic.InCheckmate(color, true))