using System.Collections;
using System.Diagnostics;
using System.Text;
using Prexonite.Compiler.Symbolic;

namespace Prexonite.Compiler.Ast;

public class AstBlock : AstExpr, IList<AstNode>, IAstHasExpressions
{
    #region Construction

    protected AstBlock(
        ISourcePosition position,
        SymbolStore symbols,
        string? uid = null,
        string? prefix = null
    )
        : base(position)
    {
        _prefix = (prefix ?? DefaultPrefix) + "\\";
        BlockUid = string.IsNullOrEmpty(uid) ? "\\" + Guid.NewGuid().ToString("N") : uid;
        Symbols = symbols ?? throw new ArgumentNullException(nameof(symbols));
    }

    protected AstBlock(
        ISourcePosition position,
        AstBlock lexicalScope,
        string? prefix = null,
        string? uid = null
    )
        : this(position, _deriveSymbolStore(lexicalScope), uid, prefix) { }

    static SymbolStore _deriveSymbolStore(AstBlock parentBlock)
    {
        return SymbolStore.Create(parentBlock.Symbols);
    }

    #endregion

    /// <summary>
    /// Symbol table for the scope of this block.
    /// </summary>
    public SymbolStore Symbols { get; private set; }

    /// <summary>
    /// Replaces symbol store backing this scope. Does not affect existing nested scopes!
    /// </summary>
    /// <param name="newStore">The new symbol store.</param>
    internal void _ReplaceSymbols(SymbolStore newStore)
    {
        Symbols = newStore;
    }

    List<AstNode> _statements = [];

    public List<AstNode> Statements
    {
        get => _statements;
        set => _statements = value ?? throw new ArgumentNullException(nameof(value));
    }

    protected override void DoEmitCode(CompilerTarget target, StackSemantics stackSemantics)
    {
        EmitCode(target, false, stackSemantics);
    }

    public void EmitCode(CompilerTarget target, bool isTopLevel, StackSemantics stackSemantics)
    {
        if (target == null)
            throw new ArgumentNullException(nameof(target));

        if (isTopLevel)
            _tailCallOptimizeTopLevelBlock();

        foreach (var node in _statements)
        {
            var stmt = node;
            if (stmt is AstExpr expr)
                stmt = _GetOptimizedNode(target, expr);

            stmt.EmitEffectCode(target);
        }

        Expression?.EmitCode(target, stackSemantics);
    }

    #region Tail call optimization

    static void tail_call_optimize_expressions_of_nested_block(IAstHasExpressions hasExpressions)
    {
        foreach (var expression in hasExpressions.Expressions)
        {
            if (expression is AstBlock blockItself)
                blockItself._tailCallOptimizeNestedBlock();
            else if (expression is IAstHasExpressions hasExpressionsItself)
                tail_call_optimize_expressions_of_nested_block(hasExpressionsItself);
            else if (expression is IAstHasBlocks hasBlocksItself)
                _tailCallOptimizeAllNestedBlocksOf(hasBlocksItself);
        }
    }

    void _tailCallOptimizeNestedBlock()
    {
        int i;
        for (i = 1; i < _statements.Count; i++)
        {
            var stmt = _statements[i];

            switch (stmt)
            {
                case AstReturn { Expression: null } ret
                    when (
                        ret.ReturnVariant == ReturnVariant.Exit
                        || ret.ReturnVariant == ReturnVariant.Continue
                    )
                        && _statements[i - 1] is AstGetSet:
                    //NOTE: Aggressive TCO disabled

                    //ret.Expression = getset;
                    //Statements.RemoveAt(i--);
                    break;
                case AstBlock blockItself:
                    blockItself._tailCallOptimizeNestedBlock();
                    break;
                case IAstHasBlocks hasBlocks:
                    _tailCallOptimizeAllNestedBlocksOf(hasBlocks);
                    break;
                case IAstHasExpressions hasExpressions:
                    tail_call_optimize_expressions_of_nested_block(hasExpressions);
                    break;
            }
        }
    }

    static void _tailCallOptimizeAllNestedBlocksOf(IAstHasBlocks hasBlocks)
    {
        foreach (var block in hasBlocks.Blocks)
            block._tailCallOptimizeNestedBlock();
    }

    void _tailCallOptimizeTopLevelBlock()
    {
        // { GetSetComplex; return; } -> { return GetSetComplex; }

        _tailCallOptimizeNestedBlock();
        AstGetSet? getset;

        if (_statements.Count == 0)
            return;
        var lastStmt = _statements[^1];
        AstCondition? cond;

        // { if(cond) block1 else block2 } -> { if(cond) block1' else block2' }
        if ((cond = lastStmt as AstCondition) != null)
        {
            cond.IfBlock._tailCallOptimizeTopLevelBlock();
            cond.ElseBlock._tailCallOptimizeTopLevelBlock();
        }
        // { ...; GetSet(); } -> { ...; return GetSet(); }
        else if ((getset = lastStmt as AstGetSet) != null)
        {
            var ret = new AstReturn(getset.File, getset.Line, getset.Column, ReturnVariant.Exit)
            {
                Expression = getset,
            };
            _statements[^1] = ret;
        }
    }

    #endregion

    public virtual bool IsEmpty => Count == 0;

    public virtual bool IsSingleStatement => Count == 1;

    #region IList<AstNode> Members

    [DebuggerStepThrough]
    public int IndexOf(AstNode item)
    {
        return _statements.IndexOf(item);
    }

    [DebuggerStepThrough]
    public void Insert(int index, AstNode? item)
    {
        if (item == null)
            throw new ArgumentNullException(nameof(item));
        _statements.Insert(index, item);
    }

    public void InsertRange(int index, IEnumerable<AstNode> items)
    {
        if (items == null)
            throw new ArgumentNullException(nameof(items));
        _statements.InsertRange(index, items);
    }

    [DebuggerStepThrough]
    public void RemoveAt(int index)
    {
        _statements.RemoveAt(index);
    }

    public AstNode this[int index]
    {
        [DebuggerStepThrough]
        get => _statements[index];
        [DebuggerStepThrough]
        set => _statements[index] = value ?? throw new ArgumentNullException(nameof(value));
    }

    #endregion

    #region ICollection<AstNode> Members

    [DebuggerStepThrough]
    public void Add(AstNode? item)
    {
        if (item == null)
            throw new ArgumentNullException(nameof(item));
        _statements.Add(item);
    }

    [DebuggerStepThrough]
    public void AddRange(IEnumerable<AstNode> collection)
    {
        if (collection == null)
            throw new ArgumentNullException(nameof(collection));

        _statements.AddRange(collection);
    }

    [DebuggerStepThrough]
    public void Clear()
    {
        _statements.Clear();
    }

    [DebuggerStepThrough]
    public bool Contains(AstNode? item)
    {
        if (item == null)
            throw new ArgumentNullException(nameof(item));
        return _statements.Contains(item);
    }

    [DebuggerStepThrough]
    public void CopyTo(AstNode[] array, int arrayIndex)
    {
        _statements.CopyTo(array, arrayIndex);
    }

    public int Count
    {
        [DebuggerStepThrough]
        get => _statements.Count;
    }

    public bool IsReadOnly
    {
        [DebuggerStepThrough]
        get => ((IList<AstNode>)_statements).IsReadOnly;
    }

    [DebuggerStepThrough]
    public bool Remove(AstNode? item)
    {
        if (item == null)
            throw new ArgumentNullException(nameof(item));
        return _statements.Remove(item);
    }

    #endregion

    #region IEnumerable<AstNode> Members

    [DebuggerStepThrough]
    public IEnumerator<AstNode> GetEnumerator()
    {
        return _statements.GetEnumerator();
    }

    #endregion

    #region IEnumerable Members

    [DebuggerStepThrough]
    IEnumerator IEnumerable.GetEnumerator()
    {
        return _statements.GetEnumerator();
    }

    #endregion

    public override string ToString()
    {
        var buffer = new StringBuilder();
        foreach (var node in _statements)
            buffer.AppendFormat("{0}; ", node);
        if (Expression != null)
            buffer.AppendFormat(" (return {0})", Expression);
        return buffer.ToString();
    }

    #region Block labels

    readonly string _prefix;
    public AstExpr? Expression;

    public string BlockUid { get; }

    public virtual AstExpr[] Expressions
    {
        get
        {
            if (Expression == null)
                return [];
            else
                return [Expression];
        }
    }

    public const string DefaultPrefix = "_";
    public const string RootBlockName = "root";

    public string CreateLabel(string verb)
    {
        return string.Concat(_prefix, verb, BlockUid);
    }

    #endregion

    public override bool TryOptimize(CompilerTarget target, [NotNullWhen(true)] out AstExpr? expr)
    {
        //Will be optimized after code generation, hopefully
        if (Expression != null)
            _OptimizeNode(target, ref Expression);

        expr = null;
        return false;
    }

    public static AstBlock CreateRootBlock(
        ISourcePosition position,
        SymbolStore symbols,
        string prefix,
        string uid
    )
    {
        return new(position, symbols, uid, prefix);
    }
}
