using System.Text;
using InnovEnergy.Lib.Protocols.DBus.Protocol.DataTypes;
using InnovEnergy.Lib.Protocols.DBus.Protocol.DataTypes.Signatures;

namespace InnovEnergy.Lib.Protocols.DBus.WireFormat;

internal abstract class DBusReader
{
    public Boolean SwapEndian { get; }

    public abstract Int32 BytesRead { get; }
    public abstract Byte  ReadByte();
    public abstract ArraySegment<Byte> ReadSegment(Int32 size); // TODO

    protected DBusReader(Boolean swapEndian) => SwapEndian = swapEndian;

    public Int16 ReadInt16()
    {
        var span = ReadAlignedSpan(sizeof(Int16));
        return BitConverter.ToInt16(span);
    }

    public UInt16 ReadUInt16()
    {
        var span = ReadAlignedSpan(sizeof(UInt16));
        return BitConverter.ToUInt16(span);
    }

    public Int32 ReadInt32()
    {
        var span = ReadAlignedSpan(sizeof(Int32));
        return BitConverter.ToInt32(span);
    }

    public UInt32 ReadUInt32()
    {
        var span = ReadAlignedSpan(sizeof(UInt32));
        return BitConverter.ToUInt32(span);
    }

    public Int64 ReadInt64()
    {
        var span = ReadAlignedSpan(sizeof(Int64));
        return BitConverter.ToInt64(span);
    }

    public UInt64 ReadUInt64()
    {
        var span = ReadAlignedSpan(sizeof(UInt64));
        return BitConverter.ToUInt64(span);
    }

    public Boolean ReadBoolean()
    {
        var uInt32 = ReadUInt32();

        return uInt32 switch
        {
            0 => false,
            1 => true,
            _ => throw new Exception($"Read value {uInt32} at position " +
                                     $"{BytesRead - sizeof(UInt32)} " +
                                     "while expecting boolean (0/1)")
        };
    }

    public Double ReadDouble()
    {
        var span = ReadAlignedSpan(sizeof(Double));
        return BitConverter.ToDouble(span);
    }

    public String ReadString()
    {
        var strLen = ReadInt32();
        var span   = ReadSegment(strLen);
        var str    = Encoding.UTF8.GetString(span);

        SkipNull();

        return str;
    }

    public ObjectPath ReadObjectPath() => ReadString();

    public Signature ReadSignature()
    {
        var sigLen = ReadByte();
        var span   = ReadSegment(sigLen);
        var sig    = Encoding.ASCII.GetString(span);  // only ascii chars allowed in sig

        SkipNull();

        return Signature.FromString(sig);
    }

    public Variant ReadVariant()
    {
        var signature = ReadSignature();
        var value     = signature.Read(this);

        return new Variant(value, signature);
    }

    public void AlignForComposite() => Align(8);  // structs/arrays/dict elements are always 8-aligned

    private ReadOnlySpan<Byte> ReadAlignedSpan(Int32 size)
    {
        Align(size);

        var start = BytesRead;
        var span  = ReadSegment(size);

        if (SwapEndian)
            Array.Reverse(span.Array!, span.Offset + start, size);

        return span;
    }

    private void SkipNull()
    {
        if (ReadByte() != 0)
            throw new Exception("Read a non-zero byte while expecting a null byte");
    }

    private void Align(Int32 alignment)
    {
        while (BytesRead % alignment != 0)
            SkipNull();
    }

    public override String ToString() => $"@{BytesRead}";
}

// internal abstract class DBusReader
// {
//     public static Boolean Debug { get; set; }
//     public Boolean SwapEndian { get; }
//
//     public abstract Int32 BytesRead { get; }
//     public abstract Byte  ReadByte();
//     public abstract ArraySegment<Byte> ReadSegment(Int32 size); // TODO
//
//     protected DBusReader(Boolean swapEndian) => SwapEndian = swapEndian;
//
//     public Int16 ReadInt16()
//     {
//         var span = ReadAlignedSpan(sizeof(Int16));
//         var r = BitConverter.ToInt16(span);
//
//         if (Debug) Console.WriteLine("ReadInt16 " + r);
//
//         return r;
//     }
//
//     public UInt16 ReadUInt16()
//     {
//         var span = ReadAlignedSpan(sizeof(UInt16));
//         var r = BitConverter.ToUInt16(span);
//         if (Debug) Console.WriteLine("ReadUInt16 " + r);
//         return r;
//     }
//
//     public Int32 ReadInt32()
//     {
//         var span = ReadAlignedSpan(sizeof(Int32));
//         var r = BitConverter.ToInt32(span);
//         if (Debug) Console.WriteLine("ReadInt32 " + r);
//         return r;
//     }
//
//     public UInt32 ReadUInt32()
//     {
//         var span = ReadAlignedSpan(sizeof(UInt32));
//         var r = BitConverter.ToUInt32(span);
//         if (Debug) Console.WriteLine("ReadUInt32 " + r);
//         return r;
//     }
//
//     public Int64 ReadInt64()
//     {
//         var span = ReadAlignedSpan(sizeof(Int64));
//         var r = BitConverter.ToInt64(span);
//         if (Debug) Console.WriteLine("ReadInt64 " + r);
//         return r;
//     }
//
//     public UInt64 ReadUInt64()
//     {
//         var span = ReadAlignedSpan(sizeof(UInt64));
//         var r = BitConverter.ToUInt64(span);
//         if (Debug) Console.WriteLine("ReadUInt64 " + r);
//         return r;
//     }
//
//     public Boolean ReadBoolean()
//     {
//         var uInt32 = ReadUInt32();
//
//         var r = uInt32 switch
//         {
//             0 => false,
//             1 => true,
//             _ => throw new ProtocolException($"Read value {uInt32} at position " +
//                                              $"{BytesRead - sizeof(UInt32)} " +
//                                              "while expecting boolean (0/1)")
//         };
//
//         if (Debug) Console.WriteLine("ReadBoolean " + r);
//         return r;
//     }
//
//     public Double ReadDouble()
//     {
//         var span = ReadAlignedSpan(sizeof(Double));
//         var r = BitConverter.ToDouble(span);
//         if (Debug) Console.WriteLine("ReadDouble " + r);
//         return r;
//     }
//
//     public String ReadString()
//     {
//         var strLen = ReadInt32();
//         var span   = ReadSegment(strLen);
//         var str    = Encoding.UTF8.GetString(span);
//         if (Debug) Console.WriteLine("ReadString " + str);
//         SkipNull();
//         return str;
//     }
//
//     public ObjectPath ReadObjectPath()
//     {
//         if (Debug) Console.WriteLine("ReadObjectPath:");
//         return ReadString();
//     }
//
//     public Signature ReadSignature()
//     {
//         if (Debug) Console.WriteLine("ReadSignature:");
//         var sigLen = ReadByte();
//         var span   = ReadSegment(sigLen);
//         var sig    = Encoding.ASCII.GetString(span);  // only ascii chars allowed in sig
//         SkipNull();
//         return Signature.FromString(sig);
//     }
//
//     public Variant ReadVariant()
//     {
//         if (Debug) Console.WriteLine("ReadVariant:");
//         var signature = ReadSignature();
//         var value     = signature.Read(this);
//
//         return new Variant(value, signature);
//     }
//
//     public void AlignForComposite() => Align(8);  // structs/arrays/dict elements are always 8-aligned
//
//     private ReadOnlySpan<Byte> ReadAlignedSpan(Int32 size)
//     {
//         Align(size);
//
//         var start = BytesRead;
//         var span  = ReadSegment(size);
//
//         if (SwapEndian)
//             Array.Reverse(span.Array!, span.Offset + start, size);
//
//         return span;
//     }
//
//     private void SkipNull()
//     {
//         if (ReadByte() != 0)
//             throw new ProtocolException("Read a non-zero byte while expecting a null byte");
//     }
//
//     private void Align(Int32 alignment)
//     {
//         while (BytesRead % alignment != 0)
//             SkipNull();
//     }
//
//     public override String ToString() => $"@{BytesRead}";
// }