using System.Diagnostics;
using Prexonite.Properties;

namespace Prexonite.Compiler.Ast;

/// <summary>
///     AST node that represents a partial application placeholder ('?'). Optionally has an index assigned (e.g., '?5')
/// </summary>
public class AstPlaceholder : AstGetSetImplBase
{
    public const int MaxPlaceholderIndex = 127;

    int? _index;

    /// <summary>
    ///     The explicit argument index, if one is set. 0-based.
    /// </summary>
    public int? Index
    {
        get => _index;
        set
        {
            if (value < 0)
                throw new ArgumentOutOfRangeException(
                    nameof(value),
                    Resources.AstPlaceholder_PlaceholdeIndexNegative
                );
            _index = value;
        }
    }

    public AstPlaceholder(string file, int line, int column)
        : this(file, line, column, null) { }

    public AstPlaceholder(string file, int line, int column, int? index)
        : base(file, line, column, PCall.Get)
    {
        Index = index;
    }

    internal AstPlaceholder(Parser p, int? index = null)
        : base(p, PCall.Get)
    {
        Index = index;
    }

    #region Overrides of AstNode

    protected override void EmitGetCode(CompilerTarget target, StackSemantics stackSemantics)
    {
        _throwSyntaxNotSupported();
    }

    void _throwSyntaxNotSupported()
    {
        throw new PartialApplicationSyntaxNotSupportedException(
            $"This syntax does not support placeholders. (Position {File}:{Line} col {Column})"
        );
    }

    protected override void EmitSetCode(CompilerTarget target)
    {
        _throwSyntaxNotSupported();
    }

    public override AstGetSet GetCopy()
    {
        var copy = new AstPlaceholder(File, Line, Column, Index);
        CopyBaseMembers(copy);
        return copy;
    }

    #endregion

    public static void DeterminePlaceholderIndices(IEnumerable<AstPlaceholder> placeholders)
    {
        //Placeholders must be assigned in two phases, because placeholders that already have an index set take priority
        var assigned = new List<AstPlaceholder?>();
        var unassigned = new List<AstPlaceholder>();

        //Phase 1: assign placeholders with an index set, and keep the rest for later (but retain their order)
        foreach (var placeholder in placeholders)
        {
            if (placeholder.Index.HasValue)
            {
                if (placeholder.Index.Value > 127)
                {
                    throw new PrexoniteException(
                        $"The placeholder (at {placeholder.Position.GetSourcePositionString()}) has a custom index value that exceeds the maximum mappable index."
                    );
                }

                if (assigned.Count <= placeholder.Index)
                {
                    for (var i = assigned.Count; i < placeholder.Index; i++)
                        assigned.Add(null);
                    assigned.Add(placeholder);
                }
                else
                {
                    assigned[placeholder.Index.Value] = placeholder;
                }
                Debug.Assert(
                    ReferenceEquals(assigned[placeholder.Index.Value], placeholder),
                    "placeholder was not inserted at the right spot."
                );
            }
            else
            {
                unassigned.Add(placeholder);
            }
        }

        //Phase 2: assign placeholders with no index in the order they appeared
        var index = 0;
        foreach (var placeholder in unassigned)
        {
            //search for free spot
            for (; index < assigned.Count; index++)
                if (assigned[index] == null)
                    break;
            //it is not actually necessary to add the placeholder to the assigned list
            //  instead we just assign the index it would occupy
            placeholder.Index = index++;

            if (index > MaxPlaceholderIndex)
                throw new PrexoniteException(
                    $"The placeholder (at {placeholder.Position.GetSourcePositionString()}) would be assigned an index that exceeds the maximum mappable index."
                );
        }
    }

    public override string ToString()
    {
        if (_index.HasValue)
            return "?" + _index.Value;
        else
            return "?";
    }
}

public class PartialApplicationSyntaxNotSupportedException : PrexoniteException
{
    public PartialApplicationSyntaxNotSupportedException() { }

    public PartialApplicationSyntaxNotSupportedException(string message)
        : base(message) { }

    public PartialApplicationSyntaxNotSupportedException(string message, Exception inner)
        : base(message, inner) { }
}
