context: using System;
  using System.Collections.Generic;
  using System.Linq;
  using Microsoft.Xna.Framework;
  using Microsoft.Xna.Framework.Input;
  using StardewValley;
  
  #pragma warning disable 809 // obsolete override of non-obsolete method (this is deliberate)
  namespace StardewModdingAPI.Framework.Input
  {
      /// <summary>Manages the game's input state.</summary>
      internal sealed class SInputState : InputState
      {
          /*********
          ** Accessors
          *********/
          /// <summary>The maximum amount of direction to ignore for the left thumbstick.</summary>
          private const float LeftThumbstickDeadZone = 0.2f;
  
          /// <summary>The cursor position on the screen adjusted for the zoom level.</summary>
          private CursorPosition CursorPositionImpl;
  
          /// <summary>The player's last known tile position.</summary>
          private Vector2? LastPlayerTile;
  
  
          /*********
          ** Accessors
          *********/
          /// <summary>The controller state as of the last update.</summary>
          public GamePadState RealController { get; private set; }
  
          /// <summary>The keyboard state as of the last update.</summary>
          public KeyboardState RealKeyboard { get; private set; }
  
          /// <summary>The mouse state as of the last update.</summary>
          public MouseState RealMouse { get; private set; }
  
          /// <summary>A derivative of <see cref="RealController"/> which suppresses the buttons in <see cref="SuppressButtons"/>.</summary>
          public GamePadState SuppressedController { get; private set; }
  
          /// <summary>A derivative of <see cref="RealKeyboard"/> which suppresses the buttons in <see cref="SuppressButtons"/>.</summary>
          public KeyboardState SuppressedKeyboard { get; private set; }
  
          /// <summary>A derivative of <see cref="RealMouse"/> which suppresses the buttons in <see cref="SuppressButtons"/>.</summary>
          public MouseState SuppressedMouse { get; private set; }
  
          /// <summary>The cursor position on the screen adjusted for the zoom level.</summary>
          public ICursorPosition CursorPosition => this.CursorPositionImpl;
  
          /// <summary>The buttons which were pressed, held, or released.</summary>
          public IDictionary<SButton, InputStatus> ActiveButtons { get; private set; } = new Dictionary<SButton, InputStatus>();
  
          /// <summary>The buttons to suppress when the game next handles input. Each button is suppressed until it's released.</summary>
          public HashSet<SButton> SuppressButtons { get; } = new HashSet<SButton>();
  
  
          /*********
          ** Public methods
          *********/
          /// <summary>Get a copy of the current state.</summary>
          public SInputState Clone()
          {
              return new SInputState
              {
                  ActiveButtons = this.ActiveButtons,
                  RealController = this.RealController,
                  RealKeyboard = this.RealKeyboard,
                  RealMouse = this.RealMouse,
                  CursorPositionImpl = this.CursorPositionImpl
              };
          }
  
          /// <summary>This method is called by the game, and does nothing since SMAPI will already have updated by that point.</summary>
          [Obsolete("This method should only be called by the game itself.")]
          public override void Update() { }
  
          /// <summary>Update the current button statuses for the given tick.</summary>
          public void TrueUpdate()
          {
              try
              {
                  float zoomMultiplier = (1f / Game1.options.zoomLevel);
  
                  // get new states
                  GamePadState realController = GamePad.GetState(PlayerIndex.One);
                  KeyboardState realKeyboard = Keyboard.GetState();
                  MouseState realMouse = Mouse.GetState();
                  var activeButtons = this.DeriveStatuses(this.ActiveButtons, realKeyboard, realMouse, realController);
                  Vector2 cursorAbsolutePos = new Vector2((realMouse.X * zoomMultiplier) + Game1.viewport.X, (realMouse.Y * zoomMultiplier) + Game1.viewport.Y);
                  Vector2? playerTilePos = Context.IsPlayerFree ? Game1.player.getTileLocation() : (Vector2?)null;
  
                  // update real states
                  this.ActiveButtons = activeButtons;
                  this.RealController = realController;
                  this.RealKeyboard = realKeyboard;
                  this.RealMouse = realMouse;
                  if (cursorAbsolutePos != this.CursorPositionImpl?.AbsolutePixels || playerTilePos != this.LastPlayerTile)
                  {
                      this.LastPlayerTile = playerTilePos;
                      this.CursorPositionImpl = this.GetCursorPosition(realMouse, cursorAbsolutePos, zoomMultiplier);
                  }
  
                  // update suppressed states
                  this.SuppressButtons.RemoveWhere(p => !this.GetStatus(activeButtons, p).IsDown());
                  this.UpdateSuppression();
              }
              catch (InvalidOperationException)
              {
                  // GetState() may crash for some players if window doesn't have focus but game1.IsActive == true
              }
          }
  
          /// <summary>Apply input suppression to current input.</summary>
          public void UpdateSuppression()
          {
              GamePadState suppressedController = this.RealController;
              KeyboardState suppressedKeyboard = this.RealKeyboard;
              MouseState suppressedMouse = this.RealMouse;
  
              this.SuppressGivenStates(this.ActiveButtons, ref suppressedKeyboard, ref suppressedMouse, ref suppressedController);
  
              this.SuppressedController = suppressedController;
              this.SuppressedKeyboard = suppressedKeyboard;
              this.SuppressedMouse = suppressedMouse;
          }
  
          /// <summary>Get the gamepad state visible to the game.</summary>
          [Obsolete("This method should only be called by the game itself.")]
          public override GamePadState GetGamePadState()
          {
              return this.ShouldSuppressNow()
                  ? this.SuppressedController
                  : this.RealController;
          }
  
          /// <summary>Get the keyboard state visible to the game.</summary>
          [Obsolete("This method should only be called by the game itself.")]
          public override KeyboardState GetKeyboardState()
          {
              return this.ShouldSuppressNow()
                  ? this.SuppressedKeyboard
                  : this.RealKeyboard;
          }
  
          /// <summary>Get the keyboard state visible to the game.</summary>
          [Obsolete("This method should only be called by the game itself.")]
          public override MouseState GetMouseState()
          {
              return this.ShouldSuppressNow()
                  ? this.SuppressedMouse
                  : this.RealMouse;
          }
  
          /// <summary>Get whether a given button was pressed or held.</summary>
          /// <param name="button">The button to check.</param>
          public bool IsDown(SButton button)
          {
              return this.GetStatus(this.ActiveButtons, button).IsDown();
          }
  
          /// <summary>Get whether any of the given buttons were pressed or held.</summary>
          /// <param name="buttons">The buttons to check.</param>
          public bool IsAnyDown(InputButton[] buttons)
          {
              return buttons.Any(button => this.IsDown(button.ToSButton()));
          }
  
  
          /*********
          ** Private methods
          *********/
          /// <summary>Get the current cursor position.</summary>
          /// <param name="mouseState">The current mouse state.</param>
          /// <param name="absolutePixels">The absolute pixel position relative to the map, adjusted for pixel zoom.</param>
          /// <param name="zoomMultiplier">The multiplier applied to pixel coordinates to adjust them for pixel zoom.</param>
          private CursorPosition GetCursorPosition(MouseState mouseState, Vector2 absolutePixels, float zoomMultiplier)
          {
              Vector2 screenPixels = new Vector2(mouseState.X * zoomMultiplier, mouseState.Y * zoomMultiplier);
              Vector2 tile = new Vector2((int)((Game1.viewport.X + screenPixels.X) / Game1.tileSize), (int)((Game1.viewport.Y + screenPixels.Y) / Game1.tileSize));
              Vector2 grabTile = (Game1.mouseCursorTransparency > 0 && Utility.tileWithinRadiusOfPlayer((int)tile.X, (int)tile.Y, 1, Game1.player)) // derived from Game1.pressActionButton
                  ? tile
                  : Game1.player.GetGrabTile();
              return new CursorPosition(absolutePixels, screenPixels, tile, grabTile);
          }
  
          /// <summary>Whether input should be suppressed in the current context.</summary>
          private bool ShouldSuppressNow()
          {
              return Game1.chatBox == null || !Game1.chatBox.isActive();
          }
  
          /// <summary>Apply input suppression to the given input states.</summary>
          /// <param name="activeButtons">The current button states to check.</param>
          /// <param name="keyboardState">The game's keyboard state for the current tick.</param>
          /// <param name="mouseState">The game's mouse state for the current tick.</param>
          /// <param name="gamePadState">The game's controller state for the current tick.</param>
          private void SuppressGivenStates(IDictionary<SButton, InputStatus> activeButtons, ref KeyboardState keyboardState, ref MouseState mouseState, ref GamePadState gamePadState)
          {
              if (this.SuppressButtons.Count == 0)
                  return;
  
              // gather info
              HashSet<Keys> suppressKeys = new HashSet<Keys>();
              HashSet<SButton> suppressButtons = new HashSet<SButton>();
              HashSet<SButton> suppressMouse = new HashSet<SButton>();
              foreach (SButton button in this.SuppressButtons)
              {
                  if (button == SButton.MouseLeft || button == SButton.MouseMiddle || button == SButton.MouseRight || button == SButton.MouseX1 || button == SButton.MouseX2)
                      suppressMouse.Add(button);
                  else if (button.TryGetKeyboard(out Keys key))
                      suppressKeys.Add(key);
                  else if (gamePadState.IsConnected && button.TryGetController(out Buttons _))
                      suppressButtons.Add(button);
              }
  
              // suppress keyboard keys
              if (keyboardState.GetPressedKeys().Any() && suppressKeys.Any())
                  keyboardState = new KeyboardState(keyboardState.GetPressedKeys().Except(suppressKeys).ToArray());
  
              // suppress controller keys
              if (gamePadState.IsConnected && suppressButtons.Any())
              {
                  GamePadStateBuilder builder = new GamePadStateBuilder(gamePadState);
                  builder.SuppressButtons(suppressButtons);
                  gamePadState = builder.ToGamePadState();
              }
  
              // suppress mouse buttons
              if (suppressMouse.Any())
              {
                  mouseState = new MouseState(
                      x: mouseState.X,
                      y: mouseState.Y,
                      scrollWheel: mouseState.ScrollWheelValue,
                      leftButton: suppressMouse.Contains(SButton.MouseLeft) ? ButtonState.Released : mouseState.LeftButton,
                      middleButton: suppressMouse.Contains(SButton.MouseMiddle) ? ButtonState.Released : mouseState.MiddleButton,
                      rightButton: suppressMouse.Contains(SButton.MouseRight) ? ButtonState.Released : mouseState.RightButton,
                      xButton1: suppressMouse.Contains(SButton.MouseX1) ? ButtonState.Released : mouseState.XButton1,
                      xButton2: suppressMouse.Contains(SButton.MouseX2) ? ButtonState.Released : mouseState.XButton2
                  );
              }
          }
  
          /// <summary>Get the status of all pressed or released buttons relative to their previous status.</summary>
          /// <param name="previousStatuses">The previous button statuses.</param>
          /// <param name="keyboard">The keyboard state.</param>
          /// <param name="mouse">The mouse state.</param>
          /// <param name="controller">The controller state.</param>
          private IDictionary<SButton, InputStatus> DeriveStatuses(IDictionary<SButton, InputStatus> previousStatuses, KeyboardState keyboard, MouseState mouse, GamePadState controller)
          {
              IDictionary<SButton, InputStatus> activeButtons = new Dictionary<SButton, InputStatus>();
  
              // handle pressed keys
              SButton[] down = this.GetPressedButtons(keyboard, mouse, controller).ToArray();
              foreach (SButton button in down)
                  activeButtons[button] = this.DeriveStatus(this.GetStatus(previousStatuses, button), isDown: true);
  
              // handle released keys
              foreach (KeyValuePair<SButton, InputStatus> prev in previousStatuses)
              {
                  if (prev.Value.IsDown() && !activeButtons.ContainsKey(prev.Key))
                      activeButtons[prev.Key] = InputStatus.Released;
              }
  
              return activeButtons;
          }
  
          /// <summary>Get the status of a button relative to its previous status.</summary>
          /// <param name="oldStatus">The previous button status.</param>
          /// <param name="isDown">Whether the button is currently down.</param>
          private InputStatus DeriveStatus(InputStatus oldStatus, bool isDown)
          {
              if (isDown && oldStatus.IsDown())
                  return InputStatus.Held;
              if (isDown)
                  return InputStatus.Pressed;
              return InputStatus.Released;
          }
  
          /// <summary>Get the status of a button.</summary>
          /// <param name="activeButtons">The current button states to check.</param>
          /// <param name="button">The button to check.</param>
          private InputStatus GetStatus(IDictionary<SButton, InputStatus> activeButtons, SButton button)
          {
              return activeButtons.TryGetValue(button, out InputStatus status) ? status : InputStatus.None;
          }
  
          /// <summary>Get the buttons pressed in the given stats.</summary>
          /// <param name="keyboard">The keyboard state.</param>
          /// <param name="mouse">The mouse state.</param>
          /// <param name="controller">The controller state.</param>
          /// <remarks>Thumbstick direction logic derived from <see cref="ButtonCollection"/>.</remarks>
          private IEnumerable<SButton> GetPressedButtons(KeyboardState keyboard, MouseState mouse, GamePadState controller)
          {
              // keyboard
              foreach (Keys key in keyboard.GetPressedKeys())
                  yield return key.ToSButton();
  
              // mouse
              if (mouse.LeftButton == ButtonState.Pressed)
                  yield return SButton.MouseLeft;
              if (mouse.RightButton == ButtonState.Pressed)
                  yield return SButton.MouseRight;
  
gt:             if (mouse.MiddleButton == ButtonState.Pressed)