using Sandbox.Game.Entities.Cube;
using Sandbox.Game.EntityComponents;
using Sandbox.Game.GameSystems;
using Sandbox.ModAPI.Ingame;
using Sandbox.ModAPI.Interfaces;

using SpaceEngineers.Game.Entities.Blocks;
using SpaceEngineers.Game.ModAPI.Ingame;

using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Immutable;
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.GameServices;

using VRageMath;

namespace IngameScript
{
    class Missile
    {
        public IMyGasTank Tank { get; }
        public IMyShipConnector Connector { get; }
        public IMyShipMergeBlock MergeBlock { get; }
        public IMyProgrammableBlock ProgrammableBlock { get; }

        public Missile(IMyGasTank tank, IMyShipConnector connector, IMyShipMergeBlock mergeBlock, IMyProgrammableBlock programmableBlock)
        {
            this.Tank = tank;
            this.Connector = connector;
            this.MergeBlock = mergeBlock;
            this.ProgrammableBlock = programmableBlock;
        }
    }

    class Launcher
    {
        enum State
        {
            NOMINAL,
            STARTING_SEQUENCE,
            FILLING_TANK,
            LAUNCHING,
        }

        readonly int number;
        readonly Output output;

        readonly IMyGridTerminalSystem gridTerminal;
        readonly IMyShipConnector connector;

        Missile missile;
        State currentState = State.NOMINAL;

        /// <summary>
        ///
        /// </summary>
        /// <param name="number"></param>
        /// <param name="gridTerminal"></param>
        /// <param name="grid"></param>
        /// <param name="output">To output some text</param>
        /// <param name="connector">Connector of the launcher (not the missile)</param>
        public Launcher(int number, IMyGridTerminalSystem gridTerminal, Output output, IMyShipConnector connector)
        {
            this.number = number;
            this.gridTerminal = gridTerminal;
            this.output = output;
            this.connector = connector;
        }

        public void Launch()
        {
            if (this.currentState == State.NOMINAL)
                this.currentState = State.STARTING_SEQUENCE;
        }

        void Print(string message)
        {
            this.output.Print(String.Format("{0:00}: {1}", this.number, message));
        }

        public void UpdateState()
        {
            switch (this.currentState)
            {
                case State.STARTING_SEQUENCE:
                    var missileConnector = this.connector.OtherConnector;
                    if (missileConnector == null)
                    {
                        this.Print("Cannot find the missile connector");
                        break;
                    }

                    var missileGrid = missileConnector.CubeGrid;
                    if (missileGrid == null)
                    { 
                        this.Print("Cannot find the missile grid");
                        break;
                    }

                    IMyGasTank tank = this.gridTerminal.GetBlock<IMyGasTank>("[PM] Hydrogen Tank", missileGrid);
                    if (tank == null)
                    {
                        this.Print("Cannot find the missile hydrogen tank");
                        break;
                    }

                    IMyShipMergeBlock mergeBlock = this.gridTerminal.GetBlock<IMyShipMergeBlock>("[PM] Merge Block", missileGrid);
                    if (mergeBlock == null)
                    {
                        this.Print("Cannot find the missile merge block");
                        break;
                    }

                    IMyProgrammableBlock programmableBlock = this.gridTerminal.GetBlock<IMyProgrammableBlock>("[PM] Programmable Block", missileGrid);
                    if (programmableBlock == null)
                    {
                        this.Print("Cannot find the missile programmable block");
                        break;
                    }

                    IEnumerable<IMyThrust> thrusters = this.gridTerminal.GetBlocksFromGroup<IMyThrust>("[PM] Thrusters", missileGrid);
                    if (thrusters.Count() == 0)
                    {
                        this.Print("Cannot find missile thrusters");
                        break;
                    }

                    // When launched the missile will enabled itself its thrusters.
                    foreach (var thruster in thrusters)
                        thruster.Enabled = false;

                    tank.Stockpile = true;
                    connector.Connect();

                    this.missile = new Missile(tank, connector, mergeBlock, programmableBlock);

                    this.currentState = State.FILLING_TANK;
                    break;

                case State.FILLING_TANK:
                    this.Print($"Waiting missile tank filled... ({this.missile.Tank.FilledRatio * 100:.0}%)");

                    if (this.missile.Tank.FilledRatio >= Program.HYDRO_TANK_FILLED_PERCENT / 100)
                    {
                        this.missile.Tank.Stockpile = false;
                        this.currentState = State.LAUNCHING;
                    }
                    break;

                case State.LAUNCHING:
                    this.Print("Launching missile...");

                    if (this.missile.ProgrammableBlock.TryRun("START"))
                        this.Print("Missile launched!");
                    else
                        this.Print("ERROR: Can't send START command to missile");

                    this.missile.MergeBlock.Enabled = false;
                    this.connector.Disconnect();
                    this.currentState = State.NOMINAL;
                    this.missile = null;

                    break;

                case State.NOMINAL:
                    break; // Nothing;
            }
        }
    }

    partial class Program : MyGridProgram
    {
        enum State
        {
            NOMINAL,
            LAUNCHING_ONE,
            LAUNCHING_ALL,
            LAUNCHING_CONTINUOUS,
        }

        public const string GRID_PREFIX = "[PML]";
        public const string MISSILE_GRID_PREFIX = "[PM]";
        public const double HYDRO_TANK_FILLED_PERCENT = 40;

        const string LAUNCHER_SMALL_CONNECTOR_NAME = "Connector Launcher"; // Following by a number: "01", "02", etc.
        const string DOORS_MISSILES_GROUP = "Doors Missiles";

        const float EPSILON = 0.05f;

        readonly Output output;

        readonly List<Launcher> launchers = new List<Launcher>();
        readonly IEnumerable<IMyDoor> missilesDoors;

        int nbLaunched = 0;
        int nextToLaunch = 0;

        State currentState = State.NOMINAL;

        public Program()
        {
            var output = this.Me.GetSurface(0);
            this.output = new Output(output, 14);
            this.output.Print("Missile launcher system starting...");

            this.missilesDoors = Utils.GetBlocksFromGroup<IMyDoor>(this.GridTerminalSystem, String.Format("{0} {1}", GRID_PREFIX, DOORS_MISSILES_GROUP));
            if (missilesDoors.Count() == 0)
            {
                this.output.Print("No missile doors found: Aborted");
                return;
            }

            var connectorNamePrefix = String.Format("{0} {1} ", GRID_PREFIX, LAUNCHER_SMALL_CONNECTOR_NAME);

            // Find all launcher sub-grid and create the associated launcher.
            var launcherConnectors = new List<IMyShipConnector>();
            this.GridTerminalSystem.GetBlocksOfType(
                launcherConnectors,
                (IMyShipConnector connector) => connector.CustomName.StartsWith(connectorNamePrefix)
            );

            foreach (var connector in launcherConnectors)
            {
                var n = int.Parse(connector.CustomName.Substring(connectorNamePrefix.Length));
                this.launchers.Add(new Launcher(n, this.GridTerminalSystem, this.output, connector));
            }

            this.Runtime.UpdateFrequency = UpdateFrequency.Update10;

            this.output.Print($"Missile launcher system started ({this.launchers.Count} launcher(s))");
        }

        void LaunchNext()
        {
            // Check if the hangar door is open.
            foreach (var missileDoor in this.missilesDoors)
            {
                if (missileDoor.Status != DoorStatus.Open)
                {
                    this.output.Print("Can't launch when missile doors are closed: Aborted");
                    return;
                }
            }

            if (this.launchers.Count > 0)
            {
                this.launchers[this.nextToLaunch].Launch();
                this.nextToLaunch = (this.nextToLaunch + 1) % this.launchers.Count;
                this.nbLaunched += 1;
            }
        }

        void ResetToNominal()
        {
            this.nbLaunched = 0;
            this.currentState = State.NOMINAL;
        }

        void UpdateState()
        {
            switch (this.currentState)
            {
                case State.LAUNCHING_ONE:
                    this.LaunchNext();
                    this.ResetToNominal();
                    break;

                case State.LAUNCHING_ALL:
                    if (this.nbLaunched >= launchers.Count)
                    {
                        this.ResetToNominal();
                    }
                    else
                    {
                        this.LaunchNext();
                    }
                    break;

                case State.LAUNCHING_CONTINUOUS:
                    this.LaunchNext();
                    break;
            }

            foreach (var launcher in this.launchers)
                launcher.UpdateState();
        }

        public void Save()
        {
        }

        public void Main(string argument, UpdateType updateSource)
        {
            if ((updateSource & UpdateType.Update10) != 0)
            {
                this.UpdateState();
            }
            else if ((updateSource & (UpdateType.Terminal | UpdateType.Trigger)) != 0)
            {
                switch (argument)
                {
                    case "LAUNCH ONE":
                        this.output.Print("Launching one missile...");
                        this.currentState = State.LAUNCHING_ONE;
                        break;

                    case "LAUNCH ALL":
                        this.output.Print("Launching all missiles...");
                        this.currentState = State.LAUNCHING_ALL;
                        break;

                    case "LAUNCH CONTINUOUS":
                        this.output.Print("Launching missiles in continous...");
                        this.currentState = State.LAUNCHING_CONTINUOUS;
                        break;

                    case "STOP":
                        this.output.Print("Stopping lauching...");
                        this.ResetToNominal();
                        break;

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