
using System.Numerics;
using Tommy;

namespace Voile.Resources.DataReaders;

/// <summary>
/// Reads key/value data from a TOML file.
/// </summary>
public class TomlDataReader : IStreamDataReader, IDataValidator, IStreamKeyValueGetter
{
    public TomlDataReader() { }

    public void Read(Stream data)
    {
        using var reader = new StreamReader(data);
        _table = TOML.Parse(reader);
        _valid = _table != null;
    }

    public bool Valid() => _valid;

    public bool HasKey(string key)
    {
        return _table != null &&
               _table.HasKey(key);
    }

    public IEnumerable<string> GetSubKeys()
    {
        if (_table == null)
            return Enumerable.Empty<string>();

        return _table.Keys
            .Where(k => _table[k].IsTable)
            .ToList();
    }

    public IEnumerable<string> GetSubKeysRecursive(string prefix = "")
    {
        if (_table == null)
            yield break;

        foreach (var key in _table.Keys)
        {
            var fullKey = string.IsNullOrEmpty(prefix) ? key : $"{prefix}.{key}";
            if (_table[key].IsTable)
            {
                var subReader = GetSubReader(key);
                if (subReader != null)
                {
                    foreach (var subKey in subReader.GetSubKeysRecursive(fullKey))
                        yield return subKey;

                    yield return fullKey;
                }
            }
        }
    }

    public IEnumerable<string> GetSubKeys(string subPath)
    {
        var subReader = GetSubReader(subPath);
        if (subReader?._table == null)
            return Enumerable.Empty<string>();

        return subReader._table.Keys
            .Where(k => subReader._table[k].IsTable)
            .ToList();
    }

    public TomlDataReader? GetSubReader(string path)
    {
        var current = _table;
        foreach (var part in path.Split('.'))
        {
            if (current == null || !current.HasKey(part) || !current[part].IsTable)
                return null;

            current = current[part].AsTable;
        }

        return new TomlDataReader { _table = current, _valid = true };
    }

    public bool GetBool(string key, bool defaultValue = false)
        => TryGetNode(key, out var node) && node.IsBoolean ? node.AsBoolean : defaultValue;

    public int GetInt(string key, int defaultValue = 0)
        => TryGetNode(key, out var node) && node.IsInteger ? node.AsInteger : defaultValue;

    public long GetLong(string key, long defaultValue = 0)
        => TryGetNode(key, out var node) && node.IsInteger ? node.AsInteger.Value : defaultValue;

    public float GetFloat(string key, float defaultValue = 0)
    {
        if (!TryGetNode(key, out var node))
            return defaultValue;

        if (node.IsFloat) return node.AsFloat;
        if (node.IsInteger) return node.AsInteger;
        return defaultValue;
    }

    public double GetDouble(string key, double defaultValue = 0)
        => TryGetNode(key, out var node) && node.IsFloat ? node.AsFloat : defaultValue;

    public string GetString(string key, string defaultValue)
    {
        if (!TryGetNode(key, out var node))
        {
            return defaultValue;
        }

        if (node.IsString)
        {
            return node.AsString;
        }

        return defaultValue;
    }

    public Color GetColor(string key, Color defaultValue)
    {
        if (!TryGetNode(key, out var node))
            return defaultValue;

        if (node.IsInteger)
        {
            return new Color(node.AsInteger);
        }
        else if (node.IsArray)
        {
            var colorArray = node.AsArray;

            var rNode = colorArray[0];
            var gNode = colorArray[1];
            var bNode = colorArray[2];

            var r = rNode.IsInteger ? rNode.AsInteger : 0;
            var g = gNode.IsInteger ? gNode.AsInteger : 0;
            var b = bNode.IsInteger ? bNode.AsInteger : 0;

            int a = 255;

            if (colorArray.RawArray.Count == 4)
            {
                var aNode = colorArray[3];
                a = aNode.IsInteger ? aNode.AsInteger : 0;
            }

            return new Color((byte)r, (byte)g, (byte)b, (byte)a);
        }
        else if (node.IsString)
        {
            var colorHexString = node.AsString.Value;
            return Color.FromHexString(colorHexString);
        }
        else
        {
            return defaultValue;
        }
    }

    public Vector2 GetVector2(string key, Vector2 defaultValue)
    {
        if (!TryGetNode(key, out var node) || !node.IsArray || node.AsArray.RawArray.Count != 2)
            return defaultValue;

        var arr = node.AsArray;
        return new Vector2(arr[0], arr[1]);
    }

    public Size GetSize(string key, Size defaultValue)
    {
        if (!TryGetNode(key, out var node))
            return defaultValue;

        if (node.IsInteger)
        {
            return new Size(node.AsInteger);
        }
        else if (node.IsFloat)
        {
            return new Size(node.AsFloat);
        }
        else if (node.IsArray)
        {
            var sizeArray = node.AsArray;

            var lNode = sizeArray[0];
            var rNode = sizeArray[1];
            var tNode = sizeArray[2];
            var bNode = sizeArray[3];

            var l = lNode.IsInteger ? lNode.AsInteger : 0;
            var t = tNode.IsInteger ? tNode.AsInteger : 0;
            var r = rNode.IsInteger ? rNode.AsInteger : 0;
            var b = bNode.IsInteger ? bNode.AsInteger : 0;

            return new Size(l, r, t, b);
        }

        return defaultValue;
    }

    public T[] GetArray<T>(string key, T[] defaultValue)
    {
        throw new NotImplementedException("Generic array reading not implemented yet.");
    }

    private bool TryGetNode(string key, out TomlNode node)
    {
        node = null!;
        if (_table == null)
            return false;

        var current = _table;
        var parts = key.Split('.');

        for (int i = 0; i < parts.Length; i++)
        {
            if (!current.HasKey(parts[i]))
                return false;

            var child = current[parts[i]];

            if (i == parts.Length - 1)
            {
                node = child;
                return true;
            }

            if (!child.IsTable)
                return false;

            current = child.AsTable;
        }

        return false;
    }

    private TomlTable? _table;
    private bool _valid;

    // Internal use for subreaders
    private TomlDataReader(TomlTable table)
    {
        _table = table;
        _valid = true;
    }
}