📄 lib/src/Internal/DltMessageParser.cs
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
using SharpDlt.Headers;
using SharpDlt.Payloads;

namespace SharpDlt.Internal;

/// <summary>Composes a <see cref="DltMessage"/> out of the parts of a DLT message.</summary>
internal static class DltMessageParser
{
    public static DltMessage Parse(ReadOnlySpan<byte> data, bool withStorageHeader, Encoding? encoding)
    {
        var offset = 0;
        StorageHeader? storageHeader = null;
        if (withStorageHeader)
        {
            storageHeader = StorageHeader.Parse(data);
            offset += StorageHeader.Length;
        }

        var standardHeader = StandardHeader.Parse(data[offset..]);
        offset += standardHeader.ByteLength;

        ExtendedHeader? extendedHeader = null;
        var extendedHeaderLength = 0;
        if (standardHeader.UseExtendedHeader)
        {
            extendedHeader = ExtendedHeader.Parse(data[offset..]);
            extendedHeaderLength = ExtendedHeader.Length;
            offset += extendedHeaderLength;
        }

        DltPayload? payload = null;
        if (standardHeader.Length > standardHeader.ByteLength + extendedHeaderLength)
        {
            var storageHeaderLength = withStorageHeader ? StorageHeader.Length : 0;
            var end = Math.Min(storageHeaderLength + standardHeader.Length, data.Length);
            if (end < offset)
            {
                throw new DltParseException(
                    $"Unexpected length of the data: {data.Length} / "
                        + $"the message declares a length of {standardHeader.Length}."
                );
            }

            var payloadData = data[offset..end];
            payload = extendedHeader is { Verbose: true }
                ? VerbosePayload.Parse(payloadData, standardHeader.MsbFirst, extendedHeader.NumberOfArguments, encoding)
                : NonVerbosePayload.Parse(payloadData, standardHeader.MsbFirst);
        }

        return new DltMessage(storageHeader, standardHeader, extendedHeader, payload);
    }

    public static string Describe(DltMessage message)
    {
        var parts = new List<string>(12);
        var storageHeader = message.StorageHeader;
        var standardHeader = message.StandardHeader;
        var extendedHeader = message.ExtendedHeader;

        if (storageHeader is not null)
        {
            parts.Add(
                storageHeader.Timestamp.UtcDateTime.ToString("yyyy/MM/dd HH:mm:ss", CultureInfo.InvariantCulture)
                    + "."
                    + storageHeader.Microseconds.ToString("D6", CultureInfo.InvariantCulture)
            );
        }

        if (standardHeader.Timestamp is { } timestamp)
        {
            parts.Add(
                (timestamp / 10000).ToString(CultureInfo.InvariantCulture)
                    + "."
                    + (timestamp % 10000).ToString("D4", CultureInfo.InvariantCulture)
            );
        }

        parts.Add(standardHeader.MessageCounter.ToString(CultureInfo.InvariantCulture));

        if (standardHeader.EcuId is not null)
        {
            parts.Add(standardHeader.EcuId);
        }
        else if (storageHeader is not null)
        {
            parts.Add(storageHeader.EcuId);
        }

        if (extendedHeader is not null)
        {
            parts.Add(extendedHeader.ApplicationId);
            parts.Add(extendedHeader.ContextId);
        }

        if (standardHeader.SessionId is { } sessionId)
        {
            parts.Add(sessionId.ToString(CultureInfo.InvariantCulture));
        }

        if (extendedHeader is not null)
        {
            parts.Add(DescribeMessageType(extendedHeader.MessageType));
            parts.Add(DescribeMessageTypeInfo(extendedHeader));
        }

        parts.Add(extendedHeader is { Verbose: true } ? "verbose" : "non-verbose");

        if (extendedHeader is not null)
        {
            parts.Add(extendedHeader.NumberOfArguments.ToString(CultureInfo.InvariantCulture));
        }

        if (message.Payload is not null)
        {
            parts.Add(message.Payload.ToString());
        }

        return string.Join(' ', parts);
    }

    private static string DescribeMessageType(DltMessageType messageType) =>
        messageType switch
        {
            DltMessageType.Log => "log",
            DltMessageType.AppTrace => "app_trace",
            DltMessageType.NetworkTrace => "nw_trace",
            DltMessageType.Control => "control",
            _ => "unknown",
        };

    private static string DescribeMessageTypeInfo(ExtendedHeader header) =>
        header.MessageType switch
        {
            DltMessageType.Log => header.LogInfo switch
            {
                DltLogInfo.Fatal => "fatal",
                DltLogInfo.Error => "error",
                DltLogInfo.Warn => "warn",
                DltLogInfo.Info => "info",
                DltLogInfo.Debug => "debug",
                DltLogInfo.Verbose => "verbose",
                _ => "unknown",
            },
            DltMessageType.AppTrace => header.TraceInfo switch
            {
                DltTraceInfo.Variable => "variable",
                DltTraceInfo.FunctionIn => "func_in",
                DltTraceInfo.FunctionOut => "func_out",
                DltTraceInfo.State => "state",
                DltTraceInfo.Vfb => "vfb",
                _ => "unknown",
            },
            DltMessageType.NetworkTrace => header.BusInfo switch
            {
                DltBusInfo.Ipc => "ipc",
                DltBusInfo.Can => "can",
                DltBusInfo.FlexRay => "flexray",
                DltBusInfo.Most => "most",
                _ => "unknown",
            },
            DltMessageType.Control => header.ControlInfo switch
            {
                DltControlInfo.Request => "request",
                DltControlInfo.Response => "response",
                DltControlInfo.Time => "time",
                _ => "unknown",
            },
            _ => "unknown",
        };
}