using libplctag;

namespace ZB.MOM.WW.OtOpcUa.Driver.AbLegacy;

/// <summary>
///     Default libplctag-backed <see cref="IAbLegacyTagRuntime"/>. Uses <c>ab_pccc</c> protocol
///     on top of EtherNet/IP — libplctag's PCCC layer handles the file-letter + word + bit +
///     sub-element decoding internally, so our wrapper just has to forward the atomic type to
///     the right Get/Set call.
/// </summary>
internal sealed class LibplctagLegacyTagRuntime : IAbLegacyTagRuntime
{
    private readonly Tag _tag;

    public LibplctagLegacyTagRuntime(AbLegacyTagCreateParams p)
    {
        _tag = new Tag
        {
            Gateway = p.Gateway,
            Path = p.CipPath,
            PlcType = MapPlcType(p.LibplctagPlcAttribute),
            Protocol = Protocol.ab_eip, // PCCC-over-EIP; libplctag routes via the PlcType-specific PCCC layer
            Name = p.TagName,
            Timeout = p.Timeout,
        };
    }

    public Task InitializeAsync(CancellationToken cancellationToken) => _tag.InitializeAsync(cancellationToken);
    public Task ReadAsync(CancellationToken cancellationToken) => _tag.ReadAsync(cancellationToken);
    public Task WriteAsync(CancellationToken cancellationToken) => _tag.WriteAsync(cancellationToken);

    public int GetStatus() => (int)_tag.GetStatus();

    public object? DecodeValue(AbLegacyDataType type, int? bitIndex) => type switch
    {
        AbLegacyDataType.Bit => bitIndex is int bit
            ? _tag.GetBit(bit)
            : _tag.GetInt8(0) != 0,
        AbLegacyDataType.Int or AbLegacyDataType.AnalogInt => (int)_tag.GetInt16(0),
        AbLegacyDataType.Long => _tag.GetInt32(0),
        AbLegacyDataType.Float => _tag.GetFloat32(0),
        AbLegacyDataType.String => _tag.GetString(0),
        AbLegacyDataType.TimerElement or AbLegacyDataType.CounterElement
            or AbLegacyDataType.ControlElement => _tag.GetInt32(0),
        _ => null,
    };

    public void EncodeValue(AbLegacyDataType type, int? bitIndex, object? value)
    {
        switch (type)
        {
            case AbLegacyDataType.Bit:
                if (bitIndex is int)
                    // Bit-within-word writes are routed at the driver level
                    // (AbLegacyDriver.WriteBitInWordAsync) via a parallel parent-word runtime —
                    // this branch only fires if dispatch was bypassed. Throw loudly rather than
                    // silently clobbering the whole word.
                    throw new NotSupportedException(
                        "Bit-with-bitIndex writes must go through AbLegacyDriver.WriteBitInWordAsync.");
                _tag.SetInt8(0, Convert.ToBoolean(value) ? (sbyte)1 : (sbyte)0);
                break;
            case AbLegacyDataType.Int:
            case AbLegacyDataType.AnalogInt:
                _tag.SetInt16(0, Convert.ToInt16(value));
                break;
            case AbLegacyDataType.Long:
                _tag.SetInt32(0, Convert.ToInt32(value));
                break;
            case AbLegacyDataType.Float:
                _tag.SetFloat32(0, Convert.ToSingle(value));
                break;
            case AbLegacyDataType.String:
                _tag.SetString(0, Convert.ToString(value) ?? string.Empty);
                break;
            case AbLegacyDataType.TimerElement:
            case AbLegacyDataType.CounterElement:
            case AbLegacyDataType.ControlElement:
                _tag.SetInt32(0, Convert.ToInt32(value));
                break;
            default:
                throw new NotSupportedException($"AbLegacyDataType {type} not writable.");
        }
    }

    public void Dispose() => _tag.Dispose();

    private static PlcType MapPlcType(string attribute) => attribute switch
    {
        "slc500" => PlcType.Slc500,
        "micrologix" => PlcType.MicroLogix,
        "plc5" => PlcType.Plc5,
        "logixpccc" => PlcType.LogixPccc,
        _ => PlcType.Slc500,
    };
}

internal sealed class LibplctagLegacyTagFactory : IAbLegacyTagFactory
{
    public IAbLegacyTagRuntime Create(AbLegacyTagCreateParams createParams) =>
        new LibplctagLegacyTagRuntime(createParams);
}
