// Copyright © Amer Koleci and Contributors.
// Licensed under the MIT License (MIT). See LICENSE in the repository root for more information.

using System.Text;

namespace Generator;

public sealed class CodeWriter : IDisposable
{
    private bool _shouldIndent = true;
    private readonly string[] _indentStrings;
    private string _indentString = "";
    private readonly StringBuilder _builder = new();

    public int IndentLevel { get; private set; }

    public string FileName { get; }
    public string Api { get; }
    public string DocFileName { get; }

    public string Directory => Path.GetDirectoryName(FileName)!;

    public CodeWriter(string fileName, string api, string docFileName, string ns, params string[] usingNamespaces)
    {
        FileName = fileName;
        Api = api;
        DocFileName = docFileName;

        _indentStrings = new string[10];
        for (int i = 0; i < _indentStrings.Length; i++)
        {
            _indentStrings[i] = new string('\t', i);
        }

        _builder.AppendLine("// ------------------------------------------------------------------------------");
        _builder.AppendLine("// <auto-generated>");
        _builder.AppendLine("//     This code was generated by a tool.");
        _builder.AppendLine("//");
        _builder.AppendLine("//     Changes to this file may cause incorrect behavior and will be lost if");
        _builder.AppendLine("//     the code is regenerated.");
        _builder.AppendLine("// </auto-generated>");
        _builder.AppendLine("// ------------------------------------------------------------------------------");
        _builder.AppendLine();

        foreach (string usingNamespace in usingNamespaces)
        {
            _builder.AppendLine($"using {usingNamespace};");
        }

        _builder.AppendLine($"namespace {ns};");
        _builder.AppendLine();
    }

    public void Dispose()
    {
        if (string.IsNullOrEmpty(FileName) == false)
        {
            string content = _builder.ToString();
            File.WriteAllText(FileName, content);
        }
    }

    public override string ToString() => _builder.ToString();

    public void Write(char chr)
    {
        WriteIndented(chr);
    }

    public void Write(string @string)
    {
        WriteIndented(@string);
    }

    public void WriteLine()
    {
        _builder.AppendLine();
        _shouldIndent = true;
    }

    public void WriteLine(string @string)
    {
        WriteIndented(@string);
        _builder.AppendLine();
        _shouldIndent = true;
    }

    public void WriteLineUndindented(string @string)
    {
        _builder.AppendLine(@string);
        _shouldIndent = true;
    }

    public void BeginBlock(string content)
    {
        WriteLine(content);
        WriteLine("{");
        Indent(1);
    }

    public void EndBlock()
    {
        Dedent(1);
        WriteLine("}");
    }

    public IDisposable PushBlock(string marker = "{") => new CodeBlock(this, marker);

    public void Indent(int count = 1)
    {
        IndentLevel += count;

        if (IndentLevel < _indentStrings.Length)
        {
            _indentString = _indentStrings[IndentLevel];
        }
        else
        {
            _indentString = new string('\t', IndentLevel);
        }
    }

    public void Dedent(int count = 1)
    {
        if (count > IndentLevel)
            throw new ArgumentException("count out of range.", nameof(count));

        IndentLevel -= count;
        if (IndentLevel < _indentStrings.Length)
        {
            _indentString = _indentStrings[IndentLevel];
        }
        else
        {
            _indentString = new string('\t', IndentLevel);
        }
    }

    private void WriteIndented(char chr)
    {
        if (_shouldIndent)
        {
            _builder.Append(_indentString);
            _shouldIndent = false;
        }

        _builder.Append(chr);
    }

    private void WriteIndented(string @string)
    {
        if (_shouldIndent)
        {
            _builder.Append(_indentString);
            _shouldIndent = false;
        }

        _builder.Append(@string);
    }

    private class CodeBlock : IDisposable
    {
        private readonly CodeWriter _writer;

        public CodeBlock(CodeWriter writer, string content)
        {
            _writer = writer;
            _writer.BeginBlock(content);
        }

        public void Dispose()
        {
            _writer.EndBlock();
        }
    }
}
