using Sandbox.ModAPI.Ingame;
using SpaceEngineers.Game.ModAPI.Ingame;
using System;
using System.Collections.Generic;
using VRage.Game.ModAPI.Ingame.Utilities;

namespace IngameScript
{
    public partial class Program : MyGridProgram, IConsoleProgram
    {
        public MyIni Ini { get; } = new MyIni();
        public IConsole Console { get; private set; }

        private MyCommandLine _cli = new MyCommandLine();
        private List<IEnumerator<bool>> _jobs = new List<IEnumerator<bool>>();
        private Dictionary<string, ActionGroup> _actionGroups = new Dictionary<string, ActionGroup>();

        public Program()
        {
            Console = new MainConsole(this, "Action Sequencer");

            // initialize all the sequencers
            List<IMyTerminalBlock> blocks = new List<IMyTerminalBlock>();
            GridTerminalSystem.GetBlocksOfType(blocks, blockFilter);

            foreach (IMyTerminalBlock block in blocks)
            {
                Ini.TryParse(block.CustomData);

                string id = Ini.Get("sequencer", "groupName").ToString().Trim();
                if (id == "")
                {
                    Console.Print($"No groupName found for '{block.CustomName}'. Skipping.");
                    continue;
                }

                string actionNames = Ini.Get("sequencer", "actions").ToString().Trim(); ;
                if (actionNames == "")
                {
                    Console.Print($"No actions defined for '{block.CustomName}'. Skipping.");
                    continue;
                }
                if (!_actionGroups.ContainsKey(id)) _actionGroups[id] = new ActionGroup(Console, id);

                // Find the custom section for each action and parse the block into it.
                foreach (string actionName in actionNames.Split(','))
                {
                    string key = "action" + actionName.Trim();
                    if (!MyIni.HasSection(block.CustomData, key))
                    {
                        Console.Print($"Missing config section '{key}'; skipping action.");
                        continue;
                    }
                    int step = Int32.Parse(Ini.Get(key, "step").ToString("0"));
                    BlockAction blockAction = buildBlockAction(block, key);
                    if (blockAction == null)
                    {
                        Console.Print($"Failed to add '{block.CustomName}' to action '{actionName}'.");
                        continue;
                    }
                    _actionGroups[id].AddActionBlock(actionName.Trim(), step, blockAction);
                }
            }

            Console.Print($"Found {_actionGroups.Count} sequences.");
            Console.UpdateTickCount();
        }

        public void Main(string argument, UpdateType updateSource)
        {
            Console.UpdateTickCount();

            if (argument != "")
            {
                _cli.TryParse(argument);

                if (_cli.ArgumentCount != 2)
                {
                    Console.Print("Must call script with exactly 2 arguments.");
                }
                else
                {
                    if (_actionGroups.ContainsKey(_cli.Argument(0)))
                    {
                        _jobs.Add(_actionGroups[_cli.Argument(0)].RunAction(_cli.Argument(1)));
                        Runtime.UpdateFrequency |= UpdateFrequency.Update10;
                    }
                    else
                    {
                        Console.Print($"Failed to find action group '{_cli.Argument(0)}'");
                    }
                }
            }

            // Process running jobs
            for (int i = 0; i < _jobs.Count; i++)
            {
                if (_jobs[i].MoveNext()) continue;

                _jobs[i].Dispose();
                _jobs.Remove(_jobs[i]);
                i--;
                Console.Print("Operation Complete.");
            }

            if (_jobs.Count == 0) Runtime.UpdateFrequency = UpdateFrequency.None;
        }

        // Prerequisite: Ini.Parse has already been called for this block.
        private BlockAction buildBlockAction(IMyTerminalBlock block, string key)
        {
            if (block is IMyDoor)
            {
                DoorAction action;
                switch (Ini.Get(key, "action").ToString("open"))
                {
                    case "open":
                        action = DoorAction.Open;
                        break;
                    case "close":
                        action = DoorAction.Close;
                        break;
                    default:
                        Console.Print($"Invalid door action for '{block.CustomName}'. Defaulting to open.");
                        action = DoorAction.Open;
                        break;
                }

                return new BlockActionDoor(
                    block as IMyDoor,
                    action,
                    Ini.Get(key, "lock").ToBoolean(true)
                );
            }
            else if (block is IMyMotorStator)
            {
                MyRotationDirection direction;
                switch (Ini.Get(key, "direction").ToString("auto"))
                {
                    case "auto":
                        direction = MyRotationDirection.AUTO;
                        break;
                    case "cw":
                    case "clockwise":
                        direction = MyRotationDirection.CW;
                        break;
                    case "ccw":
                    case "counterclockwise":
                    case "anticlockwise":
                        direction = MyRotationDirection.CCW;
                        break;
                    default:
                        Console.Print($"Invalid direction for '{block.CustomName}'. Defaulting to auto.");
                        direction = MyRotationDirection.AUTO;
                        break;
                }

                return new BlockActionRotor(
                    block as IMyMotorStator,
                    Ini.Get(key, "angle").ToSingle(0f),
                    Ini.Get(key, "velocity").ToSingle(5f),
                    direction
                );
            }
            else if (block is IMyPistonBase)
            {
                return new BlockActionPiston(
                    block as IMyPistonBase,
                    Ini.Get(key, "position").ToSingle(0f),
                    Ini.Get(key, "velocity").ToSingle(2f)
                );
            }
            else if (block is IMyShipMergeBlock)
            {
                MergeAction action;
                string config = Ini.Get(key, "action").ToString("merge").ToLower();
                switch (config)
                {
                    case "merge":
                        action = MergeAction.Merge;
                        break;
                    case "unmerge":
                        action = MergeAction.Unmerge;
                        break;
                    default:
                        Console.Print($"Unknown action '{config}. Defaulting to merge.");
                        action = MergeAction.Merge;
                        break;
                }
                return new BlockActionMerge(block as IMyShipMergeBlock, action);
            }
            else if (block is IMyLandingGear)
            {
                LandingGearAction action;
                string config = Ini.Get(key, "action").ToString("unlock").ToLower();
                switch (config)
                {
                    case "enable":
                        action = LandingGearAction.Enable;
                        break;
                    case "disable":
                        action = LandingGearAction.Disable;
                        break;
                    case "lock":
                        action = LandingGearAction.Lock;
                        break;
                    case "unlock":
                        action = LandingGearAction.Unlock;
                        break;
                }

                return new BlockActionLandingGear(block as IMyLandingGear, action);
            }
            // TODO: there are several block types added for other scripts that we should support here.

            Console.Print($"Can't add unsupported block '{block.CustomName}'");
            return null;
        }

        public bool blockFilter(IMyTerminalBlock block)
        {
            return block.IsSameConstructAs(this.Me) && MyIni.HasSection(block.CustomData, "sequencer");
        }
    }
}
