// A utility class representing output to the console.
// Provides a Print() function that will both call MyGridProgram.Echo()
// and also output to the Programmable Block's LCD.

using Sandbox.ModAPI.Ingame;
using System;
using System.Collections.Generic;
using System.Text;
using VRage.Game.GUI.TextPanel;
using VRage.Game.ModAPI.Ingame.Utilities;

namespace IngameScript
{
    // To use this library, your Program class must implement this interface.
    //
    // A Program that supports consoles by initializing a MyIni instance
    // and providing a public console for member objects to refer back to.
    //
    // (It is probably an anti-pattern that this lives outside of the Program class,
    // but we can't find a cleaner way to implement this.)
    public interface IConsoleProgram
    {
        // Properties that should be defined by the implementer.
        MyIni Ini { get; }
        Program.IConsole Console { get; }
    }

    partial class Program
    {
        public interface IConsole
        {
            void Print(string text);
            void PrintLower(string text);
            void UpdateTickCount();
        }

        // You should only instantiate one MainConsole, typically in your Program code.
        // Either use a reference to that instance directly where needed or use it to create PrefixedConsoles.
        public class MainConsole : IConsole
        {
            private List<string> _buffer = new List<string>();
            private StringBuilder _builder = new StringBuilder();
            private int _tickCount = 0;
            private Program _program;
            private string _programName;
            private int _maxLines = 10;

            private const int DefaultMaxLines = 10;

            public MainConsole(Program program, string programName)
            {
                _program = program;
                _programName = programName;

                // Check the PB's custom data for a maxlines directive.
                // TODO: can we check for which block we are and adjust accordingly? Using screen size perhaps...
                _program.Ini.TryParse(program.Me.CustomData);
                _maxLines = _program.Ini.Get("console", "maxLines").ToInt32(DefaultMaxLines);

                // Setup the block's screens
                _program.Me.GetSurface(0).ContentType = ContentType.TEXT_AND_IMAGE;
                _program.Me.GetSurface(0).WriteText("Initializing...");
                _program.Me.GetSurface(1).ContentType = ContentType.TEXT_AND_IMAGE;
                _program.Me.GetSurface(1).FontSize = 3.5f;
                _program.Me.GetSurface(1).TextPadding = 22.5f;
                _program.Me.GetSurface(1).Alignment = TextAlignment.CENTER;
                _program.Me.GetSurface(1).WriteText("Initializing...");

                switch ((int)_program.Me.GetSurface(1).SurfaceSize.X)
                {
                    case 512:
                        _maxLines = 10;
                        break;
                    case 256:
                        _maxLines = 17;
                        break;
                    default:
                        Print("Warning: unknown Programmable Block size.");
                        break;
                }
            }

            public void Print(string text)
            {
                _program.Echo(text);
                _program.Me.GetSurface(0).WriteText(writeToBuffer(text));
            }

            // Write the "standard" text to the lower console
            public void UpdateTickCount()
            {
                PrintLower($"{_programName}\nTotal Ticks: {++_tickCount}");
            }

            // Most programs probably want to use UpdateTickCount to get program name and
            // tick information.
            // If you want something else on the lower screen, write it here. This is unbuffered,
            // so text will be replaced instead of appended.
            public void PrintLower(string text)
            {
                _program.Me.GetSurface(1).WriteText(text);
            }

            // Adds the text to the buffer, trims the top if necessary, and builds a printable
            // string.
            private string writeToBuffer(string text)
            {
                _builder.Clear();
                _buffer.Add(text);
                if (_buffer.Count > _maxLines) _buffer.RemoveAt(0);
                foreach (string line in _buffer) _builder.AppendLine(line);
                return _builder.ToString();
            }
        }

        // This class is necessary because we need to keep a *single* buffer
        // to write to. So we can ask the primary Console for a prefixer.
        public class PrefixedConsole : IConsole
        {
            private IConsole _console;
            private string _prefix;

            public PrefixedConsole(IConsole console, string prefix)
            {
                _console = console;
                _prefix = prefix + ": ";
            }

            public void Print(string text)
            {
                _console.Print(_prefix + text);
            }

            // sub-consoles can't print to the ephemeral display
            public void PrintLower(string text) { }
            public void UpdateTickCount() { }
        }

        // A replacement for the main console if you don't want the display screens involved.
        // Good if you're using the Programmable Block's screens for your own purposes, but still
        // need PrefixedConsole support.
        //
        // Content will just be printed via Echo, and PrintLower and UpdateTickCount will simply print
        // an error.
        public class EchoConsole : IConsole
        {
            private Program _program;

            public EchoConsole(Program program)
            {
                _program = program;
            }

            public void Print(string text)
            {
                _program.Echo(text);
            }

            public void PrintLower(string text) { }
            public void UpdateTickCount() { }
        }
    }
}