using System.Numerics;
using Windows.Foundation;
using Windows.Storage;
using Windows.Storage.Pickers;
using Windows.UI;
using Microsoft.Graphics.Canvas;
using Microsoft.Graphics.Canvas.Text;
using Microsoft.Graphics.Canvas.UI;
using Microsoft.Graphics.Canvas.UI.Xaml;
using Microsoft.UI;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Input;
using ReactorMaintenance.Simulation;
using System.Globalization;
using Windows.UI.Popups;
using WinRT.Interop;

namespace ReactorMaintenance.Win2D;

public sealed partial class MainWindow
{
    private sealed record CanvasLayout(double CellSize, double OriginX, double OriginY)
    {
        public Rect CellRect(int x, int y)
        {
            return new(OriginX + x * CellSize, OriginY + y * CellSize, CellSize, CellSize);
        }

        public Rect DualTileRect(int x, int y)
        {
            return new(OriginX + (x - 0.5) * CellSize, OriginY + (y - 0.5) * CellSize, CellSize, CellSize);
        }
    }

    public MainWindow()
    {
        InitializeComponent();

        m_Level = BuildStarterLevel();
        ToolPicker.ItemsSource = Enum.GetValues<EEditorTool>();
        ToolPicker.SelectedItem = m_SelectedTool;
        RefreshInspector();
    }

    private void LevelCanvas_CreateResources(CanvasControl sender, CanvasCreateResourcesEventArgs args)
    {
        args.TrackAsyncAction(LoadTilemapAsync(sender).AsAsyncAction());
    }

    private async Task LoadTilemapAsync(CanvasControl sender)
    {
        var tilemapPath = Path.Combine(AppContext.BaseDirectory, "Images", "tilemap.png");
        m_TerrainTilemap = await CanvasBitmap.LoadAsync(sender, tilemapPath);
    }

    private void ToolPicker_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        if (ToolPicker.SelectedItem is EEditorTool tool)
            m_SelectedTool = tool;
    }

    private void New_Click(object sender, RoutedEventArgs e)
    {
        m_Level = BuildStarterLevel();
        m_CurrentFile = null;
        m_SelectedCell = null;
        RefreshInspector();
        LevelCanvas.Invalidate();
    }

    private async void Open_Click(object sender, RoutedEventArgs args)
    {
        try
        {
            var picker = new FileOpenPicker();
            InitializeWithWindow.Initialize(picker, WindowNative.GetWindowHandle(this));
            picker.FileTypeFilter.Add(".json");

            var file = await picker.PickSingleFileAsync();
            if (file is null)
                return;

            var json = await FileIO.ReadTextAsync(file);
            m_Level = LevelSerializer.Deserialize(json);
            m_Level = m_Level with { Forecasts = m_Simulation.Forecast(m_Level) };
            m_CurrentFile = file;
            m_SelectedCell = null;
            RefreshInspector();
            LevelCanvas.Invalidate();
        }
        catch (Exception e)
        {
            var messageDialog = new MessageDialog(e.Message);
            _ = await messageDialog.ShowAsync();
        }
    }

    private async void Save_Click(object sender, RoutedEventArgs args)
    {
        try
        {
            var file = m_CurrentFile;
            if (file is null)
            {
                var picker = new FileSavePicker();
                InitializeWithWindow.Initialize(picker, WindowNative.GetWindowHandle(this));
                picker.SuggestedFileName = m_Level.Name.Replace(' ', '-').ToLowerInvariant();
                picker.FileTypeChoices.Add("Reactor level", [".json"]);
                file = await picker.PickSaveFileAsync();
            }

            if (file is null)
                return;

            await FileIO.WriteTextAsync(file, LevelSerializer.Serialize(m_Level));
            m_CurrentFile = file;
        }
        catch (Exception e)
        {
            var messageDialog = new MessageDialog(e.Message);
            _ = await messageDialog.ShowAsync();
        }
    }

    private void Simulate_Click(object sender, RoutedEventArgs e)
    {
        m_Level = m_Simulation.AdvanceTurn(m_Level);
        RefreshInspector();
        LevelCanvas.Invalidate();
    }

    private void Activate_Click(object sender, RoutedEventArgs e)
    {
        m_Level = m_Simulation.ActivateReactor(m_Level);
        RefreshInspector();
        LevelCanvas.Invalidate();
    }

    private void LevelCanvas_PointerPressed(object sender, PointerRoutedEventArgs e)
    {
        m_Painting = true;
        _ = LevelCanvas.CapturePointer(e.Pointer);
        PaintAt(e.GetCurrentPoint(LevelCanvas).Position);
    }

    private void LevelCanvas_PointerMoved(object sender, PointerRoutedEventArgs e)
    {
        if (m_Painting)
            PaintAt(e.GetCurrentPoint(LevelCanvas).Position);
    }

    private void LevelCanvas_PointerReleased(object sender, PointerRoutedEventArgs e)
    {
        m_Painting = false;
        LevelCanvas.ReleasePointerCapture(e.Pointer);
    }

    private void PaintAt(Point point)
    {
        if (!TryGetGridPosition(point, out var position))
            return;

        m_SelectedCell = position;
        m_Level = LevelEditor.Apply(m_Level, position, m_SelectedTool);
        m_Level = m_Level with { Forecasts = m_Simulation.Forecast(m_Level) };
        RefreshInspector();
        LevelCanvas.Invalidate();
    }

    private void LevelCanvas_Draw(CanvasControl sender, CanvasDrawEventArgs args)
    {
        var drawing = args.DrawingSession;
        var layout = GetLayout();

        drawing.Clear(ColorHelper.FromArgb(255, 16, 18, 21));
        DrawTerrain(drawing, layout);
        DrawCellOverlays(drawing, layout);
        DrawGrid(drawing, layout);
        DrawRobot(drawing, layout);
    }

    private void DrawTerrain(CanvasDrawingSession drawing, CanvasLayout layout)
    {
        for (var y = 0; y <= m_Level.Height; y++)
        for (var x = 0; x <= m_Level.Width; x++)
            DrawDualTerrainTile(drawing, layout.DualTileRect(x, y), GetDualTileMask(x, y));
    }

    private void DrawCellOverlays(CanvasDrawingSession drawing, CanvasLayout layout)
    {
        for (var y = 0; y < m_Level.Height; y++)
        for (var x = 0; x < m_Level.Width; x++)
        {
            var position = new GridPosition(x, y);
            var cell = m_Level.GetCell(position);
            var rect = layout.CellRect(x, y);

            if (cell.HasPipe)
            {
                var center = new Vector2((float)(rect.X + rect.Width / 2), (float)(rect.Y + rect.Height / 2));
                var pipeColor = cell.Pipe switch {
                    EPipeMedium.Coolant => Colors.DeepSkyBlue,
                    EPipeMedium.Fuel => Colors.Goldenrod,
                    EPipeMedium.Pressure => Colors.LightSteelBlue,
                    _ => Colors.Transparent
                };
                drawing.DrawLine(center with { X = (float)rect.X + 6 }, center with { X = (float)(rect.X + rect.Width - 6) }, pipeColor, Math.Max(3, (float)rect.Width / 7));
                drawing.DrawLine(center with { Y = (float)rect.Y + 6 }, center with { Y = (float)(rect.Y + rect.Height - 6) }, pipeColor, Math.Max(3, (float)rect.Width / 7));
            }

            if (cell.LeakRate > 0)
                drawing.DrawCircle(new((float)(rect.X + rect.Width - 10), (float)(rect.Y + 10)), 5, Colors.OrangeRed, 2);

            if (cell.Hazards.Fire)
                drawing.FillCircle(new((float)(rect.X + rect.Width * 0.5), (float)(rect.Y + rect.Height * 0.5)), (float)rect.Width * 0.24f, Colors.OrangeRed);

            if (m_SelectedCell == position)
                drawing.DrawRectangle(rect, Colors.White, 3);

            DrawCellProp(drawing, cell, rect);
        }
    }

    private void DrawDualTerrainTile(CanvasDrawingSession drawing, Rect rect, int floorMask)
    {
        if (m_TerrainTilemap is null)
            return;

        var wallMask = c_AllCorners ^ floorMask;
        var sourceRect = TilemapSourceRect(wallMask);
        drawing.DrawImage(m_TerrainTilemap, rect, sourceRect);
    }

    private static Rect TilemapSourceRect(int wallMask)
    {
        var tilePosition = wallMask switch {
            c_BottomLeftCorner => new GridPosition(0, 0),
            c_TopRightCorner | c_BottomRightCorner => new GridPosition(1, 0),
            c_TopLeftCorner | c_BottomLeftCorner | c_BottomRightCorner => new GridPosition(2, 0),
            c_BottomLeftCorner | c_BottomRightCorner => new GridPosition(3, 0),
            c_TopLeftCorner | c_BottomRightCorner => new GridPosition(0, 1),
            c_BottomLeftCorner | c_TopRightCorner | c_BottomRightCorner => new GridPosition(1, 1),
            c_AllCorners => new GridPosition(2, 1),
            c_TopLeftCorner | c_BottomLeftCorner | c_TopRightCorner => new GridPosition(3, 1),
            c_TopRightCorner => new GridPosition(0, 2),
            c_TopLeftCorner | c_TopRightCorner => new GridPosition(1, 2),
            c_TopLeftCorner | c_TopRightCorner | c_BottomRightCorner => new GridPosition(2, 2),
            c_BottomLeftCorner | c_TopLeftCorner => new GridPosition(3, 2),
            0 => new GridPosition(0, 3),
            c_BottomRightCorner => new GridPosition(1, 3),
            c_BottomLeftCorner | c_TopRightCorner => new GridPosition(2, 3),
            c_TopLeftCorner => new GridPosition(3, 3),
            _ => throw new ArgumentOutOfRangeException(nameof(wallMask), wallMask, "Unsupported tile mask.")
        };

        return new(
            tilePosition.X * c_TilemapTileSize,
            tilePosition.Y * c_TilemapTileSize,
            c_TilemapTileSize,
            c_TilemapTileSize);
    }

    private int GetDualTileMask(int x, int y)
    {
        var mask = 0;
        if (GetTerrainOrWall(x - 1, y - 1) == ECellTerrain.Floor)
            mask |= c_TopLeftCorner;

        if (GetTerrainOrWall(x, y - 1) == ECellTerrain.Floor)
            mask |= c_TopRightCorner;

        if (GetTerrainOrWall(x - 1, y) == ECellTerrain.Floor)
            mask |= c_BottomLeftCorner;

        if (GetTerrainOrWall(x, y) == ECellTerrain.Floor)
            mask |= c_BottomRightCorner;

        return mask;
    }

    private ECellTerrain GetTerrainOrWall(int x, int y)
    {
        var position = new GridPosition(x, y);
        return m_Level.InBounds(position) ? m_Level.GetCell(position).Terrain : ECellTerrain.Wall;
    }

    private void DrawCellProp(CanvasDrawingSession drawing, CellState cell, Rect rect)
    {
        var text = cell.Prop switch {
            ECellProp.Reactor => "R",
            ECellProp.CoolingPump => "C",
            ECellProp.Generator => "G",
            ECellProp.PressureRegulator => "P",
            ECellProp.DiagnosticTerminal => "D",
            ECellProp.ControlTerminal => "T",
            _ => string.Empty
        };

        if (string.IsNullOrEmpty(text))
            return;

        var propRect = new Rect(rect.X + rect.Width * 0.18, rect.Y + rect.Height * 0.18, rect.Width * 0.64, rect.Height * 0.64);
        drawing.FillRoundedRectangle(propRect, 4, 4, PropColor(cell.Prop));
        drawing.DrawRoundedRectangle(propRect, 4, 4, ColorHelper.FromArgb(210, 12, 14, 16), 2);

        using var format = new CanvasTextFormat();
        format.FontSize = Math.Max(14, (float)rect.Width * 0.34f);
        format.HorizontalAlignment = CanvasHorizontalAlignment.Center;
        format.VerticalAlignment = CanvasVerticalAlignment.Center;

        drawing.DrawText(text, propRect, Colors.White, format);
    }

    private void DrawGrid(CanvasDrawingSession drawing, CanvasLayout layout)
    {
        for (var x = 0; x <= m_Level.Width; x++)
        {
            var xPos = (float)(layout.OriginX + x * layout.CellSize);
            drawing.DrawLine(xPos, (float)layout.OriginY, xPos, (float)(layout.OriginY + m_Level.Height * layout.CellSize), ColorHelper.FromArgb(120, 91, 104, 115), 1);
        }

        for (var y = 0; y <= m_Level.Height; y++)
        {
            var yPos = (float)(layout.OriginY + y * layout.CellSize);
            drawing.DrawLine((float)layout.OriginX, yPos, (float)(layout.OriginX + m_Level.Width * layout.CellSize), yPos, ColorHelper.FromArgb(120, 91, 104, 115), 1);
        }
    }

    private void DrawRobot(CanvasDrawingSession drawing, CanvasLayout layout)
    {
        var rect = layout.CellRect(m_Level.Robot.X, m_Level.Robot.Y);
        var center = new Vector2((float)(rect.X + rect.Width / 2), (float)(rect.Y + rect.Height / 2));
        drawing.FillCircle(center, (float)rect.Width * 0.28f, Colors.White);
        drawing.DrawCircle(center, (float)rect.Width * 0.28f, Colors.Black, 2);
    }

    private bool TryGetGridPosition(Point point, out GridPosition position)
    {
        var layout = GetLayout();
        var x = (int)((point.X - layout.OriginX) / layout.CellSize);
        var y = (int)((point.Y - layout.OriginY) / layout.CellSize);
        position = new(x, y);
        return m_Level.InBounds(position);
    }

    private CanvasLayout GetLayout()
    {
        var availableWidth = Math.Max(1, LevelCanvas.ActualWidth);
        var availableHeight = Math.Max(1, LevelCanvas.ActualHeight);
        var size = Math.Floor(Math.Min(availableWidth / m_Level.Width, availableHeight / m_Level.Height));
        size = Math.Max(20, size);
        var originX = Math.Max(0, (availableWidth - size * m_Level.Width) / 2);
        var originY = Math.Max(0, (availableHeight - size * m_Level.Height) / 2);
        return new(size, originX, originY);
    }

    private static Color PropColor(ECellProp prop)
    {
        return prop switch {
            ECellProp.Reactor => ColorHelper.FromArgb(255, 61, 76, 82),
            ECellProp.CoolingPump => ColorHelper.FromArgb(255, 25, 79, 96),
            ECellProp.Generator => ColorHelper.FromArgb(255, 86, 75, 35),
            ECellProp.PressureRegulator => ColorHelper.FromArgb(255, 70, 78, 98),
            ECellProp.DiagnosticTerminal => ColorHelper.FromArgb(255, 39, 84, 62),
            ECellProp.ControlTerminal => ColorHelper.FromArgb(255, 80, 61, 91),
            _ => Colors.Transparent
        };
    }

    private void RefreshInspector()
    {
        LevelNameText.Text = m_Level.Name;
        TurnText.Text = m_Level.Global.Turn.ToString(CultureInfo.InvariantCulture);
        StatusText.Text = m_Level.Global.Status;
        GlobalText.Text = $"Power: {m_Level.Global.Power}/10\n" + $"Cooling: {m_Level.Global.Cooling}/10\n" + $"Core Heat: {m_Level.Global.CoreHeat}/10\n" + $"Facility Stability: {m_Level.Global.FacilityStability}/10";

        if (m_SelectedCell is { } position && m_Level.InBounds(position))
        {
            var cell = m_Level.GetCell(position);
            CellText.Text = $"Position: {position.X},{position.Y}\n" + $"Terrain: {cell.Terrain}\n" + $"Prop: {cell.Prop}\n" + $"Pipe: {cell.Pipe}\n" + $"Flow: {cell.Flow}, Pressure: {cell.Pressure}\n" + $"Integrity: {cell.Integrity}, Leak: {cell.LeakRate}\n" + $"Heat: {cell.Hazards.Heat}, Smoke: {cell.Hazards.Smoke}\n" + $"Fuel Vapor: {cell.Hazards.FuelVapor}, Fuel: {cell.Hazards.LiquidFuel}\n" + $"Coolant: {cell.Hazards.CoolantPooling}, Charge: {cell.Hazards.ElectricalCharge}";
        }
        else
            CellText.Text = "No cell selected.";

        ForecastList.ItemsSource = m_Level.Forecasts;
    }

    private static LevelState BuildStarterLevel()
    {
        var level = LevelState.Create("Cooling Sector B", 16, 12);
        level = level.SetCell(new(3, 5), new() {
            Prop = ECellProp.CoolingPump,
            Pipe = EPipeMedium.Coolant,
            Flow = 5,
            Pressure = 5,
            Powered = true
        });
        level = level.SetCell(new(4, 5), new() {
            Pipe = EPipeMedium.Coolant,
            Flow = 5,
            Pressure = 7
        });
        level = level.SetCell(new(5, 5), new() {
            Pipe = EPipeMedium.Coolant,
            Flow = 3,
            Pressure = 8,
            LeakRate = 2,
            Integrity = 4
        });
        level = level.SetCell(new(6, 5), new() {
            Pipe = EPipeMedium.Coolant,
            Flow = 3,
            Pressure = 7
        });
        level = level.SetCell(new(8, 5), new() {
            Prop = ECellProp.Reactor,
            Hazards = new() {
                Heat = 6,
                Stability = 8
            }
        });
        level = level.SetCell(new(2, 8), new() {
            Prop = ECellProp.Generator,
            Pipe = EPipeMedium.Fuel,
            Flow = 4,
            Pressure = 6,
            Powered = true
        });
        level = level.SetCell(new(11, 4), new() {
            Prop = ECellProp.DiagnosticTerminal,
            Powered = true
        });
        level = level.SetCell(new(12, 8), new() {
            Prop = ECellProp.ControlTerminal,
            Powered = true
        });
        return level with { Forecasts = new SimulationEngine().Forecast(level) };
    }

    private readonly SimulationEngine m_Simulation = new();
    private const int c_TilemapTileSize = 512;
    private const int c_TopLeftCorner = 1;
    private const int c_TopRightCorner = 2;
    private const int c_BottomLeftCorner = 4;
    private const int c_BottomRightCorner = 8;
    private const int c_AllCorners = c_TopLeftCorner | c_TopRightCorner | c_BottomLeftCorner | c_BottomRightCorner;
    private CanvasBitmap? m_TerrainTilemap;
    private StorageFile? m_CurrentFile;
    private LevelState m_Level;
    private bool m_Painting;
    private GridPosition? m_SelectedCell;
    private EEditorTool m_SelectedTool = EEditorTool.Floor;
}