using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using AsyncAwaitBestPractices;
using Microsoft.AspNetCore.SignalR.Client;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using MonoGame.Extended;
using Myra;
using Myra.Graphics2D.Brushes;
using Myra.Graphics2D.TextureAtlases;
using Myra.Graphics2D.UI;
using Myra.Utility;
using Sledgemapper.Shared.Entities;
using Sledgemapper.UI;
using TinyMessenger;
using Thickness = Myra.Graphics2D.Thickness;

namespace Sledgemapper
{
    public class Sledgemapper : Game
    {
        private readonly CommunicationManager _communicationManager;
        private readonly Desktop _desktop;
        private readonly GraphicsDeviceManager _graphics;
        private readonly TinyMessengerHub _messenger;
        private readonly Session _sessionData;
        private readonly State _state;
        private CachedContent _cachedContent;
        private Texture2D _comment;
        private Dictionary<string, SpriteFont> _fonts;
        private bool _isDragging;
        private Label _lblOverlayName;
        private MainWidget _mainWidget;
        private MouseState _oldMouseState;
        private KeyboardState _oldState;
        private Effect _outlineShader;
        private RenderTarget2D _renderTarget;
        private SpriteSheet _rippleSpriteSheet;
        private SpriteBatch _spriteBatch;
        private SpriteSheet _spriteSheet;
        private Texture2D _transparentRedRectangle;
        private Dictionary<string, Texture2D> _wallsContent;
        private Texture2D _whiteRectangle;

        public Sledgemapper()
        {
            _graphics = new GraphicsDeviceManager(this);

            _graphics.GraphicsProfile = GraphicsProfile.Reach;
            _graphics.PreferMultiSampling = true;
            
            Content.RootDirectory = "Content";
            _desktop = new Desktop();

            MyraEnvironment.Game = this;

            _sessionData = new Session();
            IsFixedTimeStep = true;
            TargetElapsedTime = TimeSpan.FromSeconds(1d / 30d);
            _messenger = new TinyMessengerHub();
            _communicationManager = new CommunicationManager(_sessionData,_messenger);
            _communicationManager.Connection.Reconnected += OnHubReconnected;
            _communicationManager.Connection.Reconnecting += OnHubReconnecting;
            _communicationManager.Connection.Closed += OnHubDisconnected;
            _state = State.Instance;
        }

        protected override void Draw(GameTime gameTime)
        {
            if (_spriteBatch is null) return;
            var visibleTilesX = GraphicsDevice.Viewport.Width / _state.TileSize + 1;
            var visibleTilesY = GraphicsDevice.Viewport.Height / _state.TileSize + 1;

            DrawMap();
            ApplyShaderToMap();

            _spriteBatch.Begin(
                transformMatrix: Matrix.CreateTranslation(_state.ViewportCenter),
                sortMode: SpriteSortMode.Texture, samplerState: SamplerState.PointClamp);

            DrawTiles();

            DrawWalls();

            DrawOverlays();

            DrawNotes();

            DrawGrid(visibleTilesX, visibleTilesY);

            DrawPlayerOffline();

            DrawPlayers();

            DrawSelectedWall();

            DrawSelectedIntersection();

            DrawLinePreview();

            DrawRoomPreview();

            _spriteBatch.End();

            DrawRipple(gameTime);

            try
            {
                _desktop?.Render();
            }
            catch
            {
                Console.WriteLine("Desktop render error");
            }

            base.Draw(gameTime);
        }

        protected override void Initialize()
        {
            //      ExceptionlessClient.Default.SubmitEvent(new Event { Message = "Initialize", Type = "AppLifecycle", Source = _settings.MachineName });
            IsMouseVisible = true;
            Window.AllowUserResizing = true;
            Window.ClientSizeChanged += OnClientSizeChanged;
            base.Initialize();
        }

        protected override void LoadContent()
        {
            _spriteBatch = new SpriteBatch(GraphicsDevice);
            _cachedContent = CachedContent.Instance;

            _outlineShader = Content.Load<Effect>("shaders/OutlineShader");
            MyraEnvironment.Game = this;
            ResetRenderTarget();


            _mainWidget = new MainWidget(_communicationManager, _messenger, Window);
            _communicationManager.CheckLogin();

            _wallsContent = Content.LoadContentFolder<Texture2D>("walls");

            _spriteSheet = new SpriteSheet();
            _spriteSheet.LoadContent(Content, "spriteIndex", "sprites");
            _rippleSpriteSheet = new SpriteSheet();
            _rippleSpriteSheet.LoadContent(Content, "handcursorsIndex", "handcursors");

            _lblOverlayName = new Label
            {
                Background = new SolidBrush(Color.SlateGray),
                Padding = new Thickness(4)
            };

            AddItemToToolGrid(_mainWidget.GridWalls, OnWallButtonClicked, _wallsContent);
            AddItemToToolGrid(_mainWidget.GridOverlays, OnOverlayButtonClicked, _spriteSheet);

            _fonts = Content.LoadContentFolder<SpriteFont>("fonts");
            _cachedContent.Eye = Content.Load<Texture2D>("eye");
            _cachedContent.Location = Content.Load<Texture2D>("location");
            _comment = Content.Load<Texture2D>("comment");
            _mainWidget.BtnToolbarLine.Image = new TextureRegion(Content.Load<Texture2D>("icon_line"));
            _mainWidget.BtnToolbarRoom.Image = new TextureRegion(Content.Load<Texture2D>("icon_room"));
            _mainWidget.BtnToolbarDelete.Image = new TextureRegion(Content.Load<Texture2D>("icon_delete"));
            _mainWidget.TxtOverlaySearch.TextChangedByUser += OnTxtOverlaySearchChange;

            _desktop.Root = _mainWidget;

            _transparentRedRectangle = new Texture2D(GraphicsDevice, 1, 1);
            _transparentRedRectangle.SetData(new[] { new Color(Color.Red, 80) });

            _whiteRectangle = new Texture2D(GraphicsDevice, 1, 1);
            _whiteRectangle.SetData(new[] { Color.White });

            CenterOnTile(0, 0);
        }

        protected override void Update(GameTime gameTime)
        {
            var newState = Keyboard.GetState();

            if (IsActive && GraphicsDevice.Viewport.Bounds.Contains(Mouse.GetState().Position) &&
                !_desktop.IsMouseOverGUI && !_desktop.HasModalWidget)
            {
                var mouseState = Mouse.GetState();

                var screenPosition = new Point(mouseState.Position.X - (int)_state.ViewportCenter.X,
                    mouseState.Position.Y - (int)_state.ViewportCenter.Y);

                if (!_desktop.ContextMenu?.Visible ?? true)
                {
                    _state.HoveredTile.X = screenPosition.X / _state.TileSize;
                    _state.HoveredTile.Y = screenPosition.Y / _state.TileSize;


                    if (screenPosition.X < 0) _state.HoveredTile.X--;

                    if (screenPosition.Y < 0) _state.HoveredTile.Y--;
                }

                if (_state.InsertMode == InsertMode.Wall) _state.SelectClosestWall(screenPosition);

                if (_state.InsertMode == InsertMode.Overlay) _state.SelectOverlay(screenPosition);

                if (_state.InsertMode == InsertMode.NewLine || _state.InsertMode == InsertMode.NewRoom ||
                    _state.InsertMode == InsertMode.NewDelete) _state.SelectClosestSnapPoint(screenPosition);

                if (!newState.IsKeyDown(Keys.LeftControl) && mouseState.LeftButton == ButtonState.Pressed &&
                    mouseState.LeftButton == _oldMouseState.LeftButton &&
                    !_desktop.IsMouseOverGUI
                )
                {
                    _state.ViewportCenter =
                        new Vector3(_state.ViewportCenter.X + mouseState.Position.X - _oldMouseState.Position.X,
                            _state.ViewportCenter.Y + mouseState.Position.Y - _oldMouseState.Position.Y, 0);
                    if (mouseState.Position != _oldMouseState.Position) _isDragging = true;
                }

                if (mouseState.LeftButton == ButtonState.Released && mouseState.LeftButton != _oldMouseState.LeftButton)
                {
                    if (_isDragging)
                    {
                        _isDragging = false;
                    }
                    else
                    {
                        _state.SelectedTile.X = _state.HoveredTile.X;
                        _state.SelectedTile.Y = _state.HoveredTile.Y;
                        _communicationManager.Connection?.SendAsync("UpdatePosition", State.Instance.MapName,
                            State.Instance.MapId, _state.SelectedTile);
                    }
                }

                if (mouseState.RightButton == ButtonState.Released &&
                    mouseState.RightButton != _oldMouseState.RightButton)
                {
                    _state.SelectedNote.X = _state.HoveredTile.X;
                    _state.SelectedNote.Y = _state.HoveredTile.Y;

                    var popup = new VerticalStackPanel
                        { Padding = new Thickness(1), Spacing = 2, Background = new SolidBrush(Color.DarkGray) };

                    if (!_sessionData.Notes.ContainsKey(_state.SelectedNote.ToString()))
                    {
                        var newNoteButton = new TextButton
                        {
                            Text = "New Note", Width = 80, Height = 20, Padding = new Thickness(2),
                            HorizontalAlignment = HorizontalAlignment.Left
                        };
                        newNoteButton.Click += OnContextMenuNewNoteClick;
                        popup.AddChild(newNoteButton);
                    }
                    else
                    {
                        _sessionData.Notes.TryGetValue(_state.SelectedNote.ToString(), out var n);
                        _state.SelectedNote = new Note(n);
                        var viewNoteButton = new TextButton
                        {
                            Text = "View Note", Width = 80, Height = 20, Padding = new Thickness(2),
                            HorizontalAlignment = HorizontalAlignment.Left
                        };
                        var deleteNoteButton = new TextButton
                        {
                            Text = "Delete Note", Width = 80, Height = 20, Padding = new Thickness(2),
                            HorizontalAlignment = HorizontalAlignment.Left
                        };
                        viewNoteButton.Click += OnContextMenuViewNoteClick;
                        deleteNoteButton.Click += OnContextMenuDeleteNoteClick;

                        popup.AddChild(viewNoteButton);
                        popup.AddChild(deleteNoteButton);
                    }

                    var pingButton = new TextButton
                    {
                        Text = "Ping", Width = 80, Height = 20, Padding = new Thickness(2),
                        HorizontalAlignment = HorizontalAlignment.Left
                    };
                    var location = new Tile { X = _state.HoveredTile.X, Y = _state.HoveredTile.Y };
                    pingButton.Click += (s, e) => OnContextMenuPingClick(s, e, location);
                    popup.AddChild(pingButton);

                    _desktop.ShowContextMenu(popup, mouseState.Position);
                }

                if (newState.IsKeyDown(Keys.LeftControl)
                    && mouseState.LeftButton == ButtonState.Released
                    && mouseState.LeftButton != _oldMouseState.LeftButton)
                    switch (_state.InsertMode)
                    {
                        case InsertMode.Tile:
                            _state.SelectedTile.X = _state.HoveredTile.X;
                            _state.SelectedTile.Y = _state.HoveredTile.Y;
                            _communicationManager.Connection?.SendAsync("UpdatePosition", State.Instance.MapName,
                                State.Instance.MapId, _state.SelectedTile);

                            _sessionData.NewTile(_state.SelectedTile, _state.CurrentTileId);
                            break;
                        case InsertMode.Wall:
                            _sessionData.NewWall(_state.SelectedWall, _state.CurrentWallId);

                            break;
                        case InsertMode.Overlay:
                            _sessionData.NewOverlay(_state.SelectedOverlay, _state.CurrentOverlayId);
                            break;
                    }


                if (_state.InsertMode == InsertMode.NewLine && newState.IsKeyDown(Keys.LeftControl)
                                                            && mouseState.LeftButton == ButtonState.Released
                                                            && mouseState.LeftButton != _oldMouseState.LeftButton)
                {
                    if (_state.LineStart is null)
                    {
                        _state.LineStart = new SnapPoint
                        {
                            X = _state.SelectedSnapPoint.X, Y = _state.SelectedSnapPoint.Y,
                            Index = _state.SelectedSnapPoint.Index
                        };
                    }
                    else
                    {
                        var line = new Line
                        {
                            Start = new SnapPoint
                                { X = _state.LineStart.X, Y = _state.LineStart.Y, Index = _state.LineStart.Index },
                            End = new SnapPoint
                            {
                                X = _state.SelectedSnapPoint.X, Y = _state.SelectedSnapPoint.Y,
                                Index = _state.SelectedSnapPoint.Index
                            },
                            Width = _state.LineWidth
                        };
                        _state.LineStart = null;
                        _state.LineWidth = 1;
                        _sessionData.NewLine(line);
                    }
                }

                if (_state.InsertMode == InsertMode.NewRoom && newState.IsKeyDown(Keys.LeftControl)
                                                            && mouseState.LeftButton == ButtonState.Released
                                                            && mouseState.LeftButton != _oldMouseState.LeftButton)
                {
                    if (_state.LineStart is null)
                    {
                        _state.LineStart = new SnapPoint
                        {
                            X = _state.SelectedSnapPoint.X, Y = _state.SelectedSnapPoint.Y,
                            Index = _state.SelectedSnapPoint.Index
                        };
                    }
                    else
                    {
                        var line = new Room
                        {
                            Start = new SnapPoint
                                { X = _state.LineStart.X, Y = _state.LineStart.Y, Index = _state.LineStart.Index },
                            End = new SnapPoint
                            {
                                X = _state.SelectedSnapPoint.X, Y = _state.SelectedSnapPoint.Y,
                                Index = _state.SelectedSnapPoint.Index
                            }
                        };
                        _state.LineStart = null;
                        _state.LineWidth = 1;
                        _sessionData.NewRoom(line);
                    }
                }


                if (_state.InsertMode == InsertMode.NewDelete && newState.IsKeyDown(Keys.LeftControl)
                                                              && mouseState.LeftButton == ButtonState.Released
                                                              && mouseState.LeftButton != _oldMouseState.LeftButton)
                {
                    if (_state.LineStart is null)
                    {
                        _state.LineStart = new SnapPoint
                        {
                            X = _state.SelectedSnapPoint.X, Y = _state.SelectedSnapPoint.Y,
                            Index = _state.SelectedSnapPoint.Index
                        };
                    }
                    else
                    {
                        var line = new Room
                        {
                            Start = new SnapPoint
                                { X = _state.LineStart.X, Y = _state.LineStart.Y, Index = _state.LineStart.Index },
                            End = new SnapPoint
                            {
                                X = _state.SelectedSnapPoint.X, Y = _state.SelectedSnapPoint.Y,
                                Index = _state.SelectedSnapPoint.Index
                            },
                            Delete = true
                        };
                        _state.LineStart = null;
                        _state.LineWidth = 1;
                        _sessionData.NewRoom(line);
                    }
                }

                if (_state.InsertMode == InsertMode.NewLine && _state.LineStart != null)
                {
                    if (mouseState.ScrollWheelValue > _oldMouseState.ScrollWheelValue)
                        _state.LineWidth += .01f;
                    else if (mouseState.ScrollWheelValue < _oldMouseState.ScrollWheelValue) _state.LineWidth -= .01f;
                }

                if (newState.IsKeyDown(Keys.LeftControl) &&
                    mouseState.ScrollWheelValue != _oldMouseState.ScrollWheelValue)
                {
                    var maxTileSize = 500;
                    var center = new Point((Window.ClientBounds.Width + 200) / 2 - _state.TileSize / 2,
                        Window.ClientBounds.Height / 2 - _state.TileSize / 2);

                    var tx = (center.X - (int)_state.ViewportCenter.X) / _state.TileSize;
                    var ty = (center.Y - (int)_state.ViewportCenter.Y) / _state.TileSize;

                    if (mouseState.ScrollWheelValue > _oldMouseState.ScrollWheelValue)
                        _state.TileSize = Math.Min(maxTileSize, _state.TileSize + 10);
                    else if (mouseState.ScrollWheelValue < _oldMouseState.ScrollWheelValue)
                        _state.TileSize = Math.Max(10, _state.TileSize - 10);

                    CenterOnTile(tx, ty);
                }

                _oldMouseState = mouseState;
            }


            if (newState.IsKeyDown(Keys.Delete))
                switch (_state.InsertMode)
                {
                    case InsertMode.Tile:
                    case InsertMode.NewRoom:
                    case InsertMode.NewLine:
                    case InsertMode.NewDelete:
                        _state.SelectedTile.X = _state.HoveredTile.X;
                        _state.SelectedTile.Y = _state.HoveredTile.Y;
                        _sessionData.DeleteTile(_state.SelectedTile);
                        break;
                    case InsertMode.Wall:
                        _sessionData.DeleteWall(_state.SelectedWall);
                        break;
                    case InsertMode.Overlay:
                        _sessionData.DeleteOverlay(_state.SelectedOverlay);
                        break;
                }

            if (_oldState.IsKeyDown(Keys.P) && newState.IsKeyUp(Keys.P))
                _communicationManager.Ping(_state.HoveredTile).SafeFireAndForget();

            foreach (var key in newState.GetPressedKeys())
                switch (key)
                {
                    case Keys.Left:
                        if (_oldState.IsKeyUp(Keys.Left) && newState.IsKeyDown(Keys.Left))
                        {
                            _state.SelectedTile.X--;
                            _communicationManager.Connection?.SendAsync("UpdatePosition", State.Instance.MapId,
                                _state.SelectedTile);
                        }

                        break;
                    case Keys.Right:
                        if (_oldState.IsKeyUp(Keys.Right) && newState.IsKeyDown(Keys.Right))
                        {
                            _state.SelectedTile.X++;
                            _communicationManager.Connection?.SendAsync("UpdatePosition", State.Instance.MapId,
                                _state.SelectedTile);
                        }

                        break;
                    case Keys.Up:
                        if (_oldState.IsKeyUp(Keys.Up) && newState.IsKeyDown(Keys.Up))
                        {
                            _state.SelectedTile.Y--;
                            _communicationManager.Connection?.SendAsync("UpdatePosition", State.Instance.MapId,
                                _state.SelectedTile);
                        }

                        break;
                    case Keys.Down:
                        if (_oldState.IsKeyUp(Keys.Down) && newState.IsKeyDown(Keys.Down))
                        {
                            _state.SelectedTile.Y++;
                            _communicationManager.Connection?.SendAsync("UpdatePosition", State.Instance.MapId,
                                _state.SelectedTile);
                        }

                        break;
                }

            _oldState = newState;

            base.Update(gameTime);
        }

        private void AddItemToToolGrid(Grid grid, EventHandler eventAction, Dictionary<string, Texture2D> tilesFolderContent)
        {
            var indexX = 0;
            var indexY = 0;
            foreach (var item in tilesFolderContent)
            {
                var tileButton = new ImageButton
                {
                    Image = new TextureRegion(item.Value), GridColumn = indexY, GridRow = indexX, Id = item.Key,
                    Width = 40, Height = 40
                };
                tileButton.Click += eventAction;
                grid.Widgets.Add(tileButton);
                indexY++;
                if (indexY == 4)
                {
                    indexY = 0;
                    indexX++;
                }
            }
        }

        private void AddItemToToolGrid(Grid grid, EventHandler eventAction, SpriteSheet spriteSheet, string e = "")
        {
            var indexX = 0;
            var indexY = 0;
            grid.Widgets.Clear();
            _mainWidget.ScrOverlay.ResetScroll();
            foreach (var item in spriteSheet.index.Where(t =>
                string.IsNullOrWhiteSpace(e) || t.Key.ToLower().Contains(e.ToLower())))
            {
                var tileButton = new ImageButton
                {
                    Image = new TextureRegion(spriteSheet.Texture, item.Value), GridColumn = indexY, GridRow = indexX,
                    Id = item.Key, Width = 40, Height = 40
                };
                tileButton.Click += eventAction;
                tileButton.MouseMoved += OnTileButtonTouchEntered;
                tileButton.MouseLeft += OnTileButtonTouchLeft;
                grid.Widgets.Add(tileButton);
                indexY++;
                if (indexY == 4)
                {
                    indexY = 0;
                    indexX++;
                }
            }
        }

        private void ApplyShaderToMap()
        {
            var texelSize = new Vector2(_renderTarget.Width, _renderTarget.Height);
            _outlineShader.Parameters["ImageSize"].SetValue(texelSize);
            _outlineShader.Parameters["BorderSize"].SetValue((int)(_state.TileSize / 100f * 10f));

            _outlineShader.Parameters["R"].SetValue(Settings.Instance.OverlayTintColor.R / 255.0f);
            _outlineShader.Parameters["G"].SetValue(Settings.Instance.OverlayTintColor.G / 255.0f);
            _outlineShader.Parameters["B"].SetValue(Settings.Instance.OverlayTintColor.B / 255.0f);

            GraphicsDevice.Clear(Settings.Instance.BackgroundColor);

            _spriteBatch.Begin(
                effect: _outlineShader,
                sortMode: SpriteSortMode.Immediate);
            _spriteBatch.Draw(_renderTarget, Vector2.Zero, null, Color.White);
            _spriteBatch.End();
        }

        private void CenterOnTile(int x, int y)
        {
            var center = new Point((Window.ClientBounds.Width + 200) / 2 - _state.TileSize / 2,
                Window.ClientBounds.Height / 2 - _state.TileSize / 2);
            var dx = center.X - x * _state.TileSize - _state.ViewportCenter.X;
            var dy = center.Y - y * _state.TileSize - _state.ViewportCenter.Y;
            _state.ViewportCenter = new Vector3(_state.ViewportCenter.X + dx, _state.ViewportCenter.Y + dy,
                _state.ViewportCenter.Z);
        }

        private void DrawDelete(Room tile)
        {
            var posX = tile.Start.X * _state.TileSize;
            var posY = tile.Start.Y * _state.TileSize;

            var endposX = tile.End.X * _state.TileSize;
            var endposY = tile.End.Y * _state.TileSize;


            var ww = _state.TileSize / Settings.Instance.TileDeleteDivider;
            if (posX == endposX)
            {
                endposX += ww;
                posX -= ww;
            }

            if (posY == endposY)
            {
                endposY += ww;
                posY -= ww;
            }

            switch (tile.Start.Index)
            {
                case 1:
                    break;
                case 2:
                    posX += _state.TileSize / 2;
                    break;
                case 3:
                    posY += _state.TileSize / 2;

                    break;
                case 4:
                    posX += _state.TileSize / 2;
                    posY += _state.TileSize / 2;
                    break;
            }

            switch (tile.End.Index)
            {
                case 1:
                    break;
                case 2:
                    endposX += _state.TileSize / 2;
                    break;
                case 3:
                    endposY += _state.TileSize / 2;

                    break;
                case 4:
                    endposX += _state.TileSize / 2;
                    endposY += _state.TileSize / 2;
                    break;
            }

            if (posX != endposX && posY != endposY)
            {
                var r = new Rectangle();

                if (posX < endposX && posY < endposY)
                {
                    r = new Rectangle(posX, posY, endposX - posX, endposY - posY);
                }
                else if (posX > endposX && posY > endposY)
                {
                    r = new Rectangle(posX, posY, endposX - posX, endposY - posY);
                }
                else
                {
                    if (endposY < posY) r = new Rectangle(posX, endposY, endposX - posX, posY - endposY);
                    if (endposX < posX) r = new Rectangle(endposX, posY, posX - endposX, endposY - posY);
                }

                _spriteBatch.Draw(_whiteRectangle, r, null, Color.Black, 0, Vector2.Zero, SpriteEffects.None, 1);
            }
        }

        private void DrawGrid(int visibleTilesX, int visibleTilesY)
        {
            for (var i = -1; i < visibleTilesX + 3; i++)
            {
                var posX1 = i * _state.TileSize - _state.ViewportCenter.X;
                var posY1 = -_state.ViewportCenter.Y;
                posX1 -= posX1 % _state.TileSize;
                posY1 -= posY1 % _state.TileSize;
                var posX2 = i * _state.TileSize - _state.ViewportCenter.X;
                var posY2 = GraphicsDevice.Viewport.Height - _state.ViewportCenter.Y;
                posX2 -= posX2 % _state.TileSize;
                posY2 -= posY2 % _state.TileSize;

                _spriteBatch.DrawLine(
                    posX1, posY1,
                    posX2,
                    posY2+ _state.TileSize,
                    Settings.Instance.GridColor);
            }

            for (var i = -1; i < visibleTilesY + 2; i++)
            {
                var posX1 = -_state.ViewportCenter.X;
                var posY1 = i * _state.TileSize - _state.ViewportCenter.Y;
                posX1 -= posX1 % _state.TileSize;
                posY1 -= posY1 % _state.TileSize;
                var posX2 = GraphicsDevice.Viewport.Width - _state.ViewportCenter.X;
                var posY2 = i * _state.TileSize - _state.ViewportCenter.Y;
                posX2 -= posX2 % _state.TileSize;
                posY2 -= posY2 % _state.TileSize;

                _spriteBatch.DrawLine(posX1, posY1,
                    posX2 + _state.TileSize,
                    posY2,
                    Settings.Instance.GridColor);
            }


            if (_state.ShowCellNumbers && _state.TileSize >= 30)
            {
                var ffont =
                    _fonts.FirstOrDefault(m => int.Parse(m.Key.Replace("font", "")) > _state.TileSize / 8).Value ??
                    _fonts.Last().Value;
                var fscale = 1f;

                for (var i = -1; i < visibleTilesX + 2; i++)
                for (var j = -1; j < visibleTilesY + 2; j++)
                {
                    var screenPosition = new Point(i * _state.TileSize - (int)_state.ViewportCenter.X,
                        j * _state.TileSize - (int)_state.ViewportCenter.Y);

                    var x = screenPosition.X / _state.TileSize;
                    var y = screenPosition.Y / _state.TileSize;

                    _spriteBatch.DrawString(ffont,
                        $"{x}:{y}",
                        new Vector2(
                            x * _state.TileSize + 2,
                            y * _state.TileSize + 2
                        ),
                        Color.Black,
                        0,
                        Vector2.Zero,
                        fscale,
                        SpriteEffects.None,
                        0);
                }
            }
        }

        private void DrawLine(Line tile)
        {
            var posX = tile.Start.X * _state.TileSize;
            var posY = tile.Start.Y * _state.TileSize;

            var endposX = tile.End.X * _state.TileSize;
            var endposY = tile.End.Y * _state.TileSize;

            switch (tile.Start.Index)
            {
                case 1:
                    break;
                case 2:
                    posX += _state.TileSize / 2;
                    break;
                case 3:
                    posY += _state.TileSize / 2;

                    break;
                case 4:
                    posX += _state.TileSize / 2;
                    posY += _state.TileSize / 2;
                    break;
            }

            switch (tile.End.Index)
            {
                case 1:
                    break;
                case 2:
                    endposX += _state.TileSize / 2;
                    break;
                case 3:
                    endposY += _state.TileSize / 2;

                    break;
                case 4:
                    endposX += _state.TileSize / 2;
                    endposY += _state.TileSize / 2;
                    break;
            }

            var length = (int)Math.Sqrt(Math.Pow(posX - endposX, 2) + Math.Pow(posY - endposY, 2));
            var height = (int)(_state.TileSize * tile.Width);
            if (length > 0)
            {
                var angle = Math.Atan2(posY - endposY, endposX - posX);
                var angleRad = -(float)angle;
                _spriteBatch.Draw(_whiteRectangle, new Rectangle(posX, posY, length, height), null, Color.White,
                    angleRad, new Vector2(0, 0), SpriteEffects.None, 1);
            }
        }

        private void DrawLinePreview()
        {
            if (_state.InsertMode == InsertMode.NewLine && _state.SelectedSnapPoint != null)
            {
                var snapPoint = new Vector2(_state.SelectedSnapPoint.X * _state.TileSize,
                    _state.SelectedSnapPoint.Y * _state.TileSize);

                switch (_state.SelectedSnapPoint.Index)
                {
                    case 1:
                        break;
                    case 2:
                        snapPoint.X += _state.TileSize / 2;
                        break;
                    case 3:
                        snapPoint.Y += _state.TileSize / 2;
                        break;
                    case 4:
                        snapPoint.Y += _state.TileSize / 2;
                        snapPoint.X += _state.TileSize / 2;
                        break;
                }

                _spriteBatch.DrawCircle(snapPoint, _state.TileSize / 5f, 100, Color.Red, 2);

                if (_state.LineStart != null)
                {
                    var posX = _state.LineStart.X * _state.TileSize;
                    var posY = _state.LineStart.Y * _state.TileSize;

                    var endposX = _state.SelectedSnapPoint.X * _state.TileSize;
                    var endposY = _state.SelectedSnapPoint.Y * _state.TileSize;

                    switch (_state.LineStart.Index)
                    {
                        case 1:
                            break;
                        case 2:
                            posX += _state.TileSize / 2;
                            break;
                        case 3:
                            posY += _state.TileSize / 2;

                            break;
                        case 4:
                            posX += _state.TileSize / 2;
                            posY += _state.TileSize / 2;
                            break;
                    }

                    switch (_state.SelectedSnapPoint.Index)
                    {
                        case 1:
                            break;
                        case 2:
                            endposX += _state.TileSize / 2;
                            break;
                        case 3:
                            endposY += _state.TileSize / 2;

                            break;
                        case 4:
                            endposX += _state.TileSize / 2;
                            endposY += _state.TileSize / 2;
                            break;
                    }

                    var l = Math.Sqrt(Math.Pow(posX - endposX, 2) + Math.Pow(posY - endposY, 2));

                    var angle = Math.Atan2(posY - endposY, endposX - posX);
                    var angleRad = -(float)angle;
                    if (l > 0)
                        _spriteBatch.Draw(_transparentRedRectangle,
                            new Rectangle(posX, posY, (int)l, (int)(_state.TileSize * _state.LineWidth)), null,
                            Color.White, angleRad, new Vector2(0, 0), SpriteEffects.None, 1);
                }
            }
        }

        private void DrawMap()
        {
            GraphicsDevice.SetRenderTarget(_renderTarget);
            GraphicsDevice.Clear(Color.Transparent);

            _spriteBatch.Begin(
                transformMatrix: Matrix.CreateTranslation(_state.ViewportCenter),
                samplerState: SamplerState.PointClamp
            );

            var items = _sessionData.Lines.Values.Union<BaseMapEntity>(_sessionData.Rooms.Values)
                .OrderBy(t => t.Timestamp).ToArray();

            for (var i = 0; i < items.Count(); i++)
            {
                var item = items.ElementAt(i);
                if (IsMapElementOffscreen(item)) continue;
                switch (item)
                {
                    case Line l:
                        DrawLine(l);
                        break;

                    case Room room:
                        if (room.Delete)
                            DrawDelete(room);
                        else
                            DrawRoom(room);
                        break;
                }
            }

            _spriteBatch.End();

            GraphicsDevice.SetRenderTarget(null);
        }

        private void DrawNotes()
        {
            if (_state.TileSize < 30) return;

            for (var i = 0; i < _sessionData.Notes.Values.Count; i++)
            {
                var note = _sessionData.Notes.Values.ElementAt(i);
                _spriteBatch.Draw(
                    _comment,
                    new Rectangle(
                        note.X * _state.TileSize + _state.TileSize - _state.TileSize / 2 + _state.TileSize / 25,
                        note.Y * _state.TileSize + _state.TileSize / 8 + _state.TileSize / 25,
                        (int)(_state.TileSize / 2.5), (int)(_state.TileSize / 2.5 / 1.136)
                    ), Color.Black * .2f
                );

                _spriteBatch.Draw(
                    _comment,
                    new Rectangle(
                        note.X * _state.TileSize + _state.TileSize - _state.TileSize / 2,
                        note.Y * _state.TileSize + _state.TileSize / 8,
                        (int)(_state.TileSize / 2.5), (int)(_state.TileSize / 2.5 / 1.136)
                    ), Settings.Instance.NoteColor
                );
            }
        }

        private void DrawOverlays()
        {
            for (var i = 0; i < _sessionData.Overlays.Values.Count; i++)
            {
                var tile = _sessionData.Overlays.Values.ElementAt(i);
                var spriteRec = _spriteSheet.SourceRectangle(tile.ID);

                float posX;
                float posY;

                if (tile.Intersection)
                {
                    posX = tile.X * _state.TileSize;
                    posY = tile.Y * _state.TileSize;

                    _spriteBatch.Draw(_spriteSheet.Texture,
                        new Vector2(posX + _state.TileSize / 25, posY + _state.TileSize / 25), spriteRec,
                        Color.Black * .2f, MathHelper.ToRadians(90 * tile.Rotation),
                        new Vector2(spriteRec.Value.Width / 2, spriteRec.Value.Height / 2),
                        ((float)_state.TileSize - 10) / spriteRec.Value.Width, SpriteEffects.None, 0);
                    _spriteBatch.Draw(_spriteSheet.Texture, new Vector2(posX, posY), spriteRec,
                        Settings.Instance.OverlayTintColor, MathHelper.ToRadians(90 * tile.Rotation),
                        new Vector2(spriteRec.Value.Width / 2, spriteRec.Value.Height / 2),
                        ((float)_state.TileSize - 10) / spriteRec.Value.Width, SpriteEffects.None, 0);
                }
                else
                {
                    posX = tile.X * _state.TileSize + _state.TileSize / 2f;
                    posY = tile.Y * _state.TileSize + _state.TileSize / 2f;
                }

                _spriteBatch.Draw(_spriteSheet.Texture,
                    new Vector2(posX + _state.TileSize / 25, posY + _state.TileSize / 25), spriteRec, Color.Black * .2f,
                    MathHelper.ToRadians(90 * tile.Rotation),
                    new Vector2(spriteRec.Value.Width / 2, spriteRec.Value.Height / 2),
                    ((float)_state.TileSize - 10) / spriteRec.Value.Width, SpriteEffects.None, 0);
                _spriteBatch.Draw(_spriteSheet.Texture, new Vector2(posX, posY), spriteRec,
                    Settings.Instance.OverlayTintColor, MathHelper.ToRadians(90 * tile.Rotation),
                    new Vector2(spriteRec.Value.Width / 2, spriteRec.Value.Height / 2),
                    ((float)_state.TileSize - 10) / spriteRec.Value.Width, SpriteEffects.None, 0);
            }
        }

        private void DrawPingPointer(Ping ping, GameTime gameTime)
        {
            var durationMs = 2000f;
            var baseRadius = _state.TileSize / 4f;
            var baseOuterRadius = (float)_state.TileSize;
            var iterations = 3f;
            var validPointer = GetPointerVector(new Point(ping.X, ping.Y), out var points);

            if (validPointer)
            {
                _spriteBatch.DrawPolygon(Vector2.Zero, points, ping.Player.Color.ToColor(), 4);


                for (var j = 0; j < iterations; j++)
                {
                    var cycleTime = ((float)gameTime.TotalGameTime.TotalMilliseconds + j * durationMs / iterations) %
                        durationMs / durationMs;
                    var easing = Easings.Interpolate(cycleTime, Easings.Functions.SineEaseInOut);
                    var v2 = new Vector2[points.Length];


                    var tCenter = new Vector2((points[0].X + points[1].X + points[2].X) / 3f,
                        (points[0].Y + points[1].Y + points[2].Y) / 3f);


                    for (var i1 = 0; i1 < v2.Length; i1++)
                    {
                        var svx = (points[i1].X - tCenter.X) * (1 + 2 * easing) + tCenter.X;
                        var svy = (points[i1].Y - tCenter.Y) * (1 + 2 * easing) + tCenter.Y;
                        v2[i1] = new Vector2(svx, svy);
                    }

                    _spriteBatch.DrawPolygon(
                        Vector2.Zero,
                        v2,
                        new Color(ping.Player.Color.ToColor(), 1f - easing),
                        2 + 2 * (1 - easing));
                }
            }
        }

        private void DrawPlayerOffline()
        {
            if (string.IsNullOrWhiteSpace(_sessionData.SessionName))
            {
                var isoffscreen = IsOffscreen(_state.SelectedTile);
                if (isoffscreen)
                {
                    var validPointer = GetPointerVector(new Point(_state.SelectedTile.X, _state.SelectedTile.Y),
                        out var points);
                    if (validPointer) _spriteBatch.DrawPolygon(Vector2.Zero, points, Color.Red, 2);
                }
                else
                {
                    _spriteBatch.DrawRectangle(
                        new Rectangle(_state.SelectedTile.X * _state.TileSize - 2,
                            _state.SelectedTile.Y * _state.TileSize - 2, _state.TileSize + 3, _state.TileSize + 3),
                        Color.Red, 2);
                }
            }
        }

        private void DrawPlayerPointer(Player player)
        {
            var validPointer = GetPointerVector(new Point(player.Position.X, player.Position.Y), out var points);
            if (validPointer) _spriteBatch.DrawPolygon(Vector2.Zero, points, player.Color.ToColor(), 2);
        }

        private void DrawPlayers()
        {
            var ffont = _fonts.FirstOrDefault(m => int.Parse(m.Key.Replace("font", "")) > _state.TileSize).Value ??
                        _fonts.Last().Value;
            var fscale = _state.TileSize / (ffont.LineSpacing * 3f);

            var playerCells = _sessionData.Players.Select(m => m.Position).Distinct().ToList();

            foreach (var cell in playerCells.Where(c => !IsOffscreen(c)))
            {
                var playersInCell = _sessionData.Players.Where(m => m.Position == cell).ToList();
                var i = 0;
                foreach (var player in playersInCell)

                {
                    var color = player.Color.ToColor();

                    var rectangle = new Rectangle();
                    var stringPosition = new Vector2();
                    var measure = ffont.MeasureString(player.Initials);
                    var maxSize = Math.Max(measure.X, measure.Y);

                    if (playersInCell.Count == 1)
                    {
                        fscale = (_state.TileSize - 2) / maxSize;
                        rectangle = new Rectangle(
                            player.Position.X * _state.TileSize,
                            player.Position.Y * _state.TileSize,
                            _state.TileSize - 1,
                            _state.TileSize - 1);
                        stringPosition = new Vector2(
                            player.Position.X * _state.TileSize + 1,
                            player.Position.Y * _state.TileSize + _state.TileSize - measure.Y * fscale);
                    }
                    else if (playersInCell.Count == 2)
                    {
                        fscale = (_state.TileSize / 2 - 2) / maxSize;

                        if (i == 0)
                        {
                            rectangle = new Rectangle(
                                player.Position.X * _state.TileSize,
                                player.Position.Y * _state.TileSize,
                                _state.TileSize / 2 - 1,
                                _state.TileSize - 1);
                            stringPosition = new Vector2(
                                player.Position.X * _state.TileSize + 1,
                                player.Position.Y * _state.TileSize + _state.TileSize - measure.Y * fscale);
                        }
                        else
                        {
                            rectangle = new Rectangle(
                                player.Position.X * _state.TileSize + _state.TileSize / 2,
                                player.Position.Y * _state.TileSize,
                                _state.TileSize / 2 - 1,
                                _state.TileSize - 1);
                            stringPosition = new Vector2(
                                player.Position.X * _state.TileSize + _state.TileSize / 2 + 1,
                                player.Position.Y * _state.TileSize + _state.TileSize - measure.Y * fscale);
                        }

                        i++;
                    }
                    else if (playersInCell.Count >= 3)
                    {
                        fscale = (_state.TileSize / 2 - 2) / maxSize;
                        switch (i)
                        {
                            case 0:
                                rectangle = new Rectangle(
                                    player.Position.X * _state.TileSize,
                                    player.Position.Y * _state.TileSize,
                                    _state.TileSize / 2 - 1,
                                    _state.TileSize / 2 - 1);
                                stringPosition = new Vector2(
                                    player.Position.X * _state.TileSize + 1,
                                    player.Position.Y * _state.TileSize + _state.TileSize / 2 - measure.Y * fscale);

                                break;
                            case 1:
                                rectangle = new Rectangle(
                                    player.Position.X * _state.TileSize + _state.TileSize / 2,
                                    player.Position.Y * _state.TileSize,
                                    _state.TileSize / 2 - 1,
                                    _state.TileSize / 2 - 1);
                                stringPosition = new Vector2(
                                    player.Position.X * _state.TileSize + _state.TileSize / 2 + 1,
                                    player.Position.Y * _state.TileSize + _state.TileSize / 2 - measure.Y * fscale);

                                break;
                            case 2:
                                rectangle = new Rectangle(
                                    player.Position.X * _state.TileSize,
                                    player.Position.Y * _state.TileSize + _state.TileSize / 2,
                                    _state.TileSize / 2 - 1,
                                    _state.TileSize / 2 - 1);
                                stringPosition = new Vector2(
                                    player.Position.X * _state.TileSize + 1,
                                    player.Position.Y * _state.TileSize + _state.TileSize - measure.Y * fscale);

                                break;
                            case 3:
                            default:
                                rectangle = new Rectangle(
                                    player.Position.X * _state.TileSize + _state.TileSize / 2,
                                    player.Position.Y * _state.TileSize + _state.TileSize / 2,
                                    _state.TileSize / 2 - 1,
                                    _state.TileSize / 2 - 1);
                                stringPosition = new Vector2(
                                    player.Position.X * _state.TileSize + _state.TileSize / 2 + 1,
                                    player.Position.Y * _state.TileSize + _state.TileSize - measure.Y * fscale);

                                break;
                        }

                        i++;
                    }

                    _spriteBatch.DrawRectangle(rectangle, color, 2);
                    _spriteBatch.DrawString(ffont,
                        player.Initials,
                        stringPosition,
                        color,
                        0,
                        Vector2.Zero,
                        fscale,
                        SpriteEffects.None,
                        0);
                }
            }

            foreach (var player in _sessionData.Players)
            {
                var isOffscreen = IsOffscreen(player.Position);
                if (isOffscreen) DrawPlayerPointer(player);
            }
        }

        private void DrawRipple(GameTime gameTime)
        {
            _spriteBatch.Begin(
                blendState: BlendState.NonPremultiplied,
                transformMatrix: Matrix.CreateTranslation(_state.ViewportCenter));

            var durationMs = 2000;
            var baseRadius = _state.TileSize / 4f;
            var baseOuterRadius = (float)_state.TileSize;
            var iterations = 3f;
            var guids = _sessionData.Pings.Keys.ToArray();

            foreach (var guid in guids)
            {
                var pingFound = _sessionData.Pings.TryGetValue(guid, out var ping);
                if (!pingFound) continue;

                if (ping.StartTime == 0) ping.StartTime = gameTime.TotalGameTime.TotalMilliseconds;

                var x = ping.X * _state.TileSize + _state.TileSize / 2f;
                var y = ping.Y * _state.TileSize + _state.TileSize / 2f;

                if (IsOffscreen(new Tile { X = ping.X, Y = ping.Y }))
                {
                    DrawPingPointer(ping, gameTime);
                }
                else
                {
                    _spriteBatch.DrawCircle(
                        new Vector2(x, y),
                        baseRadius,
                        20,
                        ping.Player.Color.ToColor(),
                        baseRadius);

                    for (var i = 0; i < iterations; i++)
                    {
                        var cycleTime =
                            ((float)gameTime.TotalGameTime.TotalMilliseconds + (float)i * durationMs / iterations) %
                            durationMs / durationMs;
                        var easing = Easings.Interpolate(cycleTime, Easings.Functions.SineEaseInOut);
                        _spriteBatch.DrawCircle(
                            new Vector2(x, y),
                            baseRadius + baseOuterRadius * easing,
                            20,
                            new Color(ping.Player.Color.ToColor(), 1 - easing),
                            2 + 5 * (1 - easing));
                    }
                }
            }

            foreach (var guid in guids)
            {
                var pingFound = _sessionData.Pings.TryGetValue(guid, out var ping);
                if (!pingFound) continue;

                if (gameTime.TotalGameTime.TotalMilliseconds - ping.StartTime > Settings.Instance.PingDuration)
                    _sessionData.Pings.TryRemove(guid, out var _);
            }


            _spriteBatch.End();
        }

        private void DrawRoom(Room tile)
        {
            var posX = tile.Start.X * _state.TileSize;
            var posY = tile.Start.Y * _state.TileSize;

            var endposX = tile.End.X * _state.TileSize;
            var endposY = tile.End.Y * _state.TileSize;

            switch (tile.Start.Index)
            {
                case 1:
                    break;
                case 2:
                    posX += _state.TileSize / 2;
                    break;
                case 3:
                    posY += _state.TileSize / 2;

                    break;
                case 4:
                    posX += _state.TileSize / 2;
                    posY += _state.TileSize / 2;
                    break;
            }

            switch (tile.End.Index)
            {
                case 1:
                    break;
                case 2:
                    endposX += _state.TileSize / 2;
                    break;
                case 3:
                    endposY += _state.TileSize / 2;

                    break;
                case 4:
                    endposX += _state.TileSize / 2;
                    endposY += _state.TileSize / 2;
                    break;
            }

            if (posX != endposX && posY != endposY)
            {
                if (posX < endposX && posY < endposY)

                {
                    _spriteBatch.Draw(_whiteRectangle, new Rectangle(posX, posY, endposX - posX, endposY - posY), null,
                        Color.White, 0, Vector2.Zero, SpriteEffects.None, 1);
                }
                else if (posX > endposX && posY > endposY)
                {
                    _spriteBatch.Draw(_whiteRectangle, new Rectangle(posX, posY, endposX - posX, endposY - posY), null,
                        Color.White, 0, Vector2.Zero, SpriteEffects.None, 1);
                }

                else
                {
                    if (endposY < posY)
                        _spriteBatch.Draw(_whiteRectangle, new Rectangle(posX, endposY, endposX - posX, posY - endposY),
                            null, Color.White, 0, Vector2.Zero, SpriteEffects.None, 1);

                    if (endposX < posX)
                        _spriteBatch.Draw(_whiteRectangle, new Rectangle(endposX, posY, posX - endposX, endposY - posY),
                            null, Color.White, 0, Vector2.Zero, SpriteEffects.None, 1);
                }
            }
        }

        private void DrawRoomPreview()
        {
            if ((_state.InsertMode == InsertMode.NewRoom || _state.InsertMode == InsertMode.NewDelete) &&
                _state.SelectedSnapPoint != null)
            {
                var snapPoint = new Vector2(_state.SelectedSnapPoint.X * _state.TileSize,
                    _state.SelectedSnapPoint.Y * _state.TileSize);

                switch (_state.SelectedSnapPoint.Index)
                {
                    case 1:
                        break;
                    case 2:
                        snapPoint.X += _state.TileSize / 2;
                        break;
                    case 3:
                        snapPoint.Y += _state.TileSize / 2;
                        break;
                    case 4:
                        snapPoint.Y += _state.TileSize / 2;
                        snapPoint.X += _state.TileSize / 2;
                        break;
                }

                _spriteBatch.DrawCircle(snapPoint, _state.TileSize / 6f, 50, Color.Red, 2);

                if (_state.LineStart != null)
                {
                    var posX = _state.LineStart.X * _state.TileSize;
                    var posY = _state.LineStart.Y * _state.TileSize;

                    var endposX = _state.SelectedSnapPoint.X * _state.TileSize;
                    var endposY = _state.SelectedSnapPoint.Y * _state.TileSize;
                    if (_state.InsertMode == InsertMode.NewDelete)
                    {
                        var ww = _state.TileSize / Settings.Instance.TileDeleteDivider;
                        if (posX == endposX)
                        {
                            endposX += ww;
                            posX -= ww;
                        }

                        if (posY == endposY)
                        {
                            endposY += ww;
                            posY -= ww;
                        }
                    }

                    switch (_state.LineStart.Index)
                    {
                        case 1:
                            break;
                        case 2:
                            posX += _state.TileSize / 2;
                            break;
                        case 3:
                            posY += _state.TileSize / 2;

                            break;
                        case 4:
                            posX += _state.TileSize / 2;
                            posY += _state.TileSize / 2;
                            break;
                    }

                    switch (_state.SelectedSnapPoint.Index)
                    {
                        case 1:
                            break;
                        case 2:
                            endposX += _state.TileSize / 2;
                            break;
                        case 3:
                            endposY += _state.TileSize / 2;

                            break;
                        case 4:
                            endposX += _state.TileSize / 2;
                            endposY += _state.TileSize / 2;
                            break;
                    }

                    var ffont = _fonts.FirstOrDefault(m => int.Parse(m.Key.Replace("font", "")) > _state.TileSize / 3)
                        .Value ?? _fonts.Last().Value;
                    var fscale = 1.2f;
                    var width = Math.Abs(endposX - posX);
                    var height = Math.Abs(posY - endposY);
                    var tilesWidth = width / (double)_state.TileSize;
                    var tilesHeight = height / (double)_state.TileSize;
                    tilesWidth = Math.Round(tilesWidth * 2, MidpointRounding.AwayFromZero) / 2;
                    tilesHeight = Math.Round(tilesHeight * 2, MidpointRounding.AwayFromZero) / 2;
                    var xmeasure = ffont.MeasureString($"{tilesWidth}");
                    var ymeasure = ffont.MeasureString($"{tilesHeight}");
                    float widthX = 0, widthY = 0, heightX = 0, heightY = 0;

                    var area = new Rectangle();
                    if (posX != endposX && posY != endposY)
                    {
                        if (posX > endposX && posY > endposY || posX < endposX && posY < endposY)
                        {
                            area = new Rectangle(posX, posY, endposX - posX, endposY - posY);

                            if (posX > endposX && posY > endposY)
                            {
                                widthX = endposX + width / 2 - xmeasure.X / 2;
                                widthY = endposY - ymeasure.Y * 1.2f;

                                heightX = posX + xmeasure.X / 2;
                                heightY = endposY + height / 2 - ymeasure.Y / 2;
                            }
                            else if (posX < endposX && posY < endposY)
                            {
                                widthX = posX + width / 2 - xmeasure.X / 2;
                                widthY = posY - ymeasure.Y * 1.2f;

                                heightX = endposX + xmeasure.X / 2;
                                heightY = posY + height / 2 - ymeasure.Y / 2;
                            }
                        }
                        else
                        {
                            if (endposY < posY)
                            {
                                area = new Rectangle(posX, endposY, endposX - posX, posY - endposY);

                                widthX = posX + width / 2 - xmeasure.X / 2;
                                widthY = endposY - ymeasure.Y * 1.2f;

                                heightX = endposX + xmeasure.X / 2;
                                heightY = endposY + height / 2 - ymeasure.Y / 2;
                            }

                            if (endposX < posX)
                            {
                                area = new Rectangle(endposX, posY, posX - endposX, endposY - posY);

                                widthX = endposX + width / 2 - xmeasure.X / 2;
                                widthY = posY - ymeasure.Y * 1.2f;

                                heightX = posX + xmeasure.X / 2;
                                heightY = posY + height / 2 - ymeasure.Y / 2;
                            }
                        }

                        _spriteBatch.Draw(_transparentRedRectangle, area, null, Color.White, 0, new Vector2(0, 0),
                            SpriteEffects.None, 1);

                        _spriteBatch.DrawString(ffont,
                            $"{tilesWidth}",
                            new Vector2(
                                widthX,
                                widthY
                            ),
                            Color.Red,
                            0,
                            Vector2.Zero,
                            fscale,
                            SpriteEffects.None,
                            0);

                        _spriteBatch.DrawString(ffont,
                            $"{tilesHeight}",
                            new Vector2(
                                heightX, heightY
                            ),
                            Color.Red,
                            0,
                            Vector2.Zero,
                            fscale,
                            SpriteEffects.None,
                            0);
                    }
                }
            }
        }

        private void DrawSelectedIntersection()
        {
            if (_state.InsertMode == InsertMode.Overlay && _state.SelectedOverlay.Intersection)
            {
                var overlay = new Vector2(_state.SelectedOverlay.X * _state.TileSize,
                    _state.SelectedOverlay.Y * _state.TileSize);
                _spriteBatch.DrawCircle(overlay, _state.TileSize / 3f, 100, Color.Red, 2);
            }
        }

        private void DrawSelectedWall()
        {
            if (_state.InsertMode == InsertMode.Wall)
            {
                var startWall = new Vector2(_state.SelectedWall.X * _state.TileSize,
                    _state.SelectedWall.Y * _state.TileSize);
                _spriteBatch.DrawLine(startWall, _state.TileSize,
                    MathHelper.ToRadians(90 * _state.SelectedWall.Rotation), Color.Red, 2);
            }
        }

        private void DrawTiles()
        {
            for (var i = 0; i < _sessionData.Map.Values.Count; i++)
            {
                var tile = _sessionData.Map.Values.ElementAt(i);
                var content = Content.Load<Texture2D>($"tiles/{tile.ID}");

                var posX = tile.X * _state.TileSize + _state.TileSize / 2f;
                var posY = tile.Y * _state.TileSize + _state.TileSize / 2f;

                _spriteBatch.Draw(content, new Vector2(posX, posY),
                    null, Color.White, MathHelper.ToRadians(90 * tile.Rotation),
                    new Vector2(content.Width / 2, content.Height / 2), (float)_state.TileSize / content.Width,
                    SpriteEffects.None, 0);
            }
        }

        private void DrawWalls()
        {
            for (var i = 0; i < _sessionData.Walls.Values.Count; i++)
            {
                var wall = _sessionData.Walls.Values.ElementAt(i);
                var content = _wallsContent[$"{wall.ID}"];
                var scale = _state.TileSize / (float)content.Height;
                var offset = scale * content.Width / 2f;
                var posX = wall.X * _state.TileSize;
                var posY = wall.Y * _state.TileSize;
                if (wall.Rotation == 1)
                    posX -= (int)offset;
                else if (wall.Rotation == 0) posY += (int)offset;
                _spriteBatch.Draw(content, new Vector2(posX, posY), null, Color.White,
                    MathHelper.ToRadians(90 * (wall.Rotation - 1)), new Vector2(0, 0), scale, SpriteEffects.None, 0);
            }
        }

        private void EditNote(Note note)
        {
            _state.SelectedNote = new Note { X = note.X, Y = note.Y, Text = note.Text };
            new NoteWindow(_communicationManager, note).ShowInModalWindow(_desktop, $" Note on {note.X}:{note.Y}");
        }

        private bool GetPointerVector(Point target, out Vector2[] points)
        {
            var offset = _state.TileSize / 2;
            var leftBound = 200 + offset;
            var topBound = 75 + offset;
            var bottomBound = 25 + offset;
            var rightBound = offset;
            points = Array.Empty<Vector2>();
            var center = new Point((Window.ClientBounds.Width + leftBound) / 2 - (int)_state.ViewportCenter.X,
                Window.ClientBounds.Height / 2 - (int)_state.ViewportCenter.Y);

            // center
            var p1 = new Vector2(center.X, center.Y);

            // point
            var p2 = new Vector2(
                target.X * _state.TileSize + offset,
                target.Y * _state.TileSize + offset);

            // top right
            var p3 = new Vector2(
                Window.ClientBounds.Width - _state.ViewportCenter.X - rightBound,
                topBound - _state.ViewportCenter.Y);

            //bottom right
            var p4 = new Vector2(
                Window.ClientBounds.Width - _state.ViewportCenter.X - rightBound,
                Window.ClientBounds.Height - _state.ViewportCenter.Y - bottomBound);

            var ua1 = ((p4.X - p3.X) * (p1.Y - p3.Y) - (p4.Y - p3.Y) * (p1.X - p3.X)) /
                      ((p4.Y - p3.Y) * (p2.X - p1.X) - (p4.X - p3.X) * (p2.Y - p1.Y));

            p3 = new Vector2(leftBound - _state.ViewportCenter.X, topBound - _state.ViewportCenter.Y);
            p4 = new Vector2(Window.ClientBounds.Width - _state.ViewportCenter.X, topBound - _state.ViewportCenter.Y);
            var ua2 = ((p4.X - p3.X) * (p1.Y - p3.Y) - (p4.Y - p3.Y) * (p1.X - p3.X)) /
                      ((p4.Y - p3.Y) * (p2.X - p1.X) - (p4.X - p3.X) * (p2.Y - p1.Y));

            p3 = new Vector2(leftBound - _state.ViewportCenter.X, topBound - _state.ViewportCenter.Y);
            p4 = new Vector2(leftBound - _state.ViewportCenter.X,
                Window.ClientBounds.Height - _state.ViewportCenter.Y - bottomBound);
            var ua3 = ((p4.X - p3.X) * (p1.Y - p3.Y) - (p4.Y - p3.Y) * (p1.X - p3.X)) /
                      ((p4.Y - p3.Y) * (p2.X - p1.X) - (p4.X - p3.X) * (p2.Y - p1.Y));

            p3 = new Vector2(leftBound - _state.ViewportCenter.X,
                Window.ClientBounds.Height - _state.ViewportCenter.Y - bottomBound);
            p4 = new Vector2(Window.ClientBounds.Width - _state.ViewportCenter.X,
                Window.ClientBounds.Height - _state.ViewportCenter.Y - bottomBound);
            var ua4 = ((p4.X - p3.X) * (p1.Y - p3.Y) - (p4.Y - p3.Y) * (p1.X - p3.X)) /
                      ((p4.Y - p3.Y) * (p2.X - p1.X) - (p4.X - p3.X) * (p2.Y - p1.Y));

            var uas = new List<float> { ua1, ua2, ua3, ua4 };
            if (uas.Any(u => u > 0 && u < 1))
            {
                var ua = uas.Where(u => u > 0 && u < 1).Min();
                var i = uas.IndexOf(ua);
                var x = p1.X + ua * (p2.X - p1.X);
                var y = p1.Y + ua * (p2.Y - p1.Y);

                switch (i)
                {
                    case 0:
                        x += offset;
                        points = new[]
                            { new(x, y), new Vector2(x - 20, y - 10), new Vector2(x - 20, y + 10), new Vector2(x, y) };
                        break;
                    case 1:
                        y -= offset;
                        points = new[]
                            { new(x, y), new Vector2(x - 10, y + 20), new Vector2(x + 10, y + 20), new Vector2(x, y) };
                        break;
                    case 2:
                        x -= offset;
                        points = new[]
                            { new(x, y), new Vector2(x + 20, y + 10), new Vector2(x + 20, y - 10), new Vector2(x, y) };
                        break;
                    case 3:
                        y += offset;
                        points = new[]
                            { new(x, y), new Vector2(x + 10, y - 20), new Vector2(x - 10, y - 20), new Vector2(x, y) };
                        break;
                }

                return true;
            }

            return false;
        }

        private bool IsMapElementOffscreen(BaseMapEntity item)
        {
            SnapPoint start, end;

            switch (item)
            {
                case Line l:
                    start = l.Start;
                    end = l.End;
                    break;

                case Room room:
                    start = room.Start;
                    end = room.End;
                    break;
                default:
                    return true;
            }

            var visibleTilesX = GraphicsDevice.Viewport.Width / _state.TileSize + 1;
            var visibleTilesY = GraphicsDevice.Viewport.Height / _state.TileSize + 1;

            var screenPositionTopLeft =
                new Point(200 - _state.TileSize + 0 * _state.TileSize - (int)_state.ViewportCenter.X,
                    0 * _state.TileSize + _state.TileSize - (int)_state.ViewportCenter.Y);
            var screenPositionBottomRight = new Point(visibleTilesX * _state.TileSize - (int)_state.ViewportCenter.X,
                visibleTilesY * _state.TileSize - 20 - (int)_state.ViewportCenter.Y);
            var tileTopLeft = new Point(screenPositionTopLeft.X / _state.TileSize,
                screenPositionTopLeft.Y / _state.TileSize);
            var tileBottomRight = new Point(screenPositionBottomRight.X / _state.TileSize,
                screenPositionBottomRight.Y / _state.TileSize);

            if (start.X < tileTopLeft.X && start.Y < tileTopLeft.Y && start.X > tileBottomRight.X &&
                start.Y > tileBottomRight.Y &&
                end.X < tileTopLeft.X && end.Y < tileTopLeft.Y && end.X > tileBottomRight.X && end.Y > tileBottomRight.Y
            )
                return true;
            return false;
        }

        private bool IsOffscreen(Tile position)
        {
            var boxTL = new Point(200 - _state.TileSize / 2, 75 - _state.TileSize / 2);
            var boxBR = new Point(GraphicsDevice.Viewport.Width + _state.TileSize / 2,
                GraphicsDevice.Viewport.Height - 25 + _state.TileSize / 2);

            var tileTL = new Point(position.X * _state.TileSize + (int)_state.ViewportCenter.X,
                position.Y * _state.TileSize + (int)_state.ViewportCenter.Y);
            var tileBR = new Point(position.X * _state.TileSize + (int)_state.ViewportCenter.X + _state.TileSize,
                position.Y * _state.TileSize + (int)_state.ViewportCenter.Y + _state.TileSize);

            return tileTL.X <= boxTL.X || tileTL.Y <= boxTL.Y || tileBR.X >= boxBR.X || tileBR.Y >= boxBR.Y;
        }

        private void OnClientSizeChanged(object sender, EventArgs e)
        {
            _renderTarget?.Dispose();
            try
            {
                ResetRenderTarget();
            }
            catch
            {
                Console.WriteLine("rendertarget dispose exception");
            }
        }

        private void OnContextMenuDeleteNoteClick(object sender, EventArgs e)
        {
            _desktop.HideContextMenu();
            _sessionData.DeleteNote(_state.SelectedNote);
        }

        private void OnContextMenuNewNoteClick(object sender, EventArgs e)
        {
            _desktop.HideContextMenu();
            new NoteWindow(_communicationManager, new Note()).ShowInModalWindow(_desktop,
                $" Note on {_state.HoveredTile.X}:{_state.HoveredTile.Y}");
        }

        private void OnContextMenuPingClick(object sender, EventArgs e, Tile location)
        {
            _desktop.HideContextMenu();
            _communicationManager.Ping(location).SafeFireAndForget();
        }

        private void OnContextMenuViewNoteClick(object sender, EventArgs e)
        {
            _desktop.HideContextMenu();
            EditNote(_state.SelectedNote);
        }

        private async Task OnHubDisconnected(Exception arg)
        {
            // ExceptionlessClient.Default.SubmitEvent(new Event { Message = "Hub disconnected", Type = "SignalR Client Events", Source = _settings.MachineName });
            _mainWidget.lblConnectionStatus.Text = "Disconnected";
            await Task.Yield();
        }

        private async Task OnHubReconnected(string arg)
        {
            //        ExceptionlessClient.Default.SubmitEvent(new Event { Message = "Hub reconnected", Type = "SignalR Client Events", Source = _settings.MachineName });
            _mainWidget.lblConnectionStatus.Text = "Connected";
            await Task.Yield();
        }

        private async Task OnHubReconnecting(Exception arg)
        {
            //          ExceptionlessClient.Default.SubmitEvent(new Event { Message = "Reconnecting Hub", Type = "SignalR Client Events", Source = _settings.MachineName });
            _mainWidget.lblConnectionStatus.Text = "Reconnecting";
            await Task.Yield();
        }

        private void OnOverlayButtonClicked(object sender, EventArgs e)
        {
            _state.CurrentOverlayId = ((ImageButton)sender).Id;
            _mainWidget.ClearSelection();
            ((ImageButton)sender).Border = new SolidBrush(Color.Red);
            ((ImageButton)sender).BorderThickness = new Thickness(2);
            _state.InsertMode = InsertMode.Overlay;
        }

        private void OnTileButtonTouchEntered(object sender, EventArgs e)
        {
            var mouseState = Mouse.GetState().Position;
            mouseState.X += 10;
            mouseState.Y += 10;
            _lblOverlayName.Visible = true;
            _lblOverlayName.Text = ((ImageButton)sender).Id;
            _desktop.ShowContextMenu(_lblOverlayName, mouseState);
        }

        private void OnTileButtonTouchLeft(object sender, EventArgs e)
        {
            _desktop.HideContextMenu();
            _lblOverlayName.Visible = false;
        }

        private void OnTxtOverlaySearchChange(object sender, ValueChangedEventArgs<string> e)
        {
            AddItemToToolGrid(_mainWidget.GridOverlays, OnOverlayButtonClicked, _spriteSheet, e.NewValue);
        }

        private void OnWallButtonClicked(object sender, EventArgs e)
        {
            _state.CurrentWallId = ((ImageButton)sender).Id;
            _mainWidget.ClearSelection();
            ((ImageButton)sender).Border = new SolidBrush(Color.Red);
            ((ImageButton)sender).BorderThickness = new Thickness(2);
            _state.InsertMode = InsertMode.Wall;
        }

        private void ResetRenderTarget()
        {
            _renderTarget = new RenderTarget2D(GraphicsDevice,
                GraphicsDevice.Viewport.Width,
                GraphicsDevice.Viewport.Height,
                false,
                SurfaceFormat.Color,
                DepthFormat.None,
                0,
                RenderTargetUsage.DiscardContents);
        }
    }
}