using System.Drawing;
using System.Text;
using static System.Drawing.Color;

namespace InnovEnergy.Lib.Protocols.DBus.Utils;

public static class HexView
{
    public static String ToHexView2(this ArraySegment<Byte> segment, Boolean upToSegment = true, Int32 lineWith = 16)
    {
        var sbHex   = new StringBuilder();
        var sbAsc   = new StringBuilder();
        var sbLines = new StringBuilder();
        var data    = segment.Array!;
        var end     = upToSegment ? segment.Offset + segment.Count : data.Length;

        for (var lineStart = 0; lineStart < end; lineStart += lineWith)
        {
            sbHex.Clear();
            sbAsc.Clear();

            for (var pos = lineStart; pos < lineStart + lineWith; pos++)
            {
                if (pos >= data.Length)
                {
                    sbHex.Append("".PadRight((lineWith - (pos % lineWith)) * 3));
                    break;
                }

                var color = IsInsideSegment(pos) ? Red : Black;

                var octet = data[pos];
                sbHex.Append(octet.ToString("X2").Color(color));
                sbHex.Append(" ");

                sbAsc.Append(Printable(octet).Color(color));
            }

            sbLines.Append(lineStart.ToString().PadLeft(4) + ":  ");
            sbLines.Append(sbHex.ToString());
            sbLines.Append(" ");
            sbLines.AppendLine(sbAsc.ToString());
        }

        return sbLines.ToString();

        Boolean IsInsideSegment(Int32 pos) => pos >= segment.Offset && pos < segment.Offset + segment.Count;
    }


    public static String ToHexView(this ArraySegment<Byte> data, Int32 lineWith = 16, params (ArraySegment<Byte> segment, Color color)[] colored)
    {
        var sbHex   = new StringBuilder();
        var sbAsc   = new StringBuilder();
        var sbLines = new StringBuilder();

        for (var i = 0; i < data.Count; i += lineWith)
        {
            sbHex.Clear();
            sbAsc.Clear();

            for (var pos = i; pos < i + lineWith; pos++)
            {
                if (pos >= data.Count)
                {
                    sbHex.Append("".PadRight((lineWith - (pos % lineWith)) * 3));
                    break;
                }

                var color = colored
                    .Where(c => Inside(pos, c.segment))
                    .Select(c => c.color)
                    .FirstOrDefault();

                sbHex.Append(data[pos].ToString("X2").Color(color));
                sbHex.Append(" ");

                sbAsc.Append(Printable(data[pos]).Color(color));
            }

            sbLines.Append(i.ToString().PadLeft(4) + ":  ");
            sbLines.Append(sbHex.ToString());
            sbLines.Append(" ");
            sbLines.AppendLine(sbAsc.ToString());
        }

        return sbLines.ToString();

        Boolean Inside(Int32 pos, ArraySegment<Byte> segment)
        {
            pos += data.Offset ;
            return pos >= segment.Offset && pos < segment.Offset + segment.Count;
        }
    }

    public static String ToHexView(this IEnumerable<Byte> data, Int32 lineWith = 16)
    {
        var sbHex  = new StringBuilder();
        var sbAsc  = new StringBuilder();
        var sbLine = new StringBuilder();

        using var e = data.GetEnumerator();

        var more = true;
        var address = 0;

        do
        {
            var l = lineWith;

            while (l-- > 0)
            {
                more = e.MoveNext();
                if (!more)
                    break;

                sbHex.Append(e.Current.ToString("X2") + " ");
                sbAsc.Append(Printable(e.Current));
            }

            sbLine.Append(address.ToString().PadLeft(4) + ":  ");
            sbLine.Append(sbHex.ToString().PadRight(lineWith * 3 + 2));
            sbLine.AppendLine(sbAsc.ToString());

            sbHex.Clear();
            sbAsc.Clear();

            address += lineWith;
        }
        while (more);

        return sbLine.ToString();


    }

    private static String Printable(Byte b)
    {
        var ch = (Char) b;
        if (Char.IsLetterOrDigit(ch) || Char.IsPunctuation(ch) || Char.IsSymbol(ch) || ch == ' ')
            return ch.ToString();

        return ".";
    }

    private static String Color(this String txt, Color fg = default, Color bg = default)
    {
        const String startFg = "\u001b[38;2;";
        const String startBg = "\u001b[48;2;";
        const String end     = "\u001b[0m";

        if (fg == default && bg == default)
            return txt;

        var sb = new StringBuilder();

        if (fg != default)
        {
            sb.Append(startFg);
            sb.Append($"{fg.R};{fg.G};{fg.B}m");
        }
        if (bg != default)
        {
            sb.Append(startBg);
            sb.Append($"{fg.R};{fg.G};{fg.B}m");
        }

        sb.Append(txt);
        sb.Append(end);

        return sb.ToString();
    }
}