using Sandbox.Game.EntityComponents;
using Sandbox.ModAPI.Ingame;
using Sandbox.ModAPI.Interfaces;

using SpaceEngineers.Game.ModAPI.Ingame;

using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.ComponentModel;
using System.Linq;
using System.Text;

using VRage;
using VRage.Collections;
using VRage.Game;
using VRage.Game.Components;
using VRage.Game.GUI.TextPanel;
using VRage.Game.ModAPI.Ingame;
using VRage.Game.ModAPI.Ingame.Utilities;
using VRage.Game.ObjectBuilders.Definitions;
using VRage.ObjectBuilders;

using VRageMath;

namespace IngameScript
{
    partial class Program : MyGridProgram
    {
        const int NUMBER_OF_MISSILES = 52; // The quantity of components will be created to build this quantity of missiles.
        const int NUMBER_OF_CANNON_SHELL = 160;
        const int NUMBER_OF_ARTILLERY_SHELL = 40;

        const int CONSOLE_NB_LINES = 14;
        const string GRID_PREFIX = "[PML]";

        const float EPSILON = 0.05f;

        readonly IMyCubeGrid grid;

        readonly Output output;

        readonly Output topPanel;
        readonly Output bottomPanel;

        readonly IMyAssembler assembler;

        readonly List<ComponentQuantity> missileComponents = new List<ComponentQuantity>{
            new ComponentQuantity(MyItemType.MakeComponent("SteelPlate"), MyDefinitionId.Parse("MyObjectBuilder_BlueprintDefinition/SteelPlate"), 256),
            new ComponentQuantity(MyItemType.MakeComponent("Construction"), MyDefinitionId.Parse("MyObjectBuilder_BlueprintDefinition/ConstructionComponent"), 166), 
            new ComponentQuantity(MyItemType.MakeComponent("Motor"), MyDefinitionId.Parse("MyObjectBuilder_BlueprintDefinition/MotorComponent"), 18),
            new ComponentQuantity(MyItemType.MakeComponent("InteriorPlate"), MyDefinitionId.Parse("MyObjectBuilder_BlueprintDefinition/InteriorPlate"), 40),
            new ComponentQuantity(MyItemType.MakeComponent("Computer"), MyDefinitionId.Parse("MyObjectBuilder_BlueprintDefinition/ComputerComponent"), 48),
            new ComponentQuantity(MyItemType.MakeComponent("PowerCell"), MyDefinitionId.Parse("MyObjectBuilder_BlueprintDefinition/PowerCell"), 2),
            new ComponentQuantity(MyItemType.MakeComponent("Display"), MyDefinitionId.Parse("MyObjectBuilder_BlueprintDefinition/Display"), 1),
            new ComponentQuantity(MyItemType.MakeComponent("SmallTube"), MyDefinitionId.Parse("MyObjectBuilder_BlueprintDefinition/SmallTube"), 29),
            new ComponentQuantity(MyItemType.MakeComponent("MetalGrid"), MyDefinitionId.Parse("MyObjectBuilder_BlueprintDefinition/MetalGrid"), 48),
            new ComponentQuantity(MyItemType.MakeComponent("LargeTube"), MyDefinitionId.Parse("MyObjectBuilder_BlueprintDefinition/LargeTube"), 16),
            new ComponentQuantity(MyItemType.MakeComponent("Detector"), MyDefinitionId.Parse("MyObjectBuilder_BlueprintDefinition/DetectorComponent"), 8),
            new ComponentQuantity(MyItemType.MakeComponent("Girder"), MyDefinitionId.Parse("MyObjectBuilder_BlueprintDefinition/GirderComponent"), 12),
            new ComponentQuantity(MyItemType.MakeComponent("Explosives"), MyDefinitionId.Parse("MyObjectBuilder_BlueprintDefinition/ExplosivesComponent"), 24),
        };

        public Program()
        {
            this.grid = this.Me.CubeGrid;

            var output = this.Me.GetSurface(0);
            this.output = new Output(output, CONSOLE_NB_LINES);

            this.output.Print("PML system starting...");

            this.topPanel = new Output(this.GridTerminalSystem.GetBlock<IMyTextPanel>("LCD Panel - Main 02", this.grid));

            this.assembler = this.GridTerminalSystem.GetBlock<IMyAssembler>("Assembler Main", this.grid);

            Runtime.UpdateFrequency = UpdateFrequency.Update100;
        }

        public void Save()
        {
        }

        public void Main(string argument, UpdateType updateSource)
        {
            if ((updateSource & UpdateType.Update100) != 0)
            {
                this.UpdateState();
            }
            else if ((updateSource & (UpdateType.Terminal | UpdateType.Trigger)) != 0)
            {
                switch (argument)
                {
                    case "COMMAND":
                        this.output.Print("BLA...");
                        break;

                    case "START_MINING":

                    default:
                        this.output.Print($"Uknown command: {argument}");
                        break;
                }
            }
        }

        void UpdateState()
        {
            this.BuildComponentForMissiles();
            this.BuildAmmo();
            this.DisplayStatus();
        }

        void BuildComponentForMissiles()
        {
            var existingAmounts = this.GetCurrentItemQuantity(missileComponents.Select(quantity => quantity.ItemType));

            int i = 0;
            foreach (var missileComponent in this.missileComponents)
            {
                var existing = existingAmounts[i];

                var queue = new List<MyProductionItem>();
                this.assembler.GetQueue(queue);
                foreach (var item in queue)
                {
                    if (item.BlueprintId == missileComponent.blueprintId)
                        existing += item.Amount;
                }

                var desired = missileComponent.Quantity * NUMBER_OF_MISSILES;

                if (existing < desired)
                {
                    var toBuild = desired - existing;
                    this.output.Print($"Requesting {toBuild} of {missileComponent.blueprintId.SubtypeName}");
                    this.assembler.AddQueueItem(missileComponent.blueprintId, toBuild);
                }
                i += 1;
            }
        }

        void BuildAmmo()
        {
            //var ammoDefinitionIds = new List<MyDefinitionId>
            //{
            //    MyDefinitionId.Parse("MyObjectBuilder_AmmoMagazine/Missile200mm"),
            //    MyDefinitionId.Parse("MyObjectBuilder_AmmoMagazine/Missile400mm"),
            //    MyDefinitionId.Parse("MyObjectBuilder_AmmoMagazine/Missile800mm"),
            //    MyDefinitionId.Parse("MyObjectBuilder_AmmoMagazine/Missile1200mm"),
            //    MyDefinitionId.Parse("MyObjectBuilder_AmmoMagazine/Projectile200mm"),
            //    MyDefinitionId.Parse("MyObjectBuilder_AmmoMagazine/Projectile400mm"),
            //    MyDefinitionId.Parse("MyObjectBuilder_AmmoMagazine/Projectile800mm"),
            //    MyDefinitionId.Parse("MyObjectBuilder_AmmoMagazine/Projectile1200mm"),
            //    MyDefinitionId.Parse("MyObjectBuilder_AmmoMagazine/ArtilleryShell"),
            //};
            //var existingAmounts = this.GetCurrentItemQuantity(ammoDefinitionIds);
            //var desiredAmounts = new List<MyFixedPoint>
            //{
            //    (MyFixedPoint)(NUMBER_OF_MISSILES * 4), // Missile200mm
            //    (MyFixedPoint)(NUMBER_OF_MISSILES * 4), // Missile400mm
            //    (MyFixedPoint)(NUMBER_OF_MISSILES * 2), // Missile800mm
            //    (MyFixedPoint)(NUMBER_OF_MISSILES * 2), // Missile1200mm
            //    (MyFixedPoint)(NUMBER_OF_CANNON_SHELL / 4), // Projectile200mm
            //    (MyFixedPoint)(NUMBER_OF_CANNON_SHELL / 4), // Projectile400mm
            //    (MyFixedPoint)(NUMBER_OF_CANNON_SHELL / 4), // Projectile800mm
            //    (MyFixedPoint)(NUMBER_OF_CANNON_SHELL / 4), // Projectile1200mm
            //    (MyFixedPoint)(NUMBER_OF_ARTILLERY_SHELL), // ArtilleryShell
            //};
            //foreach (var tuple in ammoDefinitionIds.Zip(existingAmounts, desiredAmounts, (id, existing, desired) => (id, existing, desired)))
            //{
            //    if (tuple.existing < tuple.desired - EPSILON)
            //    {
            //        var toBuild = tuple.desired - tuple.existing;
            //        this.assembler.AddQueueItem(tuple.id, toBuild);
            //        this.output.Print($"Requesting {toBuild} of {tuple.id.SubtypeName}");
            //    }
            //}
        }

        void DisplayStatus()
        {
            var cargoSpace = this.GetCargoSpace();
            var energyState = this.GetEnergyState();
            var hydrogenVolume = this.GetHydrogenVolume();
            var oxygenVolume = this.GetOxygenVolume();

            this.topPanel.Display(
                $@"
{cargoSpace.ToString("Cargo volume", "kL")}

{energyState.ToString("Energy", "MWh")}

{hydrogenVolume.ToString("Hydrogen", "L")}

{oxygenVolume.ToString("Oxygen", "L")}"
            );
        }

        List<MyFixedPoint> GetCurrentItemQuantity(IEnumerable<MyItemType> itemTypes)
        {
            var total = new List<MyFixedPoint>(new MyFixedPoint[itemTypes.Count()]);

            foreach (var container in this.GridTerminalSystem.GetBlocks<IMyCargoContainer>(null, this.grid))
            {
                //this.output.Print($"Container: {container.CustomName}");
                int i = 0;
                foreach (var itemType in itemTypes)
                {
                    var items = container.GetInventory().FindItem(itemType);
                    //this.output.Print($"{itemType}: {items}");
                    if (items.HasValue)
                        total[i] += items.Value.Amount;
                    i += 1;
                }
            }

            foreach (var assembler in this.GridTerminalSystem.GetBlocks<IMyAssembler>(null, this.grid))
            {
                int i = 0;
                foreach (var itemType in itemTypes)
                {
                    var items = assembler.OutputInventory.FindItem(itemType);
                    if (items.HasValue)
                        total[i] += items.Value.Amount;
                    i += 1;
                }
            }

            foreach (var sorter in this.GridTerminalSystem.GetBlocks<IMyConveyorSorter>(null, this.grid))
            {
                int i = 0;
                foreach (var itemType in itemTypes)
                {
                    var items = sorter.GetInventory().FindItem(itemType);
                    if (items.HasValue)
                        total[i] += items.Value.Amount;
                    i += 1;
                }
            }

            foreach (var welder in this.GridTerminalSystem.GetBlocks<IMyShipWelder>(null, this.grid))
            {
                int i = 0;
                foreach (var itemType in itemTypes)
                {
                    var items = welder.GetInventory().FindItem(itemType);
                    if (items.HasValue)
                        total[i] += items.Value.Amount;
                    i += 1;
                }
            }

            foreach (var turret in this.GridTerminalSystem.GetBlocks<IMyLargeTurretBase>(null, this.grid))
            {
                int i = 0;
                foreach (var itemType in itemTypes)
                {
                    var items = turret.GetInventory().FindItem(itemType);
                    if (items.HasValue)
                        total[i] += items.Value.Amount;
                    i += 1;
                }
            }

            return total;
        }

        class ComponentQuantity
        {
            public ComponentQuantity(MyItemType itemType, MyDefinitionId blueprintId, MyFixedPoint quantity)
            {
                this.ItemType = itemType;
                this.blueprintId = blueprintId;

                //if (!MyDefinitionId.TryParse("MyObjectBuilder_BlueprintDefinition", this.ItemType.SubtypeId, out this.Id))
                //    throw new ArgumentException($"Cannot parse {this.ItemType} into a MyDefinitionId");

                this.Quantity = quantity;
            }

            public readonly MyItemType ItemType;
            public readonly MyDefinitionId blueprintId;
            public readonly MyFixedPoint Quantity;
        }

        struct Quantity
        {
            public Quantity(double current, double total)
            {
                this.Current = current;
                this.Total = total;
            }

            public readonly double Current;
            public readonly double Total;

            public string ToString(string name, string unit)
            {
                var firstLineLength = 50;
                var secondLineLength = 50;

                var firstLine = new StringBuilder();
                firstLine.Append(name).Append(": ");
                var values = $"{this.Current:N1} {unit} / {this.Total:N1} {unit}";
                firstLine.Append(values.PadLeft(firstLineLength - firstLine.Length));

                var secondLine = new StringBuilder();
                secondLine.Append("[");
                var ratio = this.Current / this.Total;
                var percentStr = $"{ratio * 100.0,8:N1}%";
                // "-2" because of the starting and ending characters: '[', ']'.
                var gaugeSize = secondLineLength - percentStr.Length - 2;
                var n = (int)((double)gaugeSize * ratio);
                secondLine.Append(new string('|', n));
                secondLine.Append(new string(' ', gaugeSize - n)).Append("]");
                secondLine.Append(percentStr);

                return $"{firstLine}\n{secondLine}";
            }
        }

        Quantity GetCargoSpace()
        {
            var containers = this.GridTerminalSystem.GetBlocks<IMyCargoContainer>(null, this.grid);

            double currentVolume = 0;
            double totalVolume = 0;

            foreach (var container in containers)
            {
                var inventory = container.GetInventory();
                currentVolume += (double)inventory.CurrentVolume;
                totalVolume += (double)inventory.MaxVolume;
            }

            return new Quantity(currentVolume, totalVolume);
        }

        Quantity GetEnergyState()
        {
            double currentkWh = 0;
            double maxkWh = 0;
            var batteries = this.GridTerminalSystem.GetBlocks<IMyBatteryBlock>(null, this.grid);

            foreach (var battery in batteries)
            {
                currentkWh += (double)battery.CurrentStoredPower;
                maxkWh += (double)battery.MaxStoredPower;
            }

            return new Quantity(currentkWh, maxkWh);
        }

        Quantity GetHydrogenVolume()
        {
            double currentVolume = 0;
            double totalVolume = 0;
            var tanks = this.GridTerminalSystem.GetBlocks<IMyGasTank>(null, this.grid, tank => tank.CustomName.Contains("Hydrogen"));

            //var tanks = new List<IMyGasTank>();
            //GridTerminalSystem.GetBlocksOfType<IMyGasTank>(tanks,
            //    (IMyGasTank tank) =>
            //    {
            //        return tank.CustomName.Contains("Mammouth") && tank.CustomName.Contains("hydro");
            //    }
            //);

            foreach (var tank in tanks)
            {
                currentVolume += (double)tank.Capacity * (double)tank.FilledRatio;
                totalVolume += (double)tank.Capacity;
            }

            return new Quantity(currentVolume, totalVolume);
        }

        Quantity GetOxygenVolume()
        {
            double currentVolume = 0;
            double totalVolume = 0;
            var tanks = this.GridTerminalSystem.GetBlocks<IMyGasTank>(null, this.grid, tank => tank.CustomName.Contains("Oxygen"));
            //var tanks = new List<IMyGasTank>();
            //GridTerminalSystem.GetBlocksOfType<IMyGasTank>(tanks,
            //    (IMyGasTank tank) =>
            //    {
            //        return tank.CustomName.Contains("Mammouth") && tank.CustomName.Contains("oxygène");
            //    }
            //);

            foreach (var tank in tanks)
            {
                currentVolume += (double)tank.Capacity * (double)tank.FilledRatio;
                totalVolume += (double)tank.Capacity;
            }

            return new Quantity(currentVolume, totalVolume);
        }
    }
}

